Android開發藝術探索 - 第5章 理解RemoteViews

1.RemoteViews應用

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notication);
remoteViews.setTextViewText(R.id.text, "text");
remoteViews.setImageViewResource(R.id.image, R.drawable.icon);
remoteViews.setOnClickPendingIntent(R.id.button, 
        PendingIntent.getActivities(this, 0, 
                new Intent(this, TargetActivity.class), 
                PendingIntent.FLAG_UPDATE_CURRENT));
  1. 自定義通知佈局
  2. 桌面小部件
  3. PendingIntent
getActivity(Context context, int requestCode, Intent intent, @Flags int flags)
getBroadcast(Context context, int requestCode, Intent intent, @Flags int flags)
getService(Context context, int requestCode, @NonNull Intent intent, @Flags int flags)
* PendingIntent用於描述Intent和target action,提供給其他application以執行相應的動作。
* 所以爲了安全,要使用顯示Intent,直接指定目標組件。
* PendingIntent標識了一組將要操作的數據,如果創建它的process被kill了,不影響其他process繼續使用他;之後如果重新創建了一個相同類型的PendingIntent,之前同類型的如果可用將繼續被使用。
* 相同的Intent將得到相同的PendingIntent,而Intent中extra的部分不會影響判斷Intent是否相同。所以如果想得到不同PendingIntent,要保證Intent的不同(單獨區分extra無效),或者在PendingIntent的get方法中指定不同的requestCode;如果同一時刻只想使用同一個PendingIntent,通過設置FLAG_CANCEL_CURRENT或FLAG_UPDATE_CURRENT來取消或更新指定的Intent。
* flags:
    * FLAG_ONE_SHOT
        該PendingIntent只能被使用一次。Intent被send之後會直接被cancel,之後send將失敗。
    * FLAG_CANCEL_CURRENT
        如果當前描述的PendingIntent已經存在,則cancel,然後創建一個新的PendingIntent。
    * FLAG_UPDATE_CURRENT
        如果當前描述的PendingIntent已經存在,則替換其中Intent的extra。

2.RemoteViews內部機制

Remoteviews僅支持部分layout和view,不支持其子類和其他自定義View。
RemoteViews會通過Binder傳遞到SystemServer進程,在SystemServer中加載佈局,更新View。具體流程:

  • 本地進程創建RemoteViews實例,然後調用了一系列的set方法設置了View。爲了減少IPC開銷,RemoteViews內部聲明一個Action抽象類,並實現了Parcelable接口,將set調用的操作封裝爲一個action。而set方法實際上是創建了一個action,然後添加到自身維護的一個action list裏面:
    public void setTextViewText(int viewId, CharSequence text) {
        setCharSequence(viewId, "setText", text);
    }
    public void setCharSequence(int viewId, String methodName, CharSequence value) {
        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
    }
    private void addAction(Action a) {
        if (hasLandscapeAndPortraitLayouts()) {
            throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
                    " layouts cannot be modified. Instead, fully configure the landscape and" +
                    " portrait layouts individually before constructing the combined layout.");
        }
        if (mActions == null) {
            mActions = new ArrayList<>();
        }
        mActions.add(a);
    }
  • 在遠程進程中,會調用RemoteViews的apply方法初始化View,調用performApply遍歷action list,執行一系列的action操作來更新View:
/** @hide */
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
    RemoteViews rvToApply = getRemoteViewsToApply(context);

    View result = inflateView(context, rvToApply, parent);
    loadTransitionOverride(context, handler);

    rvToApply.performApply(result, parent, handler);

    return result;
}
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
    if (mActions != null) {
        handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
        final int count = mActions.size();
        for (int i = 0; i < count; i++) {
            Action a = mActions.get(i);
            a.apply(v, parent, handler);
        }
    }
}
  • Action有很多實現,主要差異就在於其apply方法,如ReflectionAction的apply通過反射執行View的set方法,TextViewSizeAction的apply則直接對View調用setTextSize。ReflectionAction的抽象程度更高,支持的操作更多,但是反射會有一定的開銷。
  • setOnClickPendingIntent僅支持給普通View設置click事件;組合setPendingIntentTemplate和setOnClickFillInIntent可以給ListView等的item設置click事件。
  • apply方法只負責將RemoteViews中維護的View創建出來,創建完成後需要將得到的View實例添加到hierarchy中。
  • 使用RemoteViews的reapply方法可以將一系列action操作執行到指定的View上。如可以在遠程進程中自行創建同一個View,僅通過RemoteViews去執行View的更新操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章