Android パフォーマンスの最適化_大きな画像用のフレーム アニメーション カード? SurfaceView スライディング ウィンドウ フレーム多重化のフレーム アニメーションの最適化

(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/rawres/drawable、 、に配置したassetsところ、RapidDecoderGitHub でこのライブラリを見つけました (興奮!)。関数の実行時間を測定するためのカスタマイズされたツール クラス:

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サイズFrameSurfaceView1 のブロッキング キューとアクセス操作が次の場所に追加されていることです。

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 をクリックして無料で入手してください

おすすめ

転載: blog.csdn.net/m0_66264630/article/details/122964073