2011/08/07

[widget]AppWidgetManager

過去記事:2010/08/22 17:22
話の流れ上、こちらを先に扱わねばなるまい。
今回作ったAppWidgetではAppWidgetManagerを使っているのだが、まだ何をしているのかよくわかっていない。。
ちゃんと把握しよう!

まず、起動後すぐに呼ばれるのはonUpdate()。
そのまま載せよう。
    @Override

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

        Log.v(TAG, "onUpdate");



        final int APP_NUM = appWidgetIds.length;

        for(int i=0; i<APP_NUM; i++) {

            RemoteViews remoteView = getClickRemoteView(context);

            updateIcon(remoteView);

            appWidgetManager.updateAppWidget(appWidgetIds[i], remoteView);

        }

    }
    private RemoteViews getClickRemoteView(Context context) {

        Log.d(TAG, "getClickRemoteView");

        

        Intent intent = new Intent();

        intent.setAction(ACTION_WIDGET_CONTROL);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        

        // register click event

        RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.main);

        remoteView.setOnClickPendingIntent(R.id.ImageView, pendingIntent);

        return remoteView;

    }
updateIcon()は、アイコンを変更するだけの処理だから省く。
ここでは、各AppWidgetに対してupdateAppWidget()しているように見える。
しかし、印象ではいかんな。
appWidgetManagerは、引数だ。
ドキュメントによると、「updateAppWidget()を呼び出せるオブジェクトだ」とのこと。
オブジェクトなのかインスタンスなのかとか、そんなのはまあいいとしよう(私の中では、オブジェクトが具象化したものがインスタンス、としているので、引数はインスタンス扱いだ)。
第3引数のappWidgetIdsは、「アップデートが必要なappWidgetIds」とのこと。appWidgetIdsの説明に、それはなかろう・・・。
AppWidgetにはそれぞれIDがあるようなので、それは後で調べよう。
とにかく、onUpdate()が呼び出される場合には、対象となるWidgetについていろいろとやってやらなくてはならないということらしい。
そういうことからすると、掲載したonUpdate()でそれぞれのappWidgetIds[]に対してRemoteViewsをnewして割り当ててやるのは、正しいと思われる。
ここ以外では、RemoteViewsを割り当てる機会はなさそうだ。
割り当てる?
AppWidgetManager::updateAppWidget()は何だろうか。
Set the RemoteViews to use for the specified appWidgetId.
「RemoteViewsを指定したappWidgetIdに設定する」でいいのかな。
applyではなく、setか。
どこで呼んでもいいわけではなく、ACTION_APPWIDGET_UPDATEか、"outside of the handler"らしい。handlerの外部? the?
ACTION_APPWIDGET_UPDATEのハンドラ外だったら、領域UとUということになり、U∩Uなので全領域になるのではないか?
このメソッドは、UIDがAppWidget providerを所有(owner)しているところから呼ばれないと動作しないらしい。
「呼ぶことはできるけど、動作するには制限がありますよ」ということなのか。
今までを見ていると、zygoteが起動しているから、ownerはzygoteなのだろう。
とにかく、アプリレベルで呼び出せるものでないことだけは確かだ。
自然?に呼び出されるACTION_APPWIDGET_UPDATEは、ownerから呼び出されているとおもってもよさそうだ。
自分で強制的にACTION_APPWIDGET_UPDATEを使っても動かないよ、ということでよいのかな。

もう1つの呼び出し方がある。
これはonUpdate()ではないブロードキャストを受信しての動作だ。
            RemoteViews remoteView = getClickRemoteView(context);

            remoteView.setImageViewResource(R.id.ImageView, R.drawable.mounted);



            // update AppWidget

            ComponentName thisWidget = new ComponentName(context, EjectSD.class);

            AppWidgetManager.getInstance(context).updateAppWidget(thisWidget, remoteView);
前半はアイコン描画なので、どうでもいい。
ポイントは後半の2行だ。
>AppWidgetManager.getInstance(context)
これは、contextが提供するAppWidgetManagerを取得するものだ。
contextは、onReceive()の引数でもらったものになる。
ドキュメントによると、receiverが動作しているcontextらしい。
取得できるのはAppWidgetManagerのインスタンスなので、これも結局のところはAppWidgetManager::updateAppWidget()を呼び出すことになる。
さっきの「ownerから呼び出されないと」は、この意味なのか。
そうであれば、AppWidgetManagerのルールはこうなる。
  • onUpdate()の場合は、引数を使う
  • onReceive()の場合は、AppWidgetManager.getInstance(context)を使う
たぶん、これ以外では呼び出せないのだろう。

あとは、updateAppWidget()の引数だけか。
onUpdate()の場合は、各AppWidgetに対して(AppWidgetのIDに対して)RemoteViewsを設定する必要がある。
ここでは、ImageViewをタッチしたときに飛ばすPendingIntentを設定している。
不思議なのは、onReceive()の場合だ。
今回のAppWidgetだが、1つタッチすると全部のAppWidgetアイコンがちゃんと変化してくれる。
AppWidgetのIDなど何も気にしていないのに、だ。
これをどう解釈するとよいだろうか?
一番あっさりした解釈としては、複数のAppWidgetがあったとしても、全部同じRemoteViewsを参照している、だろう。
位置情報は別々に持っていて、その中身だけは一緒、と。
そうでなければ、updateAppWidget()が全AppWidgetのRemoteViewsを更新して回っている、ということになってしまう。
さすがにそれはないだろう。
もしそうなら、onUpdate()ではforの中でRemoteViewsをnewせず、外でnewしたものをupdateAppWidget()で渡してやればいいことになる。
うん、試してみたが、それでもちゃんと動いているようだ。

しかし、疑問は残る。
最初は全AppWidgetに対してnewしたRemoteViewsを設定しているのに、何でタッチすると一斉にアイコンが変化するのか?
推測するに、こうではなかろうか。
onUpdate()内で各AppWidgetごとにnew RemoteViewsしていたのは、こんなイメージ。

1回だけnew RemoteViewsした場合は、こんなイメージ。

これならば、納得できる。
psで見たとき、AppWidgetを増やしてもメモリがそんなに変化してないのも、リソースが増えていないからという理屈になる。
根拠がないのは、リソースが1つ、としているところだ。
各AppWidgetごとに異なる描画を行わせることもできるので、リソースは別々になっているものと思うのだ。
COWか?という気もするが、確証がない。

あ、updateAppWidget()の説明を読んでなかった!
なんと、引数にComponentNameをとる場合は、特定のAppWidgetインスタンスではなく全AppWidgetインスタンスが対象になると書いてある。
そうすると、最初の図が間違っていることになる。
onUpdate()時は、こうなっていたのだ。

今回のAppWidgetでは、個別に設定しなくていいので、onUpdate()はこれでよかろう。
    @Override

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

        RemoteViews remoteView = getClickRemoteView(context);

        updateIcon(remoteView);

        ComponentName thisWidget = new ComponentName(context, EjectSD.class);

        appWidgetManager.updateAppWidget(thisWidget, remoteView);

    }
これで、このAppWidget内では参照がこのような関係になる。

うむ、気が晴れた。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。