2018年4月8日日曜日

android の Widget は躊躇なく殺される。ならば、何度でも蘇ろう!

ここん所、android の Widget 作成を頑張っているんだけど、満足のいく安定性が得られない。
各OSバージョンの android エミューレータや android 6.0.1 のSH-M03 では問題なく動くんだけど、android 4.4.2 の SH-06F だと非常に不安定になる。
さまざまな処理に伴う高負荷に関しては、非同期にしたり、分散させたりで、何とか使えるレベルにきたんだけど、ActivityManager による下記の死の宣告から逃れられない。

I/ActivityManager: Killing 25548:jp.dpp.fmwatcher/u0a311 (adj 11): stop jp.dpp.fmwatcher

とか

I/ActivityManager: Process jp.dpp.fmwatcher (pid 26415) has died.

ですね。
OSに標準の殺し屋がいるので、目を付けられたら、プロセスごと逝ってしまう。こっちの都合でなく、OS側の都合なので、目を付けられないようにビクビクしながら生かすしかない。
具体的な困りごととしては、AppWidgetProvider のインスタンスから、アクティビティを呼び出して、そのアクティビティから親インスタンスに属す remoteview に変更を加えようとしたところ、親が死んでてエラーになるってケース。

...で、どうも限界だなと、発想を変えて「産みの親が死ぬんだから、里親を探すしかない」。里親探しのため、子供からブロードキャストで onUpdate を呼び出すことにした。
というのも、タブレットを回転させたときに Widget の表示が狂うので onAppWidgetOptionsChanged で、remoteview の再描画ルーチンを組み込んだ onUpdate を組み込んだら、偶然、新しい AppWidgetManager の里親を連れてきたことに気づいたから。
子供から直接 onUpdate を呼べないので、ブロードキャストで適当なアクション入れたインテントを送って、親の onReceive で受けて、onUpdate を実行させればOK。
onUpdate に必要な context は onReceive が引数で持っている。
AppWidgetManager は getInstance(context) 。
appWidgetId だけがどうしようないので、onUpdate の中で static な変数に入れるようにすれば(onAppWidgetOptionsChanged でも、上書き代入させる必要あり)、とりあえず調達できる。
骨の部分だけ記載すると、こんな感じ。

==================================================
親側
==================================================
public class XXXXXX extends AppWidgetProvider {

    public static int wid;  // appWidgetId格納変数

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

...
        wid = appWidgetIds[0];
...
    }

    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);

        wid = appWidgetId;
        onUpdate(context, appWidgetManager, new int[]{appWidgetId});

    }


    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);

        switch (intent.getAction()) {
            case "適当なアクション":
                try {
                    onUpdate(context, AppWidgetManager.getInstance(context), new int[]{wid});
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }
    }
}
==================================================


==================================================
子側
==================================================
            Intent intent01 = new Intent(this, XXXXXX.class);
            intent01.setAction(”適当なアクション”);
            sendBroadcast(intent01);
==================================================


これで、親が死んでも、子供が必要な時に新しい里親が現れる。
あとは、親の情報は基本 static にしておいて、どんな親が来ても同じ返事ができるようにしておくこと、子は子で、重複しても構わず、自力で必要な情報を確保できるよう独立した造りにすれば、何とかなるでしょう。

また、remoteview のボタンにこの「適当なアクション」を PendingIntent で発行させるようにすれば、「ウィジェットが表示されてるけど本当は死んでる」状態も自分のボタンで蘇ることができる。
もちろん、updatePeriodMillis を 0 にするなんてもったいない。最小30分ごとに自力で蘇えられるチャンスが訪れるのだから、使わない手はない(何らかのタイマー駆動処理使ってるなら、重複処理しない制御構文入れれば何の影響もないはずだし)。


とりあえず、これで死の恐怖から逃れることができた。
最大限「死なない親」にこだわる必要はあるけど、現実的に「親が死んでもなんとかなる」対策の方が重要ですな。

0 件のコメント:

コメントを投稿