
android.os.AsyncTaskの正しい使い方
2021.12.20
目次
AsyncTaskについて思うこと
Androidアプリ開発が、一般に広く普及してから「約10年」ほどが経ちました。
数々のリファレンスが生まれては、内容が古いまま残されている記事が散見されるようになってきました。
そんな中で、このタイトルにある "AsyncTask"という非同期処理用クラス をとてもよく見かけます。
APIレベル3以降から使われている非同期処理を担当するクラスの代表で、2019年6月段階では特別にgradleファイルを編集して別途ライブラリを追加する必要がないため、「下手にアプリサイズを増やさず、簡単に実装しやすい」という印象を持ちます。
今でこそRxJavaやRxKotlin、Android Architecture Componentsに提唱されるLiveData、最近正式採用化されたcoroutinesに取って代わることも多いようです。
しかし、古くから存在するようなActivityやFragmentには、未だに存在し、かつ、昔のノウハウのまま実装されてしまっているものを時折現場で見かけます。
ここでは、筆者が個人的に開発・リリース・運用を続けているTwitterクライアントを例に、AsyncTaskについて再考していきます。
AsyncTaskの「良い点」と「悪い点」
AsyncTaskの良い点
- 導入が楽、かつ学習コストが低い(古くからリファレンスが存在するため)
- Rxやcoroutinesなどのようにライブラリを追加することなく、AndroidSDKが標準で提供してくれている
- 処理時間がそんなにかからない非同期処理などに使いやすい
- 使い方さえ間違わなければ手軽
AsyncTaskの良くないところ
- "This AsyncTask class should be static or leaks might occur” 警告を起こしがち
- ActivityやFragmentなどの画面から呼ばれる事例が多い
- ActivityやFragmentが死んで(onDestroy)もAsyncTaskが生きたままになり、GCされずメモリリークが起きる
- しかもAsyncTask内でTextViewなどのViewのUIを操作するというアンチパターンなリファレンスが多い
- 画面回転時などIllegal State Exceptionが起きる(Activityの再生成が行われてAsyncTaskが行き場を失い起きる)
警告について
AsyncTaskを実装したコードをAndroid Studioで開いた場合、下記のような警告が出ていることがあります。
1 | This AsyncTask class should be static or leaks might occur... |
どのようなコードで上記の警告が出るかというと…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class TimeLineFragment() : ListFragment() { private var isSwipeRefresh = false private var mSwipeRefreshLayout: SwipeRefreshLayout? = null //(中略) fun getHomeTimeLineTweet() { val paging = Paging() paging.count = GET_MENTION_TWEET_NUM val task = object : AsyncTask<Void, Void, List<Status>>() { override fun onPreExecute() { } override fun doInBackground(vararg params: Void): List<twitter4j.Status>? { try { return twitter!!.getMentionsTimeline(paging) } catch (e: TwitterException) { e.printStackTrace() } return null } override fun onPostExecute(result: List<twitter4j.Status>?) { if (result != null) { mAdapter!!.clear() for (status in result) { mAdapter!!.add(status) } } else { showToast(getString(R.string.failed_to_load_timeline)) } //くるくる消す mSwipeRefreshLayout!!.clearAnimation() // ここでUIの操作が行われている mSwipeRefreshLayout!!.isRefreshing = false } } task.execute() } |
このように、AsyncTaskを匿名クラスとして利用し、なおかつ内部でUIに対する変更を加えている場合に起こります。
この場合、Fragment(あるいはActivity)が破棄されしまっても、AsyncTaskが生き残ってしまった場合に、UI部品(ここではSwipeRefreshLayout)への参照が残ったままとなり、メモリリークを起こす可能性があります。
非同期タスクは、あくまでも裏側で実行されていて、処理の完了には時間がかかることが想定されています。
処理が完了する前にUIコンポーネントが不要になり破棄する必要がある状況になると、本来はUIオブジェクトを破棄するべきところなのに非同期タスクが参照を握っているために破棄されない…といった事が起こります。
つまり、UIリソースが解放できなくなります。
こういったコードはどのように修正すればいいのでしょうか?
AsyncTaskのポイントまとめ
ポイントを一旦下記にまとめます
- AsyncTaskをnon-staticな内部的なクラスにせず、inner classにするか、別クラスにする
- WeakReferenceを利用する ←ここが重要!
- RefreshCallBackインターフェイスを準備する
- APIの取得・更新処理などが終わった時にUI側に結果を返す時に用いる
コードの修正
まず、AsyncTaskをディレクトリごと切ります。
パッケージ右クリック → New → Package
今回は、そのパッケージ名を「asynctask」とします。
そのディレクトリで、New → Kotlin File/Classを選択(Javaで開発されている方は以下より.javaに置き換えてください)
たとえば、ホーム・タイムラインを取得するためのクラスであれば「GetHomeTimeLineAsyncTask」と命名して保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | GetHomeTimeLineAsyncTask.kt class GetHomeTimeLineAsyncTask(var twitter: Twitter, var paging: Paging, refreshCallback: RefreshCallback) : AsyncTask<Void, List<Status>?, List<Status>?>() { private val refreshCallbackReference: WeakReference<RefreshCallback> = WeakReference(refreshCallback) override fun doInBackground(vararg params: Void?): List<twitter4j.Status>? { try { val timeline = twitter.getHomeTimeline(paging) // publishProgressを呼ばないとonProgressUpdateは呼ばれないみたい publishProgress(timeline) return timeline } catch (e: TwitterException) { e.printStackTrace() } return listOf() } } |
AsyncTaskの継承は、doInBackgroundメソッドさえ継承していればOKです。
ポイントとなるのは
1 | private val refreshCallbackReference: WeakReference<RefreshCallback> = WeakReference(refreshCallback) |
RefreshCallBack、これはインターフェイス(interface)として作ります。
このJavaが、最初から持つWeakReferenceが重要です。
1 2 3 4 5 6 | RefreshCallBack.kt interface RefreshCallback { fun addListItem(statusList : List<twitter4j.Status>?) fun refreshCompleted() fun progressUpdate(progress: List<twitter4j.Status>?) } |
twitter4jを利用しているので、twitter4jをデータモデルと捉えてください。
このRefreshCallBackを利用してFragmentへ参照を返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | TimeLineFragment.kt class TimeLineFragment() : Fragment(), SwipeRefreshLayout.OnRefreshListener, RefreshCallback { // RefreshCallBackを実装 // 略 /** * @return List<Status>? * タイムラインを取得し、callbackに返す */ fun reloadTimeLine() { val paging = Paging() paging.count = 200 GetHomeTimeLineAsyncTask(twitter, paging, this).execute() } } |
あとは、呼び出し元となるTimeLineFragmentにてRefreshCallbackインターフェイスの下記3メソッドを経由して、UIの更新処理などを行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /** * RefreshCallBack#addListItem * GetHomeTimeLineAsyncTaskのonPostExecuteがなされた時に呼ばれる */ override fun addListItem(statusList: List<Status>?) { timeLine = statusList timeLine?.let { if (it.isEmpty()) { Snackbar.make(view!!, getString(R.string.failed_to_load_timeline), Snackbar.LENGTH_SHORT).show() } } } /** * RefreshCallBack#refreshCompleted * 非同期タスクが終了したらする動き * GetHomeTimeLineAsyncTaskのonPostExecuteがなされた時に呼ばれる */ override fun refreshCompleted() { recyclerTweetViewAdapter = TweetAdapter(activity!!.applicationContext, timeLine) recyclerView.apply { adapter = recyclerTweetViewAdapter adapter?.notifyDataSetChanged() mProgressBar.visibility = ProgressBar.INVISIBLE } mSwipeRefreshLayout.isRefreshing = false } /** * RefreshCallBack#progressUpdate * 非同期タスクが進行中の時 * GetHomeTimeLineAsyncTask#onProgressUpdate(vararg values: Int?) */ override fun progressUpdate(progress: List<Status>?) { progress?.forEach { mProgressBar.progress++ } } /** * swipeRefresh **/ override fun onRefresh() { mProgressBar.visibility = ProgressBar.VISIBLE reloadTimeLine() } |
※onRefreshメソッドに関してはSwipeRefreshを使う上で実装しています。
WeakReferenceを経由して参照を渡すことで、非同期タスクがUIリソースの解放を遮らないようにします。
最終的なAsyncTask
最終的にAsyncTaskは、以下のようになります
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class GetHomeTimeLineAsyncTask(var twitter: Twitter, var paging: Paging, refreshCallback: RefreshCallback) : AsyncTask<Void, List<Status>?, List<Status>?>() { private val refreshCallbackReference: WeakReference<RefreshCallback> = WeakReference(refreshCallback) override fun doInBackground(vararg params: Void?): List<twitter4j.Status>? { try { val timeline = twitter.getHomeTimeline(paging) // publishProgressを呼ばないとonProgressUpdateは呼ばれない publishProgress(timeline) return timeline } catch (e: TwitterException) { e.printStackTrace() } return listOf() } override fun onPostExecute(result: List<twitter4j.Status>?) { super.onPostExecute(result) val callBack = this.refreshCallbackReference.get() callBack?.let { it.addListItem(result) it.refreshCompleted() } } override fun onProgressUpdate(vararg values: List<twitter4j.Status>?) { val callback = this.refreshCallbackReference.get() callback?.let { values[0]?.let { it1 -> it.progressUpdate(it1) } } } } |
AsyncTaskでよく使うメソッド
個人的にAsyncTaskでよく使うメソッドは上記で書いた
- doInBackground(必須の継承メソッド)
- onPostExecute(オプショナルだがよく使うメソッド)
- onProgressUpdate(オプショナルだがよく使うメソッド2)
となります。
onProgressUpdateに関してはアプリのユーザーにデータの読み込み情報を表示するProgressBarなどの表示のためによく使います。
また、onProgressUpdateメソッドは、doInBackgroundメソッドでpublishProgressメソッドを呼ぶ必要がある点も注意しましょう。
AsyncTaskの使い方 まとめ
- AsyncTask を使うなら WeakReference と CallBackインターフェイスを介することでメモリリークを防ぐ
- AsyncTask#onProgressUpdate が動作するには AsyncTask#doInBackground で pubishProgress() を呼ぶ必要がある
- 呼び出し元の Fragment では RefreshCallBack を介して、UIの処理を行えば良い
これらの対応により、メモリリークの危険性は低減され、見通しの良いコードが出来上がるはずです。
1 | This AsyncTask class should be static or leaks might occur... |
筆者自身、よく見かけるAndroid Studioからの警告もこのようにして撲滅できました。
古くから動くAndroidアプリの保守開発を行っている皆様、もしAsyncTaskが存在してAndroid Studioから警告されている場合は上記の対応で修正を試みてはいかがでしょうか?
こちらの記事もオススメ!
書いた人はこんな人

- 「好きなことを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もりは大歓迎!
また、WEBエンジニアとモバイルエンジニアも積極採用中です!
ご応募をお待ちしております!
ITエンタメ2022.06.22IntelliJ IDEAとkotlinを送り出したJetBrains創業物語
ITエンタメ2022.06.15【アタリ創業者】スティーブ・ジョブズを雇った男「ノーラン・ブッシュネル」
ITエンタメ2022.06.13プログラミングに飽きてPHPを開発したラスマス・ラードフ
ITエンタメ2022.06.03【Unity開発秘話】ゲーマーを開発者にしてしまうゲームエンジン