(ps: 太字の斜体は、指導スキームの段階的な進化の重要なポイントを示しています)
SurfaceView のフレームごとの分析とフレーム多重化
前回の記事の内容を簡単におさらいすると、ネイティブ フレーム アニメーションは再生前にすべてのフレームを解析するため、メモリに大きな負荷がかかります。SurfaceView
フレーム アニメーションの各フレームの描画は細かく制御でき、各フレームが描画される前に現在のフレームが解析され、後続のフレームを解析するときに前のフレームのメモリ空間が再利用されます。したがって、プロセス全体はメモリ内のフレーム サイズのスペースにのみ適用されます。いくつかのキーコードを以下に示します。
基本クラス: 描画フレームワークを定義します。
public abstract class BaseSurfaceView extends SurfaceViewimplements SurfaceHolder.Callback { ... //描画スレッドprivate HandlerThread handlerThread; private Handler handler;
@Override
public void surfaceCreated(SurfaceHolderholder) { startDrawThread(); }
private void startDrawThread() { handlerThread = new HandlerThread(“SurfaceViewThread”); handlerThread.start(); ハンドラー = 新しいハンドラー(handlerThread.getLooper()); handler.post(new DrawRunnable()); }
private class DrawRunnableimplements Runnable { @Override public void run() { try { Canvas = getHolder().lockCanvas(); // デコード + 描画フレームを含むフレームを描画onFrameDraw(canvas); } catch (Exception e) { e .printStackTrace(); }finally { getHolder().unlockCanvasAndPost(canvas); onFrameDrawFinish(); } //onFrameDraw() の実行がタイムアウトすると、次のフレームの描画が延期され、スケジュールされたフレーム時間間隔が延長されます。ハンドラー .postDelayed(this, FrameDuration); } }は有効になりません。
protected abstract void onFrameDraw(Canvas Canvas);
}
//フレーム アニメーション描画クラス: 描画コンテンツをビットマップに具体化します
public class FrameSurfaceView extends BaseSurfaceView { … private BitmapFactory.Options options;
@Override
protected void onFrameDraw(Canvas Canvas) { clearCanvas(canvas); if (!isStart()) { 戻り値; } if (!isFinish()) { //绘制一帧drawOneFrame(canvas); } else { onFrameAnimationEnd(); } }
private voiddrawOneFrame(Canvas Canvas) { //解析フレームFrameBitmap = BitmapFactory.decodeResource(getResources(), bitmaps.get(bitmapIndex), options); //フレームを再利用options.inBitmap = FrameBitmap; //フレームを描画Canvas.drawBitmap ( FrameBitmap、srcRect、dstRect、ペイント); bitmapIndex++; } … }
画像解析速度を比較する
マテリアルが 100k 未満のフレーム アニメーションの場合、前の記事のフレームごとの分析スキームが完全に機能します。ただし、材料が数百 k である場合、タイミング性能は期待どおりではありません。
熱心な友人「Small Forward」は、「あなたの計画は大きな画像でテストされましたか? たとえば、1024*768px です」と尋ねました。
このサイズのフレームアニメーションをSurfaceView上で1フレームずつ試してみたところ、再生処理は連続しているものの、600msのフレームアニメーションが1秒に収まってしまいました。各フレームの事前に定義された再生時間がデコード時間によって長くなるからです。
これよりも高速にデコードする方法はありますかBitmapFactory.decodeResource()
?
BitmapFactory.decodeStream()
そこで、 、を含むさまざまな画像のデコード速度を比較しBitmapFactory.decodeResource()
、画像をそれぞれres/raw
、res/drawable
、 、に配置したassets
ところ、RapidDecoder
GitHub でこのライブラリを見つけました (興奮!)。関数の実行時間を測定するためのカスタマイズされたツール クラス:
public class MethodUtil { //単一関数の実行時間を測定して出力public static long time(Runnable runnable) { long start = SystemClock.elapsedRealtime(); runnable.run(); long end = SystemClock.elapsedRealtime(); longスパン = 終了 - 開始; Log.v("ttaylor", "MethodUtil.time()" + " 時間スパン = " + スパン + " ms");戻りスパン; } }
public class NumberUtil { private static long total; プライベート静的 int 回。プライベート静的文字列タグ。
//統計情報を表示し、複数の実行時間の平均値を出力します
public static void Average(String tag, Long l) { if (!TextUtils.isEmpty(tag) && !tag.equals(NumberUtil.tag)) { replace(); NumberUtil .tag = タグ; }回++;合計 += l; int 平均 = 合計 / 回 ; Log.v("ttaylor", "Average.average() " + NumberUtil.tag + " Average = " + Average); }
プライベート静的無効リセット() { 合計 = 0; 回 = 0; } }
複数のテストの平均値をとった後、最も長い実行時間は最も短い実行時間でありBitmapFactory.decodeResource()
、最も短いのは画像解析を使用することでBitmapFactory.decodeStream()
assets
、後者は前者の半分の時間しかかかりません。RapidDecoder
ライブラリの時間はその中間ですが (非常に残念〜)、描画しながらデコードするテクニックが提供されています。これは、最初にデコードしてから描画するよりも速いと言われており、まだ試す時間がありません。
デコード時間は半分になりますが、1MB のピクチャをデコードするには 60 ミリ秒以上かかり、依然として時間パフォーマンス要件を満たしていません。
独立したデコードスレッド
現状の矛盾は、画像の解析速度が画像の描画速度よりも遅いことであり、デコードと描画を同一スレッド内で直列に行うと、デコードによる描画効率の低下が避けられません。
画像を別のスレッドでデコードすることは可能ですか?
前の記事に基づいて、FrameSurfaceView
独立したデコード スレッドが追加されています。
public class FrameSurfaceView extends BaseSurfaceView { … //独立したデコードスレッドprivate HandlerThread decodeThread; //デコードアルゴリズムはここに書かれていますprivate DecodeRunnable decodeRunnable;
//フレームアニメーション再生時にデコードスレッドを開始
public void start() { decodeThread = new HandlerThread(DECODE_THREAD_NAME); decodeThread.start(); handler = new Handler(decodeThread.getLooper()); handler.post(decodeRunnable); }
プライベート クラス DecodeRunnable は Runnable {を実装します。
@Override
public void run() { //ここでデコードします} } }
このように、基本クラスには独立した描画スレッドがあり、サブクラスには独立したデコード スレッドが存在し、デコード速度が描画速度に影響を与えることはなくなります。
新しい疑問が生じます。画像はデコードされた後、どこに保存されるのでしょうか?
生産者と消費者
デコードされた画像を格納するコンテナには、画像を取得する描画スレッド (コンシューマ) と、画像を格納するデコード スレッド (プロデューサー) の 2 つのスレッドがアクセスします。スレッドの同期を考慮する必要があります。最初に思い浮かぶのは、LinkedBlockingQueue
サイズFrameSurfaceView
1 のブロッキング キューとアクセス操作が次の場所に追加されていることです。
public class FrameSurfaceView extends BaseSurfaceView { … //分析キュー: 解析されたフレーム素材を格納private LinkedBlockingQueue decodedBitmaps = new LinkedBlockingQueue<>(1); //描画されたフレームの数を記録private int FrameIndex;
//存解コード図片
private void putDecodedBitmap(int resId, BitmapFactory.Options options) { ビットマップ bitmap = decodeBitmap(resId, options); { decodedBitmaps.put(bitmap);を試してください。} catch (InterruptedException e) { e.printStackTrace(); } }
//取解解图片
private Bitmap getDecodedBitmap() { Bitmap bitmap = null; { bitmap = decodedBitmaps.take();を試してください。} catch (InterruptedException e) { e.printStackTrace(); ビットマップを返します。}
//解版図片
private Bitmap decodeBitmap(int resId, BitmapFactory.Options options) { options.inScaled = false; 入力ストリーム inputStream = getResources().openRawResource(resId); return BitmapFactory.decodeStream(inputStream, null, options); }
private voiddrawOneFrame(Canvas Canvas) { //描画スレッドでデコードされた画像を取得し、ビットマップを描画しますbitmap = getDecodedBitmap(); if (bitmap != null) { Canvas.drawBitmap(bitmap, srcRect, dstRect, Pain); }フレームインデックス++; }
プライベート クラス DecodeRunnable は Runnable { プライベート int インデックスを実装します。private ビットマップ ID のリスト。プライベート BitmapFactory.Options オプション。
public DecodeRunnable(int インデックス、リスト bitmapIds、BitmapFactory.Options オプション) { this.index = インデックス; this.bitmapIds = ビットマップ ID; this.options = オプション; }
@Override
public void run() { //デコード スレッドで画像をデコードしますputDecodedBitmap(bitmapIds.get(index), options); Index++; if (index < bitmapIds.size()) { handler.post(this); } else { インデックス = 0; } } } }
- 描画スレッドは、各描画の前にブロックされた
take()
フレーム ピクチャを解析キューの先頭から呼び出し、デコード スレッドはブロックされたput()
フレーム ピクチャを解析キューの末尾まで連続的に呼び出します。 assets
ディレクトリ内の画像の解析速度が最も速いですが、res/raw
ディレクトリの速度もそれとほぼ同じなので、ここでは簡単のため、openRawResource
読み込みres/raw
中の画像を使用します。- デコードと描画は別スレッドですが、デコードされたピクチャコンテナのサイズが1の場合、描画処理はデコードスレッドを待たなければならず、やはりデコード速度の分だけ描画速度が遅くなります。お互いに影響を与えることはなく、実際にはお互いを抑制します。
スライディング ウィンドウ メカニズムと事前解析
速度の異なるプロデューサーとコンシューマーがよりスムーズに連携するには、より高速な側にバッファを提供する必要があります。
TCP 輻輳制御と同様に滑动窗口机制
、送信者は受信者がメッセージを消費するよりも速くメッセージを生成するため、送信者は次のメッセージを送信する前に前のメッセージの確認を待つ必要がありません。
現在のケースでは、画像ストレージ コンテナのサイズを増やし、フレーム アニメーションが開始される前に最初の数フレームを事前解析し、解析キューに格納する必要があります。
public class FrameSurfaceView extends BaseSurfaceView { … //次に解析するマテリアルのインデックスprivate int bitmapIdIndex; //フレーム アニメーション マテリアル コンテナprivate List bitmapIds = new ArrayList<>(); //サイズ 3 の分析キューprivate LinkedBlockingQueueデコードされたビットマップ = 新しい LinkedBlockingQueue<>(3);
//受信フレームのアニメーション素材
public void setBitmapIds(List bitmapIds) { if (bitmapIds == null || bitmapIds.size() == 0) { return; } this.bitmapIds = bitmapIds; preloadFrames(); }
//最初の数フレームを事前解析する
private void preloadFrames() { //フレームを分析し、画像を解析キューに入れるputDecodedBitmap(bitmapIds.get(bitmapIdIndex++), options); putDecodedBitmap(bitmapIds.get(bitmapIdIndex++), options ); } }
独立したデコード スレッド、スライディング ウィンドウ メカニズム、およびプリロードはすべてコーディングされています。いくつかのコードを実行します (驚くまでお待ちください~)。
スムーズに再生が始まりました!興奮して思わず何度も放送してしまいました。。。メモリ モニターをオンにして見て (頭の上の 3 本の垂直線)、一晩解放前に戻ります。再生されるたびに、N 個のビットマップ オブジェクトがメモリに追加されます (N はフレームの総数です)。アニメーション フレーム)。
元の再構築プロセスでは、デコード中のフレーム多重化ロジックが削除されました。今回の場合、フレーム多重化も複雑になる。
多重化キュー
デコードと描画が 1 つのスレッドでシリアルに実行され、1 つのフレームのみが多重化される場合は、次のようなコードを記述するだけでフレームの多重化を実現できます。
private voiddrawOneFrame(Canvas Canvas) { frameBitmap = BitmapFactory.decodeResource(getResources(), bitmaps.get(bitmapIndex), options); //最後のフレームのメモリを再利用する Bitmap options.inBitmap = FrameBitmap; Canvas.drawBitmap(frameBitmap, srcRect、dstRect、ペイント); bitmapIndex++; }
最後に著者がFlutterのアドバンスエントリーの上級情報を集めてPDFにまとめました
以下は、データ ディレクトリとコンテンツのスクリーンショットです。
詳細な説明と知識ポイントの分析が含まれており、1 週間で Flutter を使い始めることができます。高度な学習プロジェクト用の 130 の実践的なビデオ チュートリアルもあり、数秒でレベルアップできます。私を殴ることを学ぶことはできません!
(frameBitmap、srcRect、dstRect、ペイント);
bitmapIndex++;
}
最後に著者がFlutterのアドバンスエントリーの上級情報を集めてPDFにまとめました
以下は、データ ディレクトリとコンテンツのスクリーンショットです。
[外部リンク画像転送...(img-SQVsRd8e-1644995337753)]
[外部リンク画像転送...(img-YB268ikc-1644995337754)]
知識ポイントの詳細な説明と分析が含まれており、取得するには1週間かかりますフラッターから始まりました。高度な学習プロジェクト用の 130 の実践的なビデオ チュートリアルもあり、数秒でレベルアップできます。私を殴ることを学ぶことはできません!
[外部リンクの写真は転送されています...(img-fVuNRmEc-1644995337755)]
上記の情報は無料で共有されています。入手方法:私の GitHub をクリックして無料で入手してください