Post Bank

よわいエンジニアのブログ

Androidで非同期処理【Android、Java】

AsyncTaskを使って非同期処理

適当なメソッドからAsyncTaskを直接呼び出して、非同期処理を実行しています。これだとThis AsyncTask class should be static or leaks might occurというWarningが出てしまいました。

public class MainActivity extends AppCompatActivity {

    /* 非同期処理の呼び出し */
    public void asyncTaskSample() {
        new AsyncTask<Integer, Integer, Integer>() {
            @Override
            protected Integer doInBackground(Integer ...params) {
                System.out.println("Hello World!!");
                return 0;
            }
        }.execute(0);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        asyncTaskSample();
    }
}

このエラーの原因を他のサイトから引用します。

  • 非staticな内部クラスは暗黙に親オブジェクト(Activityオブジェクト)への参照を持っている。
  • AsyncTaskが内部的に利用しているスレッドが生きている限りこのオブジェクトがガベージコレクションの対象にならない。

うーん、よくわからない。

この記事にあるように、「非static内部クラスインスタンスが存在する = 外部クラスインスタンスが存在する」という状態のため、非static内部クラスは外部クラスを参照することができるようです。そして参照を持つオブジェクトはガベージコレクションの対象にならないため、非同期処理が終わってもメモリから解放されない。これがメモリリークの原因になってしまうようです。わかったようなわからないような…。

で、原因がわかったところで上のコードをどう直したらいいかと言うと、Warningの通りにAsyncTaskstaticなクラスにする、もしくは、別クラスにすればいいです。以下はstaticなクラスにした例。

public class MainActivity extends AppCompatActivity {

    /* 非同期処理の呼び出し */
    public void asyncTaskSample() {
        MyTask task = new MyTask();
        task.execute(0);
    }

    private static class MyTask extends AsyncTask<Integer, Integer, Integer> {
        @Override
        protected Integer doInBackground(Integer ...params) {
            System.out.println("Hello World!!");
            return 0;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        asyncTaskSample();
    }
}

ただこれでもまだ問題があって、上のコードだとActivityオブジェクトの非staticな変数にアクセスできないです。上のコードでは実装してないけど、ViewとかをMyTaskからいじりたかったりします。そこらへんをどうやってActivityオブジェクトから非同期処理に渡すのが最適かいまいちわかりません。

この記事では、AsyncTaskImageViewを渡すのに、コンストラクタで渡しているのですが、AsyncTaskImageViewへの参照を持たないように、WeakReferenceというのを使っています。参考までに、コードを引用させてもらいます。

class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {  
    private final WeakReference<ImageView> mImageViewReference;  
    int mWidth;  
    int mHeight;  
    String mFilePath;  
  
    public BitmapWorkerTask(ImageView imageView) {  
        mImageViewReference = new WeakReference<ImageView>(imageView);  
        mWidth = imageView.getWidth();  
        mHeight = imageView.getHeight();  
    }  
  
    // バックグラウンドで画像をデコード  
    @Override  
    protected Bitmap doInBackground(String... params) {  
        mFilePath = params[0];  
        return decodeSampledBitmapFromFile(mFilePath, mWidth, mHeight);  
    }  
  
    // ImageView に Bitmap をセット  
    @Override  
    protected void onPostExecute(Bitmap bitmap) {  
        if (mImageViewReference != null && bitmap != null) {  
            final ImageView imageView = mImageViewReference.get();  
            if (imageView != null) {  
                imageView.setImageBitmap(bitmap);  
            }  
        }  
    }  
}

まとめ

結局、方法はいろいろあってどれが最適か正直わかりません。ただ明らかに間違っているコードもネットには散見されるので、注意が必要だなと感じました。(そう言いつつも自分の書いたコードが間違ってるかも...)AsyncTask奥深すぎる...

参考