ikautak.log

C/C++, Python, CUDA, Android, Linux kernel, Network, etc.

RemoteViewsの仕組み

AppWidgetProviderで使っているRemoteViewsクラスの仕組みについて調べてみた。

Widgetは、表示領域を提供するAppWidgetHost(大抵はホームアプリ)に、部品を提供するAppWidgetProviderから表示情報を送る形で構成されている。

ホームアプリとAppWidgetProviderは別プロセスなので、AppWidgetManager~AppWidgetSeriveを通してBinderでIPCで情報を送っている。   AppWidgetHostではMessageを使って別スレッドで動作しているが、細かいところを省略すると以下のシーケンスになる。

f:id:trisection:20130414174236p:plain

要するにAppWidgetManagerに渡したRemoteViewsがHost側まで渡され、そこでapply()が呼ばれる。

public View apply(Context context, ViewGroup parent) {
    View result;

    Context c = prepareContext(context);

    LayoutInflater inflater = (LayoutInflater)
            c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    inflater = inflater.cloneInContext(c);
    inflater.setFilter(this);

    result = inflater.inflate(mLayoutId, parent, false);

    performApply(result);

    return result;
}

applyの中で、RemoteViewsを作るときに渡したリソースIDからxmlのレイアウト処理が実行され、performApply()へと続く。

private void performApply(View v) {
    if (mActions != null) {
        final int count = mActions.size();
        for (int i = 0; i < count; i++) {
            Action a = mActions.get(i);
            a.apply(v);
        }
    }
}

performApply()ではArrayListのmActionsの分だけapply()を呼んでいる。
mActionsには、RemoteViewsのset系メソッドを呼んだとき、Actionクラスがaddされているようだ。

public void setTextColor(int viewId, int color) {
    setInt(viewId, "setTextColor", color);
}

public void setInt(int viewId, String methodName, int value) {
    addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
}

つまり、RemoteView.setTextColor()などを実行すると、メソッド名と引数からActionクラスのオブジェクトが作られ、mActionsに追加される。

そしてRemoteViewsオブジェクトがAppWidgetHost側(ホームアプリ側)までシリアライズされて送信され、Host側でActionのリスト(Provider側が呼びたかったメソッド)が全て実行されることになる。

オブジェクトの生成をケチって同じRemoteViewsオブジェクトを使い回していると、mActionsのリストが肥大化する。
さらに、setImageViewBitmap()を使うとBitmapまでシリアライズされて送信されるため、いずれIPCのデータサイズ制限?か何かで
!!! FAILED BINDER TRANSACTION !!!
が発生してしまう。(以前のエントリ