RemoteViews的內(nèi)部機制

5.2 RemoteViews的內(nèi)部機制

RemoteViews的作用是在其他進程中顯示并更新View界面籍救,為了更好地理解它的內(nèi)部機制劳殖,我們先來看一下它的主要功能。首先看一下它的構(gòu)造方法诞仓,這里只介紹一個最常用的構(gòu)造方法:public RemoteViews(String packageName, int layoutId)幕与,它接受兩個參數(shù),第一個表示當(dāng)前應(yīng)用的包名价淌,第二個參數(shù)表示待加載的布局文件申眼,這個很好理解。RemoteViews目前并不能支持所有的View類型蝉衣,它所支持的所有類型如下:

Layout

FrameLayout括尸、LinarLayout、RelativeLayout病毡、GridLayout濒翻。

View

AnalogClock、Button啦膜、Chronometer有送、ImageButton、ImageView僧家、ProgressBar雀摘、TextView、ViewFlipper啸臀、ListView届宠、GridView、StackView乘粒、AdapterViewFlipper、ViewStub伤塌。

上面所描述的是RemoteViews所支持的所有的View類型灯萍,RemoteViews不支持它們的子類以及其他View類型,也就是說RemoteViews中不能使用除了上述列表意外的View每聪,也無法只用自定義View旦棉。比如如果我們在通知欄的RemoteViews中使用系統(tǒng)的EditText齿风,那么通知欄消息將無法彈出并且會拋出如下異常。

E/StatusBar(765): couldn't inflate view for notification com.chenstyle.chapter_5/0x2
E/StatusBar(765): android.view.InflateException: Binary XML file line #25: Error inflating class android.widget.EditText
E/StatusBar(765): Caused by: android.view.InflatedException: Binary XML file line #25: Class not allowed to be inflated android.widget.EditText
E/StatusBar(765):   at android.view.LayoutInflater.failNotAllowed(LayoutInflater.java:695)
E/StatusBar(765):   at android.view.LayoutInflater.createView(LayoutInflater.java:628)
E/StatusBar(765):   ...21 more

上面的異常信息很明確绑洛,android.widget.EditText不允許在RemoteViews中使用救斑。

RemoteViews沒有提供findViewById方法,因此無法直接訪問里面的View元素真屯,而必須通過RemoteViews所提供的一系列set方法來完成脸候,當(dāng)然這是因為RemoteViews在遠程進程中顯示,所以沒辦法直接findViewById绑蔫。表5-2列舉了部分常用的set方法运沦,更多方法請查看相關(guān)資料。

表5-2 RemoteViews的部分set方法

方法名 作用
setTextViewText(int viewId, CharSquence text) 設(shè)置TextView的文本
setTextViewTextSize(int viewId, int units, float size) 設(shè)置TextView的字體大小
setTextColor(int viewId, int color) 設(shè)置TextView的字體顏色
setImageViewResource(int viewId, int srcId) 設(shè)置ImageView的圖片資源
setImageViewResource 設(shè)置ImageView的圖片
setInt(int viewId, String methodName, int value) 反射調(diào)用View對象的參數(shù)類型為int的方法
setLong(int viewId, String methodName, long value) 反射調(diào)用View對象的參數(shù)類型為long的方法
setBoolean(int viewId, String methodName, boolean value) 反射調(diào)用View的對象的參數(shù)為boolean的方法
setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) 為View添加單擊事件配深,事件類型只能為PendingIntent

從表5-2中可以看出携添,原本可以直接調(diào)用的View的方法,現(xiàn)在卻必須要通過RemoteViews的一系列set方法才能完成篓叶,而且從方法的聲明上來看烈掠,很像是通過反射來完成的,事實上大部分set方法的確是通過反射來完成的缸托。

下面描述一下RemoteViews的內(nèi)部機制左敌,由于RemoteViews主要用于通知欄和桌面小部件之中,這里就通過它們來分析RemoteViews的工作過程嗦董。我們知道母谎,通知欄和桌面小部件分別由NotificationManager和AppWidgetManager管理,而NotificationManager和AppWidgetManager通過Binder分別和SystemServer進程中的NotificationManagerService以及AppWidgetService進行通信京革。由此可見奇唤,通知欄和桌面小部件中的布局文件實際上是在NotificationManagerService以及AppWidgetService中被加載的,而他們運行在系統(tǒng)的SystemServer中匹摇,這就和我們的進程構(gòu)成了跨進程的通信場景咬扇。

首先RemoteViews會通過Binder傳遞到SystemServer進程,這是因為RemoteViews實現(xiàn)了Parcelable接口廊勃,因此它可以跨進程傳輸懈贺,系統(tǒng)會根據(jù)RemoteViews中的包名等信息去得到該應(yīng)用的資源。然后會通過LayoutInflater去加載RemoteViews中的布局文件坡垫。在SystemServer進程中加載后的布局文件是一個普通的View梭灿,只不過相對于我們的進程它是一個RemoteViews而已。接著系統(tǒng)會對View執(zhí)行一系列界面更新任務(wù)冰悠,這些任務(wù)就是之前我們通過set方法來提交的堡妒。set方法對View所做的更新并不是立刻執(zhí)行的,在RemoteViews內(nèi)部會記錄所有的更新操作溉卓,具體的執(zhí)行時機要等到RemoteViews被加載以后才能執(zhí)行皮迟,這樣RemoteViews就可以在SystemServer進程中顯示了搬泥,這就是我們所看到的同時蘭消息或者桌面小部件。當(dāng)需要更新RemoteViews時伏尼,我們需要調(diào)用一系列set方法并通過NotificationManager和AppWidgetManager來提交更新任務(wù)忿檩,具體的更新操作也是在SystemServer進程中完成的。

從理論上來說爆阶,系統(tǒng)完全可以通過Binder去支持所有的View和View操作燥透,但是這樣做的話代價太大,因為View的方法太多了扰她,另外就是大量的IPC操作會影響效率兽掰。為了解決這個問題,系統(tǒng)并沒有通過Binder去直接支持View的跨進程訪問徒役,而是提供了一個Action的概念孽尽,Action代表一個View操作,Action同樣實現(xiàn)了Parcelable接口忧勿。系統(tǒng)首先將View操作封裝到Action對象并將這些對象跨進程傳輸?shù)竭h程進程杉女,接著再遠程進程中執(zhí)行Action對象中的具體操作。在我們的應(yīng)用中每調(diào)用一次set方法鸳吸,RemoteViews中就會添加一個對應(yīng)的Action對象熏挎,當(dāng)我們通過NotificationManager和AppWidgetManager來提交我們的更新時,這些Action對象就會傳輸?shù)竭h程進程并在遠程進程中依次執(zhí)行晌砾,這個過程可以參看圖5-3坎拐。遠程進程通過RemoteViews的apply方法來進行View的更新操作,RemoteViews的apply方法內(nèi)部則會去遍歷所有的Action對象并調(diào)它們的apply方法养匈,具體的View更新操作是由Action對象的apply方法來完成的哼勇。上述做法的好處是顯而易見的,首先不需要定義大量的Binder接口呕乎,其次通過在遠程進程中批量執(zhí)行RemoteViews的修改操作從而避免了大量的IPC操作积担,這就提高了程序的性能,由此可見猬仁,Android系統(tǒng)在這方面的設(shè)計的確很精妙帝璧。

圖5-3 RemoteViews的內(nèi)部機制.jpg

上面從理論上分析了RemoteViews的內(nèi)部機制,接下來我們從源碼的角度再來分析RemoteViews的工作流程湿刽。它的構(gòu)造方法就不用多說了的烁,這么我們首先看一下它提供的一系列set方法,比如setTextViewText方法诈闺,其源碼如下所示撮躁。

public void setTextViewText(int viewId, CharSequence text) {
    setCharSequence(viewId, "setText", text);
}

在上面的代碼中,viewId是被操作的View的id买雾,“setText"是方法名把曼,text是要給TextView設(shè)置的文本,這里可以聯(lián)想一下TextView的setText方法漓穿,是不是很一致呢嗤军?接著再看setCharSequence的實現(xiàn),如下所示晃危。

public void setCharSequence(int viewId, String methodName, CharSequence value) {
    addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}

從setCharSequence的實現(xiàn)可以看出叙赚,它的內(nèi)部并沒有對View進程直接的操作,而是添加了一個ReflectionAction對象僚饭,從名字來看震叮,這應(yīng)該是一個反射類型的動作。再看addAction的實現(xiàn)鳍鸵,如下所示苇瓣。

private void addAction(Action a) {
    ...
    if (mAction == null) {
        mAction = new ArrayList<Action>();
    }
    mAction.add(a);
    // update the memory usage stats
    a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}

從上述代碼可以知道,RemoteViews內(nèi)部有一個mActions成員偿乖,它是一個ArrayList击罪,外界每調(diào)用一次set方法,RemoteViews就會為其創(chuàng)建一個Action對象并加入到這個ArrayList中贪薪。需要注意的是媳禁,這里僅僅是將Action對象保存起來了,并未對View進行實際的操作画切,這一點在上面的理論分析中已經(jīng)提到過了竣稽。到這里setTextViewText這個方法的源碼已經(jīng)分析完了,但是我們好像還是什么都不知道的感覺霍弹,沒關(guān)系毫别,接著我們需要看一下這個ReflectionAction的實現(xiàn)就知道了。在看它的實現(xiàn)之前庞萍,我們需要先看一下RemoteViews的apply方法以及Action類的實現(xiàn)拧烦,首先看一下RemoteViews的apply方法,如下所示钝计。

public View apply(Context context, ViewGroup parent, onClickHandler handler) {
    RemoteViews rvToApply getRemoteViewsToApply(context);
    
    View result;
    ...
    
    LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    
    // Clone inflater so we load resources from correct context and
    // we don't add a filter to the static version returned by getSystemService.
    inflater = inflater.cloneInContext(inflationContext);
    inflater.setFilter(this);
    result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
    
    rvToApply.performApply(result, parent, handler);
    
    return result;
}

從上面的代碼可以看出恋博,首先會通過LayoutInflater去加載RemoteViews中的布局文件,RemoteViews中的布局文件可以通過getLayoutId這個方法獲得私恬,加載完布局文件后會通過performApply去執(zhí)行一些更新操作债沮,代碼如下所示。

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);
        }
    }
}

performApply的實現(xiàn)就比較好理解了本鸣,它的作用就是遍歷mActions這個列表并執(zhí)行每個Action對象的apply方法疫衩。還記得mAction嗎?每一次的set操作都會對應(yīng)著它里面的一個Action對象荣德,因此我們可以斷定闷煤,Action對象的apply方法就是真正操作View的地方童芹,實際上的確如此。

RemoteViews在通知欄和桌面小部件中的工作過程和上面描述的過程是一致的鲤拿,當(dāng)我們調(diào)用RemoteViews的set方法時假褪,并不會立刻更新它們的界面,而必須要通過NotificationManager的notify方法以及AppWidgetManager的updateAppWidget的內(nèi)部實現(xiàn)中近顷,它們的確是通過RemoteViews的apply以及reapply方法來加載或者更新界面的生音,apply和reApply的區(qū)別在于:apply會加載布局并更新界面,而reApply則只會更新界面窒升。通知欄和桌面小插件在初始化界面時會調(diào)用apply方法缀遍,而在后續(xù)的更新界面時則會調(diào)用reapply方法。這里先看一下BaseStatusBar的updateNotificationViews方法中饱须,如下所示域醇。

private void updateNotificationViews(NotificationData.Entry entry, StatusBarNotification notification, boolean isHandsUp) {
    final RemoteViews contentViews = notification.getNotification().contentView;
    final RemoteViews bigContentView = isHandsUp ? notification.getNotification().headsUpContentView : notification.getNotification().bigCOntentView;
    final Notification publicVersion = notification.getNotification().publicVersion;
    final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView : null;
    
    // Reapply the RemoteViews
    contentView.reapply(mContext, entry.expanded, mOnClickHandler);
    ...
}

很顯然,上述代碼表示當(dāng)通知欄界面需要更新時冤寿,它會通過RemoteViews的reapply方法來更新界面歹苦。

接著再看一下AppWidgetHostView的updateAppWidget方法,在它的內(nèi)部有如下一段代碼:

mRemoteContext = getRemoteContext();
int layoutId = remoteViews.getLayoutId();

// If our stale view has been prepared to match active, and the new
// layout matches, try recycling it
if (content == null && layoutId == mLayoutId) {
    try {
        remoteViews.reapply(mContext, mView, mOnClickHandler);
        content = mView;
        recycled = true;
        if (LOGD) Log.d(TAG, "was able to recycled existing layout");
    } catch (RuntimeException e) {
        exception = e;
    }
}

// Try normal RemoteView inflation
if (content == null) {
    try {
        content = remoteViews.apply(mContext, this, mOnClickHandler);
        if (LOGD) Log.d(TAG, "had to inflate new layout");
    } catch (RuntimeException e) {
        exception = e;
    }
}

從上述代碼可以發(fā)現(xiàn)督怜,桌面小部件在更新界面時也是通過RemoteViews的reapply方法來實現(xiàn)的殴瘦。

了解了apply以及reapply的作用以后,我們再繼續(xù)看一些Action的子類的具體實現(xiàn)号杠,首先看一下RefectionAction的具體實現(xiàn)蚪腋,它的源碼如下所示。

private final class ReflectionAction extends Action {
    ReflectionAction(int viewId, String methodName, int type, Object value) {
        this.valueId = viewId;
        this.methodName = methodName;
        this.type = type;
        this.value = value;
    }
    
    ...
    @Override
    public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
        final View view = root.findViewById(viewId);
        if (view == null) return;
        
        Class<?> param = getParameterType();
        if (param == null) {
            throw new ActionException("bad type: " + this.type);
        }
        
        try {
            getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
        } catch (ActionException e) {
            throw e;
        } catch (Exception ex) {
            throw new ActionException(ex);
        }
    }
}

通過上述代碼可以發(fā)現(xiàn)姨蟋,RefectionAction表示的是一個反射動作屉凯,通過它對View的操作會以反射的方式來調(diào)用,其中g(shù)etMethod就是根據(jù)方法名來得到反射所需要的Method對象眼溶。使用Reflection的set方法有:setTextViewText悠砚、setBoolean、setLong堂飞、setDouble等灌旧。除了ReflectionAction,還有其他Action绰筛,比如TextViewSizeAction枢泰、ViewPaddingAction、setOnClickPendingIntent等铝噩。這里再分析一下TextViewSizeAction衡蚂,它的實現(xiàn)如下所示。

private class TextViewSizeAction extends Action {
    public TextViewSizeAction(int viewId, int units, float size) {
        this.viewId = viewId;
        this.units = units;
        this.size = size;
    }
    ...
    
    @Override
    public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
        final TextView target = (TextView) root.findViewById(viewId);
        if (target == null) return;
        target.setTextSize(units, size);
    }
    
    public String getActionName() {
        return "TextViewSizeAction";
    }
    
    int units;
    float size;
    
    public final static int TAG = 13;
}

TextViewSizeAction的實現(xiàn)比較簡單,它之所以不用反射來實現(xiàn)毛甲,是因為setTextSize這個方法有2個參數(shù)年叮,因此無法復(fù)用ReflectionAction,因為ReflectionAction的反射調(diào)用只有一個參數(shù)丽啡。其他Action這里就不一一進行分析了谋右,讀者可以查看RemoteViews的源代碼。

關(guān)于單擊事件补箍,RemoteViews中只支持發(fā)起PendingIntent,不支持onClickListener那種模式啸蜜。另外坑雅,我們需要注意setOnClickPendingIntent、setPendingIntentTemplate以及setOnClickFillInIntent它們之間的區(qū)別和聯(lián)系衬横。首先setOnClickPendingIntent用于給普通View設(shè)置單擊事件裹粤,但是不能給集合(ListView和StackView)中的View設(shè)置單擊事件,因為開銷比較大蜂林,所以系統(tǒng)禁止了這種方式遥诉;其次,如果要給ListView和StackView中的item添加單擊事件噪叙,則必須將setPendingIntentTemplate和setOnClickFillInIntent組合使用才可以矮锈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市睁蕾,隨后出現(xiàn)的幾起案子苞笨,更是在濱河造成了極大的恐慌,老刑警劉巖子眶,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瀑凝,死亡現(xiàn)場離奇詭異,居然都是意外死亡臭杰,警方通過查閱死者的電腦和手機粤咪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渴杆,“玉大人寥枝,你說我怎么就攤上這事〗埽” “怎么了脉顿?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長点寥。 經(jīng)常有香客問我艾疟,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任蔽莱,我火速辦了婚禮弟疆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盗冷。我一直安慰自己怠苔,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布仪糖。 她就那樣靜靜地躺著柑司,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锅劝。 梳的紋絲不亂的頭發(fā)上攒驰,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音故爵,去河邊找鬼玻粪。 笑死,一個胖子當(dāng)著我的面吹牛诬垂,可吹牛的內(nèi)容都是我干的劲室。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼结窘,長吁一口氣:“原來是場噩夢啊……” “哼很洋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晦鞋,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蹲缠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悠垛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體线定,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年确买,在試婚紗的時候發(fā)現(xiàn)自己被綠了斤讥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡湾趾,死狀恐怖芭商,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搀缠,我是刑警寧澤铛楣,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站艺普,受9級特大地震影響簸州,放射性物質(zhì)發(fā)生泄漏鉴竭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一岸浑、第九天 我趴在偏房一處隱蔽的房頂上張望搏存。 院中可真熱鬧,春花似錦矢洲、人聲如沸璧眠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽责静。三九已至,卻和暖如春掘譬,著一層夾襖步出監(jiān)牢的瞬間泰演,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工葱轩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藐握。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓靴拱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猾普。 傳聞我的和親對象是個殘疾皇子袜炕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 書上很多東西都過時了或者在Android8.0上會出現(xiàn)問題,下面的總結(jié)都是在Android8.0上運行成功的初家,會有...
    Utte閱讀 3,568評論 0 5
  • 學(xué)習(xí)內(nèi)容: RemoteViews 在通知欄和桌面小部件上的應(yīng)用 RemoteViews 的內(nèi)部機制 Remote...
    whd_Alive閱讀 479評論 0 0
  • 概述 RemoteViews顧名思義就是遠程View偎窘,它表示的是一個View結(jié)構(gòu),它可以在其他進程中顯示溜在,為了跨進...
    shenhuniurou閱讀 23,782評論 1 20
  • 本文基于android-27編譯并進行源碼分析 一陌知、概述 什么是RemoteViews? 其和遠程Service很...
    達則兼濟天下閱讀 2,929評論 0 2
  • 長沙,夏天的時候被成為火爐城市掖肋。如果不是身臨其境仆葡,還真是不敢相信連非洲人都覺得熱。不過志笼,得虧有美食和美景沿盅,那份狂熱...
    szh無為不爭閱讀 590評論 1 1