2019/09/23

Androidアプリ開発:非同期処理のAsyncTaskでのメモリリーク警告の原因と解消方法

Androidで、処理を非同期に実行したい場合は、AsyncTaskを使うのが基本。AsyncTaskは、スレッドプールからスレッドを割り当てて非同期に処理を実行し、UIスレッドで完了処理が実行されるようメッセージなどの処理を実装してくれている。具体的には、
AsyncTaskをベースとするクラスで、doInBackgroundとonPostExecuteメソッドに非同期処理本体、完了時の処理を実装し、インスタンス化してexecuteメソッドを実行すると非同期処理がキックされる。ネットワークを使う処理はUIスレッドでは実行できないので、AsyncTaskなどを使った非同期処理が必須。

ここでAsyncTaskの拡張クラスを定義する際によく使うテクニックが、無名クラス(Anonymous Class、匿名クラスとも呼ばれる)だ。あるクラスのメソッド内で拡張クラスを定義できる。この時、クラスに名前を付ける必要がないので、無名クラスと呼ばれる。非同期処理をキックする処理の近くに処理の内容をかけるし、無名クラスをインスタンス化するオブジェクトのメンバにアクセスできるというメリットもある。これは便利。

無名クラスを定義しているクラスのオブジェクトのメンバにアクセスできるのは、明示的には書かないが、無名クラスのオブジェクトが、その外のクラスのオブジェクトへの参照を隠し(implicit)持っているから。これは実は注意が必要で、コンパイル時にも警告が出る。"This AsyncTask class should be static or leaks might occur."というあれだ。私のようなJava初心者には結構難しい警告である。

非同期実行しているライブな無名クラスのオブジェクトが、そのオブジェクトを作った元オブジェクト参照していることになるので、元オブジェクトが破棄されてよいオブジェクトであっても破棄されないことになる。メモリリークが起きるかもしれない、というわけ。

そんなことあるのかい? と思われるかもしれないが、Activity から非同期処理をキックしたと考えてみると良い。非同期処理の進行とは無関係にActivityは閉じられ破棄される。非同期処理が長く走る処理だと、AsyncTaskのオブジェクトがActivityを参照しているため、Activityがいつまで経っても破棄されないことになる。非同期処理が短時間の処理ならあまり神経質になる必要はないのかもしれない。

この関係を解消するやり方は、非同期処理用のクラスを外に作ること。非同期処理で元オブジェクトを参照しないなら、これで解決できる。しかし、多くの場合は、元オブジェクトを参照したくなるはず。だって、非同期処理は、キックされたところにある処理に関係が深いだろうから。

どうしても元オブジェクトへの参照が必要な場合は、WeakReferenceという仕掛けを使うと良いようだ、これも新たにクラスを作らなければならないのだが、元オブジェクトへの参照をWeakReferenceでラップして保持するようにする。実際にそれを参照する処理を実行するときには、ライブな参照を取り出せたときだけ実行する形になる。

ということで、コンパイラの警告は消せるのだが、よくよく考えてほしい。特に、元オブジェクトがActivity,の場合。イベントドリブンに処理が分解できていて、Obseverパターンがうまく実装できている場合、元Activityへの参照は消せる場合も多いはず。非同期処理でデータのリポジトリが更新され、そのリポジトリを見ているObserverが登録されていて、Observerパターンでビューが更新されるような形がきれいに出来上がっているなら、非同期処理の完了処理でActivityが直接参照されることはない。

どっちを取るかは、、、メンテの必要性かかっているのかなぁ。


2019/09/18

Androidアプリ開発:データとビューの整理の考え方とObserverパターン

データを扱い始めると、データとビューをどうつなげるかを整理しないと、あっという間に混乱が襲ってくる。たとえActivityが一小さなアプリであっても、表示されるデータが変化する場合、想像以上にアプリの構造は複雑になっていく。ネットワーク経由でデータを取得する場合は、なおさらだ。

加えて、処理はイベントドリブンな形で実現しなければならない。何も考えずに書いていると、巨大なActivityができあがる。これば絶対おかしいと思い調べてみると、Fat activityというよくある罠。

Fat Activityは、アプリのクラス構造の設計を適切にすることで解決できる。昔からの定石があり、この先人の知恵は絶対に参考にすべきだ。MVC(Model-Vew-Controller), MVP(Model-View-Presenter), MVVM(Model-View-View Model)などのキーワードで検索すると、ありがたい情報がたくさん見つかるはずだ。

ポイントは、UIのイベント・画面更新、データの維持、これらのつなぐロジックをそれぞれ別のクラスとして実装し、かつ、これらのクラス間の依存関係を極力減らすような実装にすることだ。 依存関係を減らすカギが、Observerというデザインパターンである。

MVCやMVPなどの考え方をクラス設計に取り入れるだけで、Activityの混沌は大きく解消されると思う。Activityが一つのような小さなアプリでも、絶対にこれらの考え方を取り入れないと、メンテできないアプリになると思う。

複数のオブジェクトでアプリを構成するようになると、オブジェクトの状態変更をどう知らせるかが、次の悩みポイントになる。単純にクラス間でメソッドを呼び出す形で通知することもできるが、これをやると、状態変更を知りたいクラスが複数あると破綻する。そんなことあるのか?と思うかもしれないが、リストビュー、グラフビューなど、Viewが複数ある方が普通だ。同時にアクティブにはなっていないかもしれないが、表示の形態を切り替えられるのは普通だ。

ここで登場するのが、Observerと呼ばれるデザインパターンだ。あるオブジェクトが、誰かわからない外部のオブジェクトに状態の変化を伝えたい場合、変化が発生したときに呼び出すメソッドを含むインタフェースを定義する。変化を知りたいオブジェクト(Observer)は、このインタフェースを実装し、自身を変化の発生元のオブジェクトに登録する。このObserverのオブジェクトは、変化の発生元のクラスが使っているインタフェースさえ実装していればよく、どんなクラスでも構わない。これによって、状態変化元とそれを観測する側のクラスの依存関係をなくしている。インタフェースは特別なものでなくてよくて、Runnableインタフェイスなどで十分出会う。

ソース側は、Observerオブジェクトの登録・削除を受け付けるメソッドを用意し、状態変化時に登録されたオブジェクトが実装するインタフェースメソッドを呼び出せば良い。このとき、ソース側はObserverのクラスは知らなくて良い、というところが重大なポイント。これによって、ソース側はObserverとは独立して開発ができることになる。

一方のObserver側は、オブジェクトの登録、通知インタフェースに変更がない限り、ソース側の実装から切り離される。つまり、こちらも独立して開発、メンテができるようになる。

実装が隠蔽され、他のクラスとの依存関係がへり、テストもしやすくなり。効果は、小さなアプリでも絶大。たぶん、当たり前のことなんだけど、一度実際に罠にハマって悩んでここにたどり着くと、その効果がよくわかります。