RemoteViews詳細(xì)解釋

RemoteViews詳細(xì)解釋

原載于:RemoteViews詳細(xì)解釋

說明

想要完全的理解RetmoteView必須要說明一下Android Widet客冈。

Android widget 也稱為桌面插件霞篡,其是android系統(tǒng)應(yīng)用開發(fā)層面的一部分。Android中的AppWidget與google widget和中移動(dòng)的widget并不是一個(gè)概念挟裂,這里的AppWidget只是把一個(gè)進(jìn)程的控件嵌入到別外一個(gè)進(jìn)程的窗口里的一種方法。

AppWidgetFramework

Android系統(tǒng)增加了AppWidget 框架蛋铆,用以支持widget類型應(yīng)用的開發(fā)触幼。AppWidget 框架主要由兩個(gè)部件來組成:

(1)AppWidgetService是框架的的核心類拆祈,是系統(tǒng) service之一恨闪,它負(fù)責(zé)widgets的管理工作。加載放坏,刪除咙咽,定時(shí)事件等都需要AppWidgetService的處理。開機(jī)自啟動(dòng)的淤年。

   AppWidgetService存在的目的主要是解開AppWidgetProvider和AppWidgetHost之間的耦合钧敞。如果 AppWidgetProvider和AppWidgetHost的關(guān)系固定死了,AppWidget就無法在任意進(jìn)程里顯示了麸粮。而有了 AppWidgetService溉苛,AppWidgetProvider根本不需要知道自己的AppWidget在哪里顯示了。

(2)AppWidgetManager 負(fù)責(zé)widget視圖的實(shí)際更新以及相關(guān)管理弄诲。

工作流程

繪制流程201704071445
繪制流程201704071445
  1. 編寫一個(gè)widget(先不考慮后臺(tái)服務(wù)以及用戶管理界面等)

實(shí)際是寫一個(gè)事件監(jiān)聽類即一個(gè)BroadcastReceiver子類愚战,當(dāng)然框架已經(jīng)提供了一個(gè)輔助類AppWidgetProvider娇唯,實(shí)現(xiàn)的類只要實(shí)現(xiàn)其方法即可,其中必須實(shí)現(xiàn)的方法是onUpdate 寂玲,其實(shí)就是一個(gè)定時(shí)事件塔插,widget監(jiān)聽此事件
另外就是規(guī)劃好視圖(layout),將此widget打包安裝拓哟。

  1. 當(dāng)android系統(tǒng)啟動(dòng)時(shí)想许,AppWidgetService 就將負(fù)責(zé)檢查所有的安裝包

將檢查AndroidManifest.xml(不要告訴我不知道,如果不知道可要看看基本開發(fā)知識(shí)了)文件中有<metadata android:name="android.appwidget.provider" android:resource="@xml/appwidget_info" />
信息的程序包記錄下來

  1. 從用戶菜單將已經(jīng)安裝的widget添加到桌面
    也就是將widget在桌面上顯示出來断序,這個(gè)是由AppWidgetService和AppWidgetManager完成的流纹,其中AppWidgetManager 將負(fù)責(zé)將視圖發(fā)送到桌面顯示出來,并將此widget記錄到系統(tǒng)文件中
  2. AppWidgetService將根據(jù)widget配置中的updatePeriodMillis屬性來定時(shí)發(fā)送ACTION_APPWIDGET_UPDATE事件违诗,此事件將激活widget的事件監(jiān)聽方法onUpdate漱凝,此方法將通過AppWidgetManager完成widget內(nèi)容的更新和其他操作。

AppWidgetHost

AppWidgetHost 是實(shí)際控制widget的地方较雕,大家注意碉哑,widget不是一個(gè)單獨(dú)的用戶界面程序挚币,他必須寄生在某個(gè)程序(activity)中亮蒋,這樣如果程序要支持widget寄生就要實(shí)現(xiàn)AppWidgetHost,桌面程序(Launcher)就實(shí)現(xiàn)了這個(gè)接口妆毕。

AppWidgetHost和AppWidgetHostView是在框架中定義的兩個(gè)基類慎玖。

AppWidgetHostView是真正的View,但它只是一個(gè)容器笛粘,用來容納實(shí)際的AppWidget的View趁怔。這個(gè)AppWidget的View是根據(jù)RemoteViews的描述來創(chuàng)建。

AppWidgetProvider

AppWidgetProvider是AppWidget提供者需要實(shí)現(xiàn)的接口薪前,它實(shí)際上是一個(gè)BroadcastReceiver润努。只不過子類要實(shí)現(xiàn)的不再是onReceive。作為AppWidgetProvider的實(shí)現(xiàn)者示括,一定要實(shí)現(xiàn)onUpdate函數(shù)铺浇,因?yàn)檫@個(gè)函數(shù)決定widget的顯示方式,如果沒有這個(gè)函數(shù)widget根本沒辦法出現(xiàn)

RemoteViews介紹

RemoteViews表示的是一個(gè)view結(jié)構(gòu)垛膝,它可以在其他進(jìn)程中顯示鳍侣。由于它在其他進(jìn)程中顯示,為了能夠更新它的界面吼拥,RemoteViews提供了一組基礎(chǔ)的操作用于跨進(jìn)程更新它的界面倚聚。

RemoteViews主要用于通知欄通知和桌面小部件的開發(fā),通知欄通知是通過NotificationManagernotify方法來實(shí)現(xiàn)的凿可;桌面小部件是通過AppWidgetProvider來實(shí)現(xiàn)的惑折,它本質(zhì)上是一個(gè)廣播(BroadcastReceiver)。這兩者的界面都是運(yùn)行在SystemServer進(jìn)程中(跨進(jìn)程)

RemoteViews并不是一個(gè)真正的View,它沒有實(shí)現(xiàn)View的接口惨驶,而只是一個(gè)用于描述View的實(shí)體矗积。比如:創(chuàng)建View需要的資源ID和各個(gè)控件的事件響應(yīng)方法。RemoteViews會(huì)通過進(jìn)程間通信機(jī)制傳遞給AppWidgetHost敞咧。

現(xiàn)在我們可以看出棘捣,Android中的AppWidget與google widget和中移動(dòng)的widget并不是一個(gè)概念,這里的AppWidget只是把一個(gè)進(jìn)程的控件嵌入到別外一個(gè)進(jìn)程的窗口里的一種方法休建。View在另 外一個(gè)進(jìn)程里顯示乍恐,但事件的處理方法還是在原來的進(jìn)程里。

snipaste_20170407_114428

RemoteViews應(yīng)用

在通知欄的應(yīng)用

創(chuàng)建一個(gè)通知:

    public void showNotification(View view) {
        Notification notification = new Notification();
        notification.icon = R.mipmap.ic_launcher;
        notification.tickerText = "天意博文";
        notification.flags = Notification.FLAG_AUTO_CANCEL;
        notification.when = System.currentTimeMillis();
        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra("ceshi",0);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification);
        remoteViews.setTextViewText(R.id.tv,"天意博文textview");
        remoteViews.setImageViewResource(R.id.iv,R.mipmap.ic_launcher);
        remoteViews.setTextColor(R.id.tv,getResources().getColor(R.color.colorPrimaryDark));
        PendingIntent pendingIntent1 = PendingIntent.getActivity(this, 0, new Intent(this, Main2Activity.class), PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.iv,pendingIntent1);
        notification.contentView = remoteViews;
        notification.contentIntent = pendingIntent;
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(0,notification);
    }

對(duì)用的布局notification.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="horizontal">

    <TextView
        android:id="@+id/tv"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <ImageView
        android:id="@+id/iv"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>
</LinearLayout>

顯示如下圖所示的通知欄:

snipaste_20170407_100103

并且點(diǎn)擊圖片的時(shí)候會(huì)跳轉(zhuǎn)到Main2Activity:

snipaste_20170407_100141

給對(duì)應(yīng)的布局View設(shè)置點(diǎn)擊事件:

remoteViews.setOnClickPendingIntent(R.id.iv,pendingIntent1)

單擊通知時(shí)的響應(yīng)事件:

notification.contentIntent = pendingIntent//對(duì)應(yīng)的是第一個(gè)pendingIntent

RemoteViews在桌面小部件的應(yīng)用

新建桌面小部件测砂,在as中創(chuàng)建十分簡(jiǎn)單茵烈,在布局中新建widget,下一步即可:

snipaste_20170407_101710

創(chuàng)建完成之后會(huì)創(chuàng)建如下幾個(gè)文件:


snipaste_20170407_101919

home_widget.xml是小部件的布局文件砌些,home_widget_info.xml是小部件的配置文件呜投,Home_Widget.java是小部件的邏輯控制文件

snipaste_20170407_102124

小部件的本質(zhì)是一個(gè)BroadcastReceiver,所以還要在mainifest.xml中注冊(cè)

home_widget.xml具體實(shí)現(xiàn)存璃,都是自動(dòng)生成仑荐,和普通的布局沒有區(qū)別

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#09C"
                android:padding="@dimen/widget_margin">

    <TextView
        android:id="@+id/appwidget_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:background="#09C"
        android:contentDescription="@string/appwidget_text"
        android:text="@string/appwidget_text"
        android:textColor="#ffffff"
        android:textSize="24sp"
        android:textStyle="bold|italic"/>

</RelativeLayout>

home_widget_info.xml具體實(shí)現(xiàn),是小部件的配置文件纵东,指定了布局粘招,大小更新時(shí)間等

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/home__widget"
    android:initialLayout="@layout/home__widget"
    android:minHeight="40dp"
    android:minWidth="40dp"
    android:previewImage="@drawable/example_appwidget_preview"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="86400000"
    android:widgetCategory="home_screen">
</appwidget-provider>

Androidmainfest.xml更新標(biāo)簽,注意在intent-filter中一定要含有<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>偎球,這是系統(tǒng)的規(guī)范

        <receiver android:name="layout.Home_Widget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
                <!--能夠響應(yīng)自定義的action-->
                <action android:name="haotianyi.win"/>
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/home__widget_info"/>
        </receiver>

具體實(shí)現(xiàn)邏輯

當(dāng)每一次點(diǎn)擊小部件的時(shí)候洒扎,顯示的textview都會(huì)顯示當(dāng)前的時(shí)間

201704071058
public class Home_Widget extends AppWidgetProvider {
    public static final String ACTION_CLICK = "haotianyi.win";
    public static final String TAG = "haotianyi.win";
    public static int click_count = 0;

    public void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                                int appWidgetId) {

        String widgetText = context.getString(R.string.appwidget_text);
        // Construct the RemoteViews object
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.home__widget);
        views.setTextViewText(R.id.appwidget_text, widgetText);

        Intent intentClick = new Intent();
        intentClick.setAction(ACTION_CLICK);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0);
        views.setOnClickPendingIntent(R.id.appwidget_text, pendingIntent);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        Log.e(TAG, "onReceive: onReceive");
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.home__widget);
        views.setTextViewText(R.id.appwidget_text, "天意博文" + System.currentTimeMillis());

        Intent intentClick = new Intent();
        intentClick.setAction(ACTION_CLICK);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0);
        views.setOnClickPendingIntent(R.id.appwidget_text, pendingIntent);
        AppWidgetManager manager = AppWidgetManager.getInstance(context);

        // Instruct the widget manager to update the widget
        manager.updateAppWidget(new ComponentName(context, Home_Widget.class), views);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        Log.e(TAG, "onReceive: onUpdate");
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
        Log.e(TAG, "onReceive: onEnabled");
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }
}

實(shí)現(xiàn)邏輯,首先第一次添加的時(shí)候會(huì)執(zhí)行onReceive方法衰絮,在方法中設(shè)置了點(diǎn)擊監(jiān)聽袍冷,當(dāng)發(fā)生點(diǎn)擊事件的時(shí)候,由于自定義了action猫牡,所以含有特定action的broadcastReceiver會(huì)啟動(dòng)胡诗,在當(dāng)前案例中也就是Home_Widget在一次啟動(dòng),同時(shí)又執(zhí)行了onReceive镊掖,更新視圖乃戈,同時(shí)設(shè)置事件監(jiān)聽。

小部件的生命周期

onEnable:當(dāng)小部件第一次添加到桌面時(shí)調(diào)用亩进,小部件可以添加多次但是只在第一次添加的時(shí)候調(diào)用症虑;

onUpdate:小部件被添加時(shí)或者每次小部件更新時(shí)都會(huì)調(diào)用一次該方法,每個(gè)周期小部件都會(huì)自動(dòng)更新一次归薛,不是點(diǎn)擊的時(shí)候更新谍憔,而是到指定配置文件時(shí)間的時(shí)候才更新

onDeleted:每刪除一次小部件就調(diào)用一次匪蝙;

onDisabled:當(dāng)最后一個(gè)該類型的小部件被刪除時(shí)調(diào)用該方法;

onReceive:這是廣播內(nèi)置的方法习贫,用于分發(fā)具體的事件給其他方法逛球,所以該方法一般要調(diào)用super.onReceive(context, intent); 如果自定義了其他action的廣播,就可以在調(diào)用了父類方法之后進(jìn)行判斷苫昌,如上面代碼所示颤绕。

PendingIntent

PendingIntent表示一種處于Pending狀態(tài)的Intent,pending表示的是即將發(fā)生的意思祟身,它是在將來的某個(gè)不確定的時(shí)刻放生奥务,而Intent是立刻發(fā)生。

PendingIntent支持三種待定意圖:?jiǎn)?dòng)Activity(getActivity)袜硫,啟動(dòng)Service(getService)氯葬,發(fā)送廣播(getBroadcast)。

匹配規(guī)則

如果兩個(gè)Intent的ComponentName和intent-filter都相同婉陷,那么這兩個(gè)Intent就是相同的帚称,Extras不參與Intent的匹配過程。

參數(shù)flags常見的類型有:FLAG_ONE_SHOT秽澳、FLAG_NO_CREATE闯睹、FLAG_CANCEL_CURRENTFLAG_UPDATE_CURRENT肝集。

FLAG_ONE_SHOT:當(dāng)前描述的PendingIntent只能被調(diào)用一次瞻坝,然后它就會(huì)被自動(dòng)cancel蛛壳。如果后續(xù)還有相同的PendingIntent杏瞻,那么它們的send方法就會(huì)調(diào)用失敗。對(duì)于通知欄消息來說衙荐,如果采用這個(gè)flag捞挥,那么同類的通知只能使用一次,后續(xù)的通知單擊后將無法打開忧吟。

FLAG_NO_CREATE:當(dāng)前描述的PendingIntent不會(huì)主動(dòng)創(chuàng)建砌函,如果當(dāng)前PendingIntent之前不存在,那么getActivity溜族、getService和getBroadcast方法會(huì)直接返回null讹俊,即獲取PendingIntent失敗。這個(gè)標(biāo)志位使用很少煌抒。

FLAG_CANCEL_CURRENT:當(dāng)前描述的PendingIntent如果已經(jīng)存在仍劈,那么它們都會(huì)被cancel,然后系統(tǒng)會(huì)創(chuàng)建一個(gè)新的PendingIntent寡壮。

對(duì)于通知欄消息來說贩疙,那些被cancel的通知單擊后將無法打開讹弯。

FLAG_UPDATE_CURRENT:當(dāng)前描述的PendingIntent如果已經(jīng)存在,那么它們都會(huì)被更新这溅,即它們的Intent中的Extras會(huì)被替換成最新的组民。

RemoteViews機(jī)制

RemoteView沒有findViewById方法,因此無法訪問里面的View元素悲靴,而必須通過RemoteViews所提供的一系列set方法來完成臭胜,這是通過反射調(diào)用的

通知欄和小組件分別由NotificationManager(NM)和AppWidgetManager(AWM)管理,而NM和AWM通過Binder分別和SystemService進(jìn)程中的NotificationManagerService以及AppWidgetService中加載的癞尚,而它們運(yùn)行在系統(tǒng)的SystemService中庇楞,這就和我們進(jìn)程構(gòu)成了跨進(jìn)程通訊。

構(gòu)造方法

public RemoteViews(String packageName, int layoutId)否纬,第一個(gè)參數(shù)是當(dāng)前應(yīng)用的包名吕晌,第二個(gè)參數(shù)是待加載的布局文件。

支持組件

布局:FrameLayout临燃、LinearLayout睛驳、RelativeLayout、GridLayout

組件:Button膜廊、ImageButton乏沸、ImageView、ProgressBar爪瓜、TextView蹬跃、ListView、GridView铆铆、ViewStub等(例如EditText是不允許在RemoveViews中使用的蝶缀,使用會(huì)拋異常)。

原理

系統(tǒng)將view操作封裝成Action對(duì)象薄货,Action同樣實(shí)現(xiàn)了Parcelable接口翁都,通過Binder傳遞到SystemServer進(jìn)程。遠(yuǎn)程進(jìn)程通過RemoteViews的apply方法來進(jìn)行view的更新操作谅猾,RemoteViews的apply方法內(nèi)部則會(huì)去遍歷所有的action對(duì)象并調(diào)用它們的apply方法來進(jìn)行view的更新操作柄慰。

這樣做的好處是不需要定義大量的Binder接口,其次批量執(zhí)行RemoteViews中的更新操作提高了程序性能税娜。

工作流程

首先RemoteViews會(huì)通過Binder傳遞到SystemService進(jìn)程坐搔,因?yàn)镽emoteViews實(shí)現(xiàn)了Parcelable接口,因此它可以跨進(jìn)程傳輸敬矩,系統(tǒng)會(huì)根據(jù)RemoteViews的包名等信息拿到該應(yīng)用的資源概行;然后通過LayoutInflater去加載RemoteViews中的布局文件。接著系統(tǒng)會(huì)對(duì)View進(jìn)行一系列界面更新任務(wù)谤绳,這些任務(wù)就是之前我們通過set來提交的占锯。set方法對(duì)View的更新并不會(huì)立即執(zhí)行袒哥,會(huì)記錄下來,等到RemoteViews被加載以后才會(huì)執(zhí)行消略。

apply和reApply的區(qū)別

apply會(huì)加載布局并更新界面堡称,而reApply則只會(huì)更新界面。通知欄和桌面小部件在界面的初始化中會(huì)調(diào)用apply方法艺演,而在后面的更新界面中會(huì)調(diào)用reapply方法

源碼分析

首先從setTextViewText方法切入:

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

繼續(xù)跟進(jìn):

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

沒有對(duì)view直接操作却紧,但是添加了一個(gè)ReflectionAction,繼續(xù)跟進(jìn):

    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<Action>();
        }
        mActions.add(a);

        // update the memory usage stats
        a.updateMemoryUsageEstimate(mMemoryUsageCounter);
    }

這里僅僅把每一個(gè)action存進(jìn)list胎撤,似乎線索斷掉了晓殊,這時(shí)候換一個(gè)切入點(diǎn)查看updateAppWidget方法,或者是notificationManager.notify因?yàn)楦乱晥D都要調(diào)用者兩個(gè)方法

    public void updateAppWidget(int appWidgetId, RemoteViews views) {
        if (mService == null) {
            return;
        }
        updateAppWidget(new int[] { appWidgetId }, views);
    }

繼續(xù)跟進(jìn):

    public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
        if (mService == null) {
            return;
        }
        try {
            mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

出現(xiàn)方法無法訪問伤提,這時(shí)我們思考巫俺,RemoteViews不是真正的view啊,所以是否可以去AppWidgetHostView看看肿男,調(diào)轉(zhuǎn)到updateAppWidget方法:

    public void updateAppWidget(RemoteViews remoteViews) {
        applyRemoteViews(remoteViews);
    }

繼續(xù)跟進(jìn)介汹,好多代碼:

![](file:///C:\Users\SIMAXI1\AppData\LocalLow\Baidu\BAIDUP1\Account\COMMON1\CUSTOM1\RECOMM1\0C1CC91.JPG)

   protected void applyRemoteViews(RemoteViews remoteViews) {
        if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);

        boolean recycled = false;
        View content = null;
        Exception exception = null;

//各種判斷,省略
        if (remoteViews == null) {
            if (mViewMode == VIEW_MODE_DEFAULT) {
                // We've already done this -- nothing to do.
                return;
            }
            content = getDefaultView();
            mLayoutId = -1;
            mViewMode = VIEW_MODE_DEFAULT;
        } else {
            if (mAsyncExecutor != null) {
                inflateAsync(remoteViews);
                return;
            }
            // Prepare a local reference to the remote Context so we're ready to
            // inflate any requested LayoutParams.
            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 recycle 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;
                }
            }

            mLayoutId = layoutId;
            mViewMode = VIEW_MODE_CONTENT;
        }

        applyContent(content, recycled, exception);
        updateContentDescription(mInfo);
    }

這么多行代碼舶沛,好像只有remoteViews.reapply(mContext, mView, mOnClickHandler);有點(diǎn)意思嘹承,和RemoteViews相關(guān)聯(lián)了,那么跳轉(zhuǎn)到RemoteViews的reapply方法:

    public void reapply(Context context, View v, OnClickHandler handler) {
        RemoteViews rvToApply = getRemoteViewsToApply(context);

        // In the case that a view has this RemoteViews applied in one orientation, is persisted
        // across orientation change, and has the RemoteViews re-applied in the new orientation,
        // we throw an exception, since the layouts may be completely unrelated.
        if (hasLandscapeAndPortraitLayouts()) {
            if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
                throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
                        " that does not share the same root layout id.");
            }
        }

        rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
    }

繼續(xù)跟進(jìn):

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

有點(diǎn)意思了如庭,剛才我們說吧視圖轉(zhuǎn)換成action叹卷,現(xiàn)在終于看到了,由于action是抽象類坪它,我們可以看看它子類的實(shí)現(xiàn):

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

終于看到反射調(diào)用改變內(nèi)容的方法了

![](file:///C:\Users\SIMAXI1\AppData\LocalLow\Baidu\BAIDUP1\Account\COMMON1\CUSTOM1\E6371E1\F21E1A1.JPG)

簡(jiǎn)單應(yīng)用

可以參考桌面小部件的原理骤竹,利用RemoteViews來實(shí)現(xiàn)兩個(gè)進(jìn)程之間View的傳遞,

201704071532

首先第一個(gè)activity2啟動(dòng)activity1哟楷,在activity1中發(fā)送廣播瘤载,返回,在activity2中看到來自activity1中的天意博文

Main2Activity主要負(fù)責(zé)廣播注冊(cè)和啟動(dòng)第一個(gè)activity

public class Main2Activity extends AppCompatActivity {

    private LinearLayout mLinearLayout;

    private final String TAG = "ty";
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            RemoteViews view = intent.getParcelableExtra("view");
            View apply = view.apply(Main2Activity.this, mLinearLayout);
            mLinearLayout.addView(apply);
            Log.e(TAG, "onReceive:---------- " );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        mLinearLayout = (LinearLayout)findViewById(R.id.ll);
        registerReceiver(mReceiver,new IntentFilter("haotianyi.win"));
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(mReceiver);
        super.onDestroy();
    }

    public void startActivity(View view) {
        startActivity(new Intent(this,MainActivity.class));
    }
}

兩個(gè)activity跑在不同的進(jìn)程之中:

        <activity
            android:process=":accept"
            android:name=".MainActivity">
        </activity>
        <activity
            android:name=".Main2Activity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

activity1的代碼卖擅,就是發(fā)送廣播的操作:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void sendBroadcast(View view) {
        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.emulator);
        remoteViews.setTextViewText(R.id.tv,"天意博文");

        Intent intent = new Intent("haotianyi.win");
        intent.putExtra("view",remoteViews);

        sendBroadcast(intent);
    }
}

對(duì)應(yīng)的布局很簡(jiǎn)單只有一個(gè)TextView,emulator.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"/>
</LinearLayout>

終于搞定了D肌3徒住!?弁簟断楷!

img

參考

文章整理自Android開發(fā)藝術(shù)探索

http://blog.csdn.net/sadamdiyi/article/details/8245818

https://www.kancloud.cn/kancloud/art-of-android-development-reading-notes/90450

http://www.reibang.com/p/23041852bd85

https://developer.android.google.cn/reference/android/widget/RemoteViews.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市崭别,隨后出現(xiàn)的幾起案子冬筒,更是在濱河造成了極大的恐慌恐锣,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舞痰,死亡現(xiàn)場(chǎng)離奇詭異土榴,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)响牛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門玷禽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呀打,你說我怎么就攤上這事矢赁。” “怎么了贬丛?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵撩银,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我豺憔,道長(zhǎng)蜒蕾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任焕阿,我火速辦了婚禮咪啡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暮屡。我一直安慰自己撤摸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布褒纲。 她就那樣靜靜地躺著准夷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莺掠。 梳的紋絲不亂的頭發(fā)上衫嵌,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音彻秆,去河邊找鬼楔绞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛唇兑,可吹牛的內(nèi)容都是我干的酒朵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼扎附,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蔫耽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起留夜,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匙铡,失蹤者是張志新(化名)和其女友劉穎图甜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳖眼,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡黑毅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了具帮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片博肋。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜂厅,靈堂內(nèi)的尸體忽然破棺而出匪凡,到底是詐尸還是另有隱情,我是刑警寧澤掘猿,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布病游,位于F島的核電站,受9級(jí)特大地震影響稠通,放射性物質(zhì)發(fā)生泄漏衬衬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一改橘、第九天 我趴在偏房一處隱蔽的房頂上張望滋尉。 院中可真熱鬧,春花似錦飞主、人聲如沸狮惜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)碾篡。三九已至,卻和暖如春筏餐,著一層夾襖步出監(jiān)牢的瞬間开泽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工魁瞪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留穆律,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓佩番,卻偏偏與公主長(zhǎng)得像众旗,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趟畏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 概述 RemoteViews顧名思義就是遠(yuǎn)程View,它表示的是一個(gè)View結(jié)構(gòu)滩租,它可以在其他進(jìn)程中顯示赋秀,為了跨進(jìn)...
    shenhuniurou閱讀 23,793評(píng)論 1 20
  • RemoteViews是一種遠(yuǎn)程View利朵,可以在其他進(jìn)程中顯示,為了能夠更新它的界面猎莲,RemoteViews提供了...
    HuDP閱讀 6,828評(píng)論 6 25
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,522評(píng)論 25 707
  • 1. RemoteViews簡(jiǎn)介 遠(yuǎn)程View绍弟?遠(yuǎn)程服務(wù)更好理解。遠(yuǎn)程服務(wù)是跨進(jìn)程的服務(wù)著洼,那么遠(yuǎn)程View當(dāng)然是跨...
    武安長(zhǎng)空閱讀 655評(píng)論 0 0
  • 一覺醒來樟遣,宿舍還在沉睡,遠(yuǎn)處只有蛐蛐聲在與我應(yīng)和作伴身笤。 本來是在做一個(gè)夢(mèng)豹悬,夢(mèng)里我回到小學(xué)的時(shí)候,天很冷液荸,我?guī)Я艘幻?..
    木木雨林閱讀 188評(píng)論 0 0