fbpx
  1. HOME
  2. ブログ
  3. IT技術
  4. android.os.AsyncTaskの正しい使い方

android.os.AsyncTaskの正しい使い方

AsyncTaskについて思うこと

Androidアプリ開発が、一般に広く普及してから「約10年」ほどが経ちました。

数々のリファレンスが生まれては、内容が古いまま残されている記事が散見されるようになってきました。

そんな中で、このタイトルにある “AsyncTask”という非同期処理用クラス をとてもよく見かけます。

APIレベル3以降から使われている非同期処理を担当するクラスの代表で、2019年6月段階では特別にgradleファイルを編集して別途ライブラリを追加する必要がないため、「下手にアプリサイズを増やさず、簡単に実装しやすい」という印象を持ちます。

今でこそRxJavaRxKotlinAndroid Architecture Componentsに提唱されるLiveData、最近正式採用化されたcoroutinesに取って代わることも多いようです。

しかし、古くから存在するようなActivityFragmentには、未だに存在し、かつ、昔のノウハウのまま実装されてしまっているものを時折現場で見かけます。

ここでは、筆者が個人的に開発・リリース・運用を続けているTwitterクライアントを例に、AsyncTaskについて再考していきます。

AsyncTaskの「良い点」と「悪い点」

AsyncTaskの良い点

  1. 導入が楽、かつ学習コストが低い(古くからリファレンスが存在するため)
  2. Rxやcoroutinesなどのようにライブラリを追加することなく、AndroidSDKが標準で提供してくれている
  3. 処理時間がそんなにかからない非同期処理などに使いやすい
  4. 使い方さえ間違わなければ手軽

AsyncTaskの良くないところ

  1. “This AsyncTask class should be static or leaks might occur” 警告を起こしがち
  2. ActivityやFragmentなどの画面から呼ばれる事例が多い
  3. ActivityやFragmentが死んで(onDestroy)もAsyncTaskが生きたままになり、GCされずメモリリークが起きる
  4. しかもAsyncTask内でTextViewなどのViewのUIを操作するというアンチパターンなリファレンスが多い
  5. 画面回転時などIllegal State Exceptionが起きる(Activityの再生成が行われてAsyncTaskが行き場を失い起きる)

警告について

AsyncTaskを実装したコードをAndroid Studioで開いた場合、下記のような警告が出ていることがあります。

どのようなコードで上記の警告が出るかというと…

このように、AsyncTaskを匿名クラスとして利用し、なおかつ内部でUIに対する変更を加えている場合に起こります。

この場合、Fragment(あるいはActivity)が破棄されしまっても、AsyncTaskが生き残ってしまった場合に、UI部品(ここではSwipeRefreshLayout)への参照が残ったままとなり、メモリリークを起こす可能性があります。

非同期タスクは、あくまでも裏側で実行されていて、処理の完了には時間がかかることが想定されています。

処理が完了する前にUIコンポーネントが不要になり破棄する必要がある状況になると、本来はUIオブジェクトを破棄するべきところなのに非同期タスクが参照を握っているために破棄されない…といった事が起こります。

つまり、UIリソースが解放できなくなります。

こういったコードはどのように修正すればいいのでしょうか?

AsyncTaskのポイントまとめ

ポイントを一旦下記にまとめます

  1. AsyncTaskをnon-staticな内部的なクラスにせず、inner classにするか、別クラスにする
  2. WeakReferenceを利用する ←ここが重要!
  3. RefreshCallBackインターフェイスを準備する
  4. APIの取得・更新処理などが終わった時にUI側に結果を返す時に用いる

コードの修正

まず、AsyncTaskをディレクトリごと切ります。

パッケージ右クリック → New → Package

今回は、そのパッケージ名を「asynctask」とします。

そのディレクトリで、New → Kotlin File/Classを選択(Javaで開発されている方は以下より.javaに置き換えてください)

たとえば、ホーム・タイムラインを取得するためのクラスであれば「GetHomeTimeLineAsyncTask」と命名して保存。

AsyncTaskの継承は、doInBackgroundメソッドさえ継承していればOKです。

ポイントとなるのは

RefreshCallBack、これはインターフェイス(interface)として作ります。

このJavaが、最初から持つWeakReferenceが重要です。

twitter4jを利用しているので、twitter4jをデータモデルと捉えてください。

このRefreshCallBackを利用してFragmentへ参照を返します。

あとは、呼び出し元となるTimeLineFragmentにてRefreshCallbackインターフェイスの下記3メソッドを経由して、UIの更新処理などを行います。

onRefreshメソッドに関してはSwipeRefreshを使う上で実装しています。

WeakReferenceを経由して参照を渡すことで、非同期タスクがUIリソースの解放を遮らないようにします。

最終的なAsyncTask

最終的にAsyncTaskは、以下のようになります

AsyncTaskでよく使うメソッド

個人的にAsyncTaskでよく使うメソッドは上記で書いた

  1. doInBackground(必須の継承メソッド)
  2. onPostExecute(オプショナルだがよく使うメソッド)
  3. onProgressUpdate(オプショナルだがよく使うメソッド2)

となります。

onProgressUpdateに関してはアプリのユーザーにデータの読み込み情報を表示するProgressBarなどの表示のためによく使います。

また、onProgressUpdateメソッドは、doInBackgroundメソッドでpublishProgressメソッドを呼ぶ必要がある点も注意しましょう。

AsyncTaskの使い方 まとめ

  1. AsyncTask を使うなら WeakReference と CallBackインターフェイスを介することでメモリリークを防ぐ
  2. AsyncTask#onProgressUpdate が動作するには AsyncTask#doInBackground で pubishProgress() を呼ぶ必要がある
  3. 呼び出し元の Fragment では RefreshCallBack を介して、UIの処理を行えば良い

これらの対応により、メモリリークの危険性は低減され、見通しの良いコードが出来上がるはずです。

筆者自身、よく見かけるAndroid Studioからの警告もこのようにして撲滅できました。

古くから動くAndroidアプリの保守開発を行っている皆様、もしAsyncTaskが存在してAndroid Studioから警告されている場合は上記の対応で修正を試みてはいかがでしょうか?

一緒に働いてくれる仲間を募集しております!

ライトコードでは、仲間を募集しております!

当社のモットーは「好きなことを仕事にするエンジニア集団」「エンジニアによるエンジニアのための会社」。エンジニアであるあなたの「やってみたいこと」を全力で応援する会社です。

また、ライトコードは現在、急成長中!だからこそ、あなたにお任せしたいやりがいのあるお仕事は沢山あります。「コアメンバー」として活躍してくれる、あなたからのご応募をお待ちしております!

なお、ご応募の前に、「話しだけ聞いてみたい」「社内の雰囲気を知りたい」という方はこちらをご覧ください。

ライトコードでは一緒に働いていただける方を募集しております!

採用情報はこちら

関連記事