注: アプリがビットマップ用のネイティブ バッキング メモリを使い果たしているだけの場合、このアプローチは役に立ちません。ビットマップ、特にハニカム以前の問題について説明が難しい場合は、Dalvik ヒープとネイティブ バッキング メモリとの関係を理解することの重要性をいくら強調してもしすぎることはありません。これに関する Dubroy 氏の議論は、私にとって非常に役に立ちました。
そして、上記のあなたの質問に対する私の答えを試みました。. . それを証明することはできませんが、スレッドセーフではないという非常に強い疑いがあります。フェッチ後に画像操作をするときにこれを叩きます。上記の例のように、複数の画像ファイルをリクエストし、それらが到着したときにそれらを処理すると、OutOfMemory
私がキャッチしたエラーは、ヒープと利用可能なネイティブバッキングメモリの両方が正常であることを発見するためだけです(それぞれ> 100kと> 100M)。そして、フェッチが機能することもあります(説明したように)が、機能しないこともあります。一部のデバイスでは、他のデバイスよりも堅牢です。これがなぜなのかという話を考え出すよう求められたとき、OS のネイティブ ライブラリがそれ自体を利用する場合と利用しない場合があるデバイスによっては画像処理ハードウェア (jpg エンコーダーなど) が存在し、他のデバイスでは存在しない可能性があると想像します。次に、これらのハードウェアのボトルネックがスレッドセーフではないことをすぐに非難します。証拠に似たものはほとんどありません。とにかく、私のテスト安定版 (約 12 個) のすべてのデバイスで確実に動作することがわかった唯一のアプローチは、ビットマップ操作部分とシングルスレッドを分離することです。
AsyncTask
上記の例では、実際にネットワークからファイルをフェッチし、それらをストレージのどこかに書き込むために引き続き使用します (生のバイト ストリーム)。がAsyncTask
完了する (つまり、そのデリゲートを呼び出すonPostExecution
) と、以下の Poster クラスのようなことを行うことができます。
私のアクティビティ (複数のダウンロード リクエストを作成する場所) では、最初に UI スレッドでインスタンス化されるクラスにグローバル エグゼキュータを作成します。
public ExecutorService mImagePipelineTask = null; // Thread to use for pipelining images (overlays, etc.)
そしてそれを初期化します:
mImagePipelineTask = Executors.newSingleThreadExecutor();
次に、プールAsyncTask
内のスレッド数を制御するために を使用する必要はありません。Thread
代わりに、私の非同期ビットは次のようになります。
public class PosterImage extends HashMap<String, Object> {
private final String TAG = "DEBUG -- " + ClassUtils.getShortClassName(this.getClass());
private PosterImageDelegate mPosterDelegate = null;
private Drawable mBusyDrawable = null;
private Drawable mErrorDrawable = null;
private ExecutorService mImagePipelineTask = null;
/*
* Globals
*/
Context mContext = null;
/*
* Constructors
*/
public PosterImage() {
}
public PosterImage(PlaygroundActivity aContext) {
mContext = aContext;
mImagePipelineTask = aContext.mImagePipelineTask;
mBusyDrawable = mContext.getResources().getDrawable(R.drawable.loading);
mErrorDrawable = mContext.getResources().getDrawable(R.drawable.load_error);
}
次に、おそらく気にしないいくつかのビット。. . 次に、デリゲートの設定方法など、いくつかの初期化を行います (もちろん、PosterImageDelegate インターフェイスが必要になります)。
public void setPosterDelegate(PosterImageDelegate aPosterDelegate) {
mPosterDelegate = aPosterDelegate;
}
そして、画像操作を行うビットは、副作用としてBitmapFactory
(およびDrawable
) クラスを使用します。これを使用するには、PosterImage オブジェクトをインスタンス化し、自分自身をデリゲートとして設定してから、このフェローを呼び出します。
public Drawable getPreformattedFileAsync() {
if(mFetchFileTask == null) {
Log.e(TAG, " -- Task is Null!!, Need to start an executor");
return(mErrorDrawable);
}
Runnable job = new Runnable() {
public void run() {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Thread.currentThread().yield();
if(mPosterDelegate != null) {
Drawable retDrawable = getPreformattedFile();
if(retDrawable != null) {
mPosterDelegate.onDrawableRequest(retDrawable);
} else {
mPosterDelegate.onDrawableRequest( mErrorDrawable);
}
}
}
};
mImagePipelineTask.execute(job);
return(mBusyDrawable);
}
public Drawable getPreformattedFile() {
Drawable ret = null;
try {
FileInputStream in = new FileInputStream(preformattedFileName());
ret = Drawable.createFromStream(in, null);
// do something interesting with the Drawable
} catch( OutOfMemoryError e ) {
System.gc();
e.printStackTrace();
// Will return null on its own
} catch( Exception e) {
Log.e(TAG, "Trouble reading PNG file ["+e+"]");
}
return(ret);
}
これが戻ると、呼び出し元のオブジェクト (UI スレッド内) には「ビジー」なドローアブルがあります。デリゲートが呼び出されると (ファイルがダウンロードされ、このスレッドによって Drawable に変換された後、指定した Drawable レシーバーにロードする準備が整います。任意の数の画像を並行してダウンロードできます。これにより、バックグラウンド スレッドが一度に 1 つの画像のみを処理します。幸いなことに、画像処理を行うために UI スレッドを拘束しません。
(注意: UI スレッドが実際に Drawableを受信側に置くには、Handler
呼び出し側クラス (デリゲートとして設定するクラス) に が必要です)。完全を期すために、次のようになります。View
Layout
mHandler.post(new Runnable() {
@Override
public void run() {
aItem.getButton().setBackgroundDrawable(aDrawable);
aItem.getButton().postInvalidate();
}
});
おそらく、これはすべて役立つかもしれませんが、そうではないかもしれません。しかし、あなたが提起した素晴らしい質問に対する決定的な答えを聞きたいです.