<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ### 5.2 RemoteViews的內部機制 RemoteViews的作用是在其他進程中顯示并更新View界面,為了更好地理解它的內部機制,我們先來看一下它的主要功能。首先看一下它的構造方法,這里只介紹一個最常用的構造方法:public RemoteViews(String packageName, int layoutId),它接受兩個參數,第一個表示當前應用的包名,第二個參數表示待加載的布局文件,這個很好理解。RemoteViews目前并不能支持所有的View類型,它所支持的所有類型如下: **Layout** FrameLayout、LinearLayout、RelativeLayout、GridLayout。 **View** AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub。 上面所描述的是RemoteViews所支持的所有的View類型,RemoteViews不支持它們的子類以及其他View類型,也就是說RemoteViews中不能使用除了上述列表中以外的View,也無法使用自定義View。比如如果我們在通知欄的RemoteViews中使用系統的EditText,那么通知欄消息將無法彈出并且會拋出如下異常: E/StatusBar(765): couldn't inflate view for notification com.ryg.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.InflateException: 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方法來完成,當然這是因為RemoteViews在遠程進程中顯示,所以沒辦法直接findViewById。表5-2列舉了部分常用的set方法,更多的方法請查看相關資料。 :-: 表5-2 RemoteViews的部分set方法 ![](https://img.kancloud.cn/b1/27/b12738c55eb782ca9af176f721461292_1354x481.png) 從表5-2中可以看出,原本可以直接調用的View的方法,現在卻必須要通過RemoteViews的一系列set方法才能完成,而且從方法的聲明上來看,很像是通過反射來完成的,事實上大部分set方法的確是通過反射來完成的。 下面描述一下RemoteViews的內部機制,由于RemoteViews主要用于通知欄和桌面小部件之中,這里就通過它們來分析RemoteViews的工作過程。我們知道,通知欄和桌面小部件分別由NotificationManager和AppWidgetManager管理,而NotificationManager和AppWidgetManager通過Binder分別和SystemServer進程中的NotificationManagerService以及AppWidgetService進行通信。由此可見,通知欄和桌面小部件中的布局文件實際上是在NotificationManagerService以及AppWidgetService中被加載的,而它們運行在系統的SystemServer中,這就和我們的進程構成了跨進程通信的場景。 首先RemoteViews會通過Binder傳遞到SystemServer進程,這是因為RemoteViews實現了Parcelable接口,因此它可以跨進程傳輸,系統會根據RemoteViews中的包名等信息去得到該應用的資源。然后會通過LayoutInflater去加載RemoteViews中的布局文件。在SystemServer進程中加載后的布局文件是一個普通的View,只不過相對于我們的進程它是一個RemoteViews而已。接著系統會對View執行一系列界面更新任務,這些任務就是之前我們通過set方法來提交的。set方法對View所做的更新并不是立刻執行的,在RemoteViews內部會記錄所有的更新操作,具體的執行時機要等到RemoteViews被加載以后才能執行,這樣RemoteViews就可以在SystemServer進程中顯示了,這就是我們所看到的通知欄消息或者桌面小部件。當需要更新RemoteViews時,我們需要調用一系列set方法并通過NotificationManager和AppWidgetManager來提交更新任務,具體的更新操作也是在SystemServer進程中完成的。 從理論上來說,系統完全可以通過Binder去支持所有的View和View操作,但是這樣做的話代價太大,因為View的方法太多了,另外就是大量的IPC操作會影響效率。為了解決這個問題,系統并沒有通過Binder去直接支持View的跨進程訪問,而是提供了一個Action的概念,Action代表一個View操作,Action同樣實現了Parcelable接口。系統首先將View操作封裝到Action對象并將這些對象跨進程傳輸到遠程進程,接著在遠程進程中執行Action對象中的具體操作。在我們的應用中每調用一次set方法,RemoteViews中就會添加一個對應的Action對象,當我們通過NotificationManager和AppWidgetManager來提交我們的更新時,這些Action對象就會傳輸到遠程進程并在遠程進程中依次執行,這個過程可以參看圖5-3。遠程進程通過RemoteViews的apply方法來進行View的更新操作,RemoteViews的apply方法內部則會去遍歷所有的Action對象并調用它們的apply方法,具體的View更新操作是由Action對象的apply方法來完成的。上述做法的好處是顯而易見的,首先不需要定義大量的Binder接口,其次通過在遠程進程中批量執行RemoteViews的修改操作從而避免了大量的IPC操作,這就提高了程序的性能,由此可見,Android系統在這方面的設計的確很精妙。 :-: ![](https://img.kancloud.cn/bc/bb/bcbb2986e2ab816d851b17eb89a4a04a_1317x607.png) 圖5-3 RemoteViews的內部機制 上面從理論上分析了RemoteViews的內部機制,接下來我們從源碼的角度再來分析RemoteViews的工作流程。它的構造方法就不用多說了,這里我們首先看一下它提供的一系列set方法,比如setTextViewText方法,其源碼如下所示。 ``` public void setTextViewText(int viewId, CharSequence text) { setCharSequence(viewId, "setText", text); } ``` 在上面的代碼中,viewId是被操作的View的id, “setText”是方法名,text是要給TextView設置的文本,這里可以聯想一下TextView的setText方法,是不是很一致呢?接著再看setCharSequence的實現,如下所示。 ``` public void setCharSequence(int viewId, String methodName, CharSequence value) { addAction(new ReflectionAction(viewId, methodName, ReflectionAction. CHAR_SEQUENCE, value)); } ``` 從setCharSequence的實現可以看出,它的內部并沒有對View進程直接的操作,而是添加了一個ReflectionAction對象,從名字來看,這應該是一個反射類型的動作。再看addAction的實現,如下所示。 private void addAction(Action a) { … if (mActions == null) { mActions = new ArrayList<Action>(); } mActions.add(a); // update the memory usage stats a.updateMemoryUsageEstimate(mMemoryUsageCounter); } 從上述代碼可以知道,RemoteViews內部有一個mActions成員,它是一個ArrayList,外界每調用一次set方法,RemoteViews就會為其創建一個Action對象并加入到這個ArrayList中。需要注意的是,這里僅僅是將Action對象保存起來了,并未對View進行實際的操作,這一點在上面的理論分析中已經提到過了。到這里setTextViewText這個方法的源碼已經分析完了,但是我們好像還是什么都不知道的感覺,沒關系,接著我們需要看一下這個ReflectionAction的實現就知道了。再看它的實現之前,我們需要先看一下RemoteViews的apply方法以及Action類的實現,首先看一下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 getSystem- Service. 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去執行一些更新操作,代碼如下所示。 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的實現就比較好理解了,它的作用就是遍歷mActions這個列表并執行每個Action對象的apply方法。還記得mAction嗎?每一次的set操作都會對應著它里面的一個Action對象,因此我們可以斷定,Action對象的apply方法就是真正操作View的地方,實際上的確如此。 RemoteViews在通知欄和桌面小部件中的工作過程和上面描述的過程是一致的,當我們調用RemoteViews的set方法時,并不會立刻更新它們的界面,而必須要通過Notification-Manager的notify方法以及AppWidgetManager的updateAppWidget才能更新它們的界面。實際上在AppWidgetManager的updateAppWidget的內部實現中,它們的確是通過RemoteViews的apply以及reapply方法來加載或者更新界面的,apply和reApply的區別在于:apply會加載布局并更新界面,而reApply則只會更新界面。通知欄和桌面小插件在初始化界面時會調用apply方法,而在后續的更新界面時則會調用reapply方法。這里先看一下BaseStatusBar的updateNotificationViews方法中,如下所示。 private void updateNotificationViews(NotificationData.Entry entry, StatusBarNotification notification, boolean isHeadsUp) { final RemoteViews contentView = notification.getNotification(). contentView; final RemoteViews bigContentView = isHeadsUp ? notification.getNotification().headsUpContentView : notification.getNotification().bigContentView; final Notification publicVersion = notification.getNotification(). publicVersion; final RemoteViews publicContentView = publicVersion ! = null ? public- Version.contentView : null; // Reapply the RemoteViews contentView.reapply(mContext, entry.expanded, mOnClickHandler); ... } 很顯然,上述代碼表示當通知欄界面需要更新時,它會通過RemoteViews的reapply方法來更新界面。 接著再看一下AppWidgetHostView的updateAppWidget方法,在它的內部有如下一段代碼: 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; } } 從上述代碼可以發現,桌面小部件在更新界面時也是通過RemoteViews的reapply方法來實現的。 了解了apply以及reapply的作用以后,我們再繼續看一些Action的子類的具體實現,首先看一下ReflectionAction的具體實現,它的源碼如下所示。 private final class ReflectionAction extends Action { ReflectionAction(int viewId, String methodName, int type, Object value) { this.viewId = 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); } } } 通過上述代碼可以發現,ReflectionAction表示的是一個反射動作,通過它對View的操作會以反射的方式來調用,其中getMethod就是根據方法名來得到反射所需的Method對象。使用ReflectionAction的set方法有:setTextViewText、setBoolean、setLong、setDouble等。除了ReflectionAction,還有其他Action,比如TextViewSizeAction、ViewPaddingAction、SetOnClickPendingIntent等。這里再分析一下TextViewSizeAction,它的實現如下所示。 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的實現比較簡單,它之所以不用反射來實現,是因為setTextSize這個方法有2個參數,因此無法復用ReflectionAction,因為ReflectionAction的反射調用只有一個參數。其他Action這里就不一一進行分析了,讀者可以查看RemoteViews的源代碼。 關于單擊事件,RemoteViews中只支持發起PendingIntent,不支持onClickListener那種模式。另外,我們需要注意setOnClickPendingIntent、setPendingIntentTemplate以及setOnClickFillInIntent它們之間的區別和聯系。首先setOnClickPendingIntent用于給普通View設置單擊事件,但是不能給集合(ListView和StackView)中的View設置單擊事件,比如我們不能給ListView中的item通過setOnClickPendingIntent這種方式添加單擊事件,因為開銷比較大,所以系統禁止了這種方式;其次,如果要給ListView和StackView中的item添加單擊事件,則必須將setPendingIntentTemplate和setOnClickFillInIntent組合使用才可以。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看