RemoteViewsの仕組み
AppWidgetProviderで使っているRemoteViewsクラスの仕組みについて調べてみた。
Widgetは、表示領域を提供するAppWidgetHost(大抵はホームアプリ)に、部品を提供するAppWidgetProviderから表示情報を送る形で構成されている。
ホームアプリとAppWidgetProviderは別プロセスなので、AppWidgetManager~AppWidgetSeriveを通してBinderでIPCで情報を送っている。 AppWidgetHostではMessageを使って別スレッドで動作しているが、細かいところを省略すると以下のシーケンスになる。
要するに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には、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 !!!
が発生してしまう。(以前のエントリ)