0

サーバーからosmタイルをダウンロードして、タイルごとに新しいAsyncTaskを生成する単純なマップアプリケーションを作成しています。一部のAsyncTaskは完了するまで実行され、一部は実行の機会を得る前にキ​​ャンセルされます(画面をパンした場合)。それは、彼らが漏れているように見えるときかもしれません。私は最初にタイルビットマップの寿命を調べてこれを発見し、それらがリークするのを見ました。AsyncTaskのビットマップへの(間接的な)参照を無効にした後、ビットマップのリークは止まりましたが、なぜその参照が重要なのかを考え、AsycTasksへの参照を閉じるためのあらゆる努力にもかかわらずAsycTasksの数が継続的に増加していることを発見しました。MAT分析を実行しましたが、これは助けが必要なときです。誰かが私が画像を解釈するのを手伝ってくれますか(申し訳ありませんが、画像を直接添付するのに十分なスタックオーバーフローの茶色のポイントがありません)。

示されているツリーエントリは、リークされた多くのAsyncTasks(FileCacheTaskタイプ)の1つを示し、子はそれへのすべての着信参照を示します。「指示対象」がガベージコレクターであることを理解しています。タイプandroid.os.AsyncTaskのthis$0(外部クラスとの親和性を示唆)として示される他の2つ(これはFileCacheTaskの外部クラスとはまったく関係ありません。確かに、FileCacheTaskはタイルファクトリ内の非静的内部クラスです) 、mWorkerメンバーを介してリークされたオブジェクトへの参照を保持していることは、私が説明できず、明らかに取り除くことができないものです。パスをたどってandroid.os.AsyncTaskオブジェクトへの着信参照を探してみましたが、スケジューリングの内部がいくつか見られ、コードに関連するものは何もありませんでした)。this $ 0と呼ばれるandroid.os.AsyncTaskオブジェクトは何でしょうか?

編集します。アドバイスに従って、私は(一種の)問題を消化可能なコードスニペットに蒸留しました。一見同じ振る舞いをしているように見えるので、そう言ったのですが、本当に同じ問題を抱えているかどうかは定かではありません。それはまだ不可解に動作します。コードは次のとおりです。

public class TaskLeak {
static public int DDeletedTasks; //these are all for debug/tracing purposes
static public int DCreatedTasks;
static public int DCancelled;

private class PrivateTask extends AsyncTask<Void, Void, Void>
{
    private int mTaskId;
    private boolean mFinished;
    private Bitmap mBitmap;
    public PrivateTask()
    {
        mTaskId = ++DCreatedTasks;
                //allocating a bitmap to give this object a meaningful weight for GC to consider.
        mBitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888);
        //Log.d("Tasks", "(constructor) Task count:"+ mTaskId+" tasks in existence:"+(DCreatedTasks-DDeletedTasks));
        execute();
    }

    @Override
    protected Void doInBackground(Void... params) {
        try {
            int sleepForMs = mRandomGenerator.nextInt(100);
            //emulating blocking download action for a random few milliseconds.
            Thread.sleep(sleepForMs);
            //Log.d("Tasks", "Waking up from sleep, task:" + mTaskId);
        } catch (InterruptedException e) {
            //Log.d("Tasks", "Can't sleep, task:" + mTaskId);
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void result) 
    {
        mFinished = true;
        maintain();
    }

    @Override
    protected void onCancelled()
    {
        DCancelled++;
        onPostExecute(null);
    }

    @Override
    protected void finalize()
    {
        ++DDeletedTasks;
        Log.d("Tasks", "(destructor) Task count:"+ mTaskId+" tasks in existence:"+(DCreatedTasks-DDeletedTasks));
    }       

    public boolean finished()
    {
        return mFinished;
    }       
}

private final static int KMaxTasks = 100;
private Random mRandomGenerator;
private final int mMaxCycles;
private int mCycles;
private final Vector<PrivateTask> mTasks;
public TaskLeak(int maxCycles)
{
    mMaxCycles = maxCycles;
    mRandomGenerator = new Random();
    mTasks = new Vector<PrivateTask>();
    for (int i = 0; i < KMaxTasks; i++)
    {
        mTasks.add(new PrivateTask());
    }
}

public void maintain()
{
    Log.d("Tasks", "***maintain(), tasks held:"+mTasks.size()+" there are "+ (mMaxCycles - ++mCycles)+" cycles left to go");
    for (int i = 0; i < mTasks.size();)
    {
        if (mTasks.get(i).finished())
        {
            mTasks.remove(i);
            if (mCycles < mMaxCycles)
            {
                mTasks.add(new PrivateTask());
            }
        }
        else
        {
            i++;
        }
    }

    if ((mCycles % 10) == 0 )
    {
        DCancelled++;
        mTasks.get(5).cancel(true);
    }

    if (mTasks.size() == 0)
    {
        new TestMemoryGrabber(200).test();
        Log.d("Tasks", "maintain() has finished!");
        Log.d("Tasks", "maintain(). There have been "+DCreatedTasks+" tasks created.");
        Log.d("Tasks", "maintain(). There have been "+DDeletedTasks+" tasks deleted.");
        Log.d("Tasks", "maintain(). There have been "+(DCreatedTasks-DDeletedTasks)+" tasks remaining.");   
        Log.d("Tasks", "maintain(). There have been "+DCancelled+" tasks cancelled.");
    }
}

これを1000サイクル実行すると、メモリ不足の例外で途中で失敗します(サイクル#570)。ただし、PrivateTaskオブジェクトへの参照は常に100個以下です。GCは継続的に新しいエントリの余地を作る必要があるため、これ自体はやや不可解です。なんでそうじゃないの?

oom例外がいつ発生するかについてのLogCatは次のとおりです。

01-12 16:37:50.902: D/Tasks(3235): (destructor) Task count:568 tasks in existence:156
01-12 16:37:50.993: D/Tasks(3235): ***maintain(), tasks held:100 there are 430 cycles left to go
01-12 16:37:51.062: I/dalvikvm-heap(3235): Clamp target GC heap from 49.251MB to 48.000MB
01-12 16:37:51.062: D/dalvikvm(3235): GC_FOR_ALLOC freed 257K, 2% free 48337K/48903K, paused 70ms, total 71ms
01-12 16:37:51.062: D/Tasks(3235): (destructor) Task count:569 tasks in existence:156
01-12 16:37:51.092: D/Tasks(3235): ***maintain(), tasks held:100 there are 429 cycles left to go
01-12 16:37:51.222: I/dalvikvm-heap(3235): Clamp target GC heap from 49.251MB to 48.000MB
01-12 16:37:51.232: D/dalvikvm(3235): GC_FOR_ALLOC freed 257K, 2% free 48337K/48903K, paused 139ms, total 139ms
01-12 16:37:51.242: D/Tasks(3235): ***maintain(), tasks held:100 there are 428 cycles left to go
01-12 16:37:51.312: I/dalvikvm-heap(3235): Clamp target GC heap from 49.502MB to 48.000MB
01-12 16:37:51.322: D/dalvikvm(3235): GC_FOR_ALLOC freed <1K, 1% free 48593K/48903K, paused 70ms, total 72ms
01-12 16:37:51.322: I/dalvikvm-heap(3235): Forcing collection of SoftReferences for 262160-byte allocation
01-12 16:37:51.412: I/dalvikvm-heap(3235): Clamp target GC heap from 49.494MB to 48.000MB
01-12 16:37:51.412: D/dalvikvm(3235): GC_BEFORE_OOM freed 9K, 1% free 48584K/48903K, paused 86ms, total 86ms
01-12 16:37:51.412: E/dalvikvm-heap(3235): Out of memory on a 262160-byte allocation.
01-12 16:37:51.412: I/dalvikvm(3235): "main" prio=5 tid=1 RUNNABLE
01-12 16:37:51.412: I/dalvikvm(3235):   | group="main" sCount=0 dsCount=0 obj=0x40a14568 self=0x2a00b9e0
01-12 16:37:51.412: I/dalvikvm(3235):   | sysTid=3235 nice=0 sched=0/0 cgrp=apps handle=1073870640
01-12 16:37:51.412: I/dalvikvm(3235):   | schedstat=( 21236163363 7517071458 4002 ) utm=1940 stm=183 core=0
01-12 16:37:51.412: I/dalvikvm(3235):   at android.graphics.Bitmap.nativeCreate(Native Method)
01-12 16:37:51.412: I/dalvikvm(3235):   at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
01-12 16:37:51.412: I/dalvikvm(3235):   at android.graphics.Bitmap.createBitmap(Bitmap.java:620)
4

1 に答える 1

0

コード、ログ、および説明を確認した後、この OutOfMemoryError は、メモリに格納されているビットマップが多すぎるために発生したと推測します。

各 PrivateTask を作成するときは、実行を開始する前に、ビットマップを初期化してメモリに保持します。100 を超えるタスクが存在するため、メモリの上限に達しています。次のような Logcat 行は、これを示しています。

01-12 16:37:51.412: D/dalvikvm(3235): GC_BEFORE_OOM freed 9K, 1% free 48584K/48903K, paused 86ms, total 86ms

問題の軽減に関して、私が推奨できる一番の解決策は、各タスクがキャンセルされたら、明示的に呼び出すことですrecycle()Bitmap オブジェクトで。PrivateTasks がキャンセルされていても、それらへの参照が維持されていると思われます。これにより、システムがそうではないため、GC が発生したときにメモリのごく少量 (1% 程度) しか解放されません。それらを破壊するか、あなたのビットマップを破壊します。あるいは、GC がこれを正確に処理している可能性もありますが、アクティブな PrivateTasks に十分なビットマップがあり、メモリ制限に達する可能性があります。これは、システムが収集するものを見つけることができないため、ガベージ コレクションが行われないことにもつながります! サーバーから非常に多くのビットマップを定期的にダウンロードする必要がある場合は、それらをディスクに保存し、必要に応じてのみメモリに取得することを検討することをお勧めしますが、これによりパフォーマンスが大幅に低下する可能性があります。

ビットマップ管理に関するいくつかのリンクとその他の SO の質問を次に示します。

Android Dev Site からビットマップを効率的に表示する(特にビットマップのキャッシュに関するセクションを参照)

同様の問題を抱えていた人のための解決策

品質によっては、ダウンロードが完了したら、ビットマップを圧縮してみてください。圧縮後にオリジナルへの参照をリサイクルすることを忘れないでください!

ビットマップにメモリを割り当てるときに内部で何が起こっているかについての詳細情報。

補足として、これをテストしているデバイス/エミュレーターのタイプ、特にSDKバージョンについて知りたいです。Android アプリに提供されるメモリの量は、新しいハードウェアで増加しているため、別の電話で異なるテスト結果が表示される場合があります。さらに、多くのデバイス (Gingerbread が市場シェアを失い、ICS/Jellybean が増加するにつれて、その数が増える) がこれらの操作を連続して実行する可能性があると考えています。これは、この特定の問題の根源であり、アプリの将来の互換性にとってより懸念事項となるでしょう。

お役に立てれば!

于 2013-01-16T20:13:13.080 に答える