Android開發藝術探索 第5章 理解RemoteViews 讀書筆記

RemoteViews是一種遠程View,可以在其他進程中顯示,為了能夠更新它的界面,RemoteViews提供了一組基礎操作用于跨進程更新它的界面。
本章會介紹RemoteViews在通知欄和桌面小部件上的應用,分析RemoveViews的內部機制,最后分析RemoteViews的意義并給出一個采用RemoteViews來跨進程更新界面的示例。


5.1 RemoteViews的應用

RemoteViews主要用于通知欄和桌面小部件的開發。通知欄主要通過NotificationManager的notify方法來實現;桌面小部件則是通過AppWidgetProvider來實現的,AppWidgetProvider本質上是一個廣播。因為RemoteViews運行在其他進程(SystemService進程),所以無法直接更新界面。

5.1.1 RemoteViews在通知欄上的應用
    Notification notification = new Notification();
    notification.icon = R.mipmap.ic_launcher;
    notification.tickerText = "hello notification";
    notification.when = System.currentTimeMillis();
    notification.flags = Notification.FLAG_AUTO_CANCEL;
    Intent intent = new Intent(this, RemoteViewsActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);//RemoveViews所加載的布局文件
    remoteViews.setTextViewText(R.id.tv, "這是一個Test");//設置文本內容
    remoteViews.setTextColor(R.id.tv, Color.parseColor("#abcdef"));//設置文本顏色
    remoteViews.setImageViewResource(R.id.iv, R.mipmap.ic_launcher);//設置圖片
    PendingIntent openActivity2Pending = PendingIntent.getActivity
            (this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);//設置RemoveViews點擊后啟動界面
    remoteViews.setOnClickPendingIntent(R.id.tv, openActivity2Pending);
    notification.contentView = remoteViews;
    notification.contentIntent = pendingIntent;
    NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(2, notification);
5.1.2 RemoveViews在桌面小部件上的應用
  1. 定義好小部件界面
    在res/layout下新建一個xml文件,命名為widget.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="vertical">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="360dp"
        android:layout_height="360dp"
        android:layout_gravity="center" />
</LinearLayout>
  1. 定義小部件的配置信息
    在res/xml/下新建appwidget_provider_info.xml,名稱任意。
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider    xmlns:android="http://schemas.android.com/apk/res/android"
      android:initialLayout="@layout/widget"
      android:minHeight="360dp"
      android:minWidth="360dp"
      android:updatePeriodMillis="864000"/>
  1. 定義小部件的實現類
    這個類需要繼承AppWidgetProvider;我們這里實現一個簡單的widget,點擊它后,3張圖片隨機切換顯示。
public class ImgAppWidgetProvider extends AppWidgetProvider {
    public static final String TAG = "ImgAppWidgetProvider";
    public static final String CLICK_ACTION = "cn.hudp.androiddevartnote.action.click";
    private static int index;

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if (intent.getAction().equals(CLICK_ACTION)) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

            updateView(context, remoteViews, appWidgetManager);
        }
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);

        updateView(context, remoteViews, appWidgetManager);
    }

    // 由于onReceive 和 onUpdate中部分代碼相同 則抽成一個公用方法
    public void updateView(Context context, RemoteViews remoteViews, AppWidgetManager appWidgetManager) {
        index = (int) (Math.random() * 3);
        if (index == 1) {
            remoteViews.setImageViewResource(R.id.iv, R.mipmap.haimei1);
        } else if (index == 2) {
            remoteViews.setImageViewResource(R.id.iv, R.mipmap.haimei2);
        } else {
            remoteViews.setImageViewResource(R.id.iv, R.mipmap.haimei3);
        }
        Intent clickIntent = new Intent();
        clickIntent.setAction(CLICK_ACTION);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, clickIntent, 0);
        remoteViews.setOnClickPendingIntent(R.id.iv, pendingIntent);
        appWidgetManager.updateAppWidget(new ComponentName(context, ImgAppWidgetProvider.class), remoteViews);
    }
}
  1. 在AndroidManifest.xml中聲明小部件
    因為桌面小部件的本質是一個廣播組件,因此必須要注冊。
<receiver android:name=".RemoveViews_5.ImgAppWidgetProvider">
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/appwidget_provider_info">
        </meta-data>
    <intent-filter>
        <action android:name="cn.hudp.androiddevartnote.action.click" />
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
</receiver>

上面代碼中有兩個action,第一個是用于識別小部件的單擊行為,而第二個則是作為小部件的標識必須存在的;如果不加這個receiver就不是一個桌面小部件并且也無法顯示在手機的小部件中。

  1. 廣播到來的時候,AppWidgetProvider會自動根據廣播的Action通過onReceive方法來分發廣播,也就是調用
    onEnable: 當該窗口小部件第一次添加到桌面時調用的方法,可添加多次但只在第一次調用。
    onUpdate: 小部件被添加時或者每次小部件更新時都會調用一次該方法,小部件的更新時機是有updatePeriodMillis來指定,每個周期小部件就會自動更新一次。
    onDeleted: 每刪除一次桌面小部件就調用一次。
    onDisabled: 當最后一個該類型的小部件被刪除時調用該方法。
    onReceive: 這是廣播的內置方法,用于分發具體事件給其他方法。
5.1.3 PendingIntent概述

PendingIntent表示一種處于pending(待定、等待、即將發生)狀態的意圖;PendingIntent通過send和cancel方法來發送和取消特定的待定Intent。
PendingIntent支持三種待定意圖:啟動Activity、啟動Service和發送廣播。分別對應:

getActivity / getService / getBroadcast
參數相同,都為:(Context context, int requestCode, Intent intent, int flags) 

其中第二個參數,requestCode表示PendingIntent發送方的請求碼,多少情況下為0即可,requestCode會影響到flags的效果。
PendingIntent的匹配規則是:如果兩個PendingIntent他們內部的Intent相同并且requestCode也相同,那么這兩個PendingIntent就是相同的。那么什么情況下Intent相同呢?Intent的匹配規則是,如果兩個Intent的ComponentName和intent-filter都相同;那么這兩個Intent也是相同的。

flags參數的含義:
FLAG_ONE_SHOP 當前的PendingIntent只能被使用一次,然后他就會自動cancel,如果后續還有相同的PendingIntent,那么它們的send方法就會調用失敗。
FLAG_NO_CREATE 當前描述的PendingIntent不會主動創建,如果當前PendingIntent之前存在,那么getActivity、getService和getBroadcast方法會直接返回Null,即獲取PendingIntent失敗,無法單獨使用,平時很少用到。
FLAG_CANCEL_CURRENT 當前描述的PendingIntent如果已經存在,那么它們都會被cancel,然后系統會創建一個新的PendingIntent。對于通知欄消息來說,那些被cancel的消息單擊后無法打開。
FLAG_UPDATE_CURRENT 當前描述的PendingIntent如果已經存在,那么它們都會被更新,即它們的Intent中的Extras會被替換為最新的。

NotificationManager的notify方法分析

manager.notify(1,notification);
  1. 如果notify方法的id是常量,那么不管PendingIntent是否匹配,后面的通知都會替換掉前面的通知。
  2. 如果notify的方法id每次都不一樣,那么當PendingIntent不匹配的時候,不管在何種標記為下,這些通知都不會互相干擾。
  3. 如果PendingIntent處于匹配階段,分情況:
  4. 采用FLAG_ONE_SHOT標記位,那么后續通知中的PendingIntent會和第一條通知保持一致,包括其中的Extras,單擊任何一條通知后,其他通知均無法再打開;當所有通知被清除后。
  5. 采用FLAG_CANCEL_CURRENT標記位,只有最新的通知可以打開,之前彈出的所有通知均無法打開。
  6. 采用FLAG_UPDATE_CURRENT標記位,那么之前彈出的PendingIntent會被更新,最終它們和最新的一條保存完全一致,包括其中的Extras,并且這些通知都是可以打開的。

5.2 RemoteViews的內部機制

  1. RemoteViews的構造方法:public RemoteViews(String packageName,int layoutId),第一個參數表示當前應用的包名,第二個參數表示待加載的布局文件。
  2. RemoveViews并不能支持所有View類型,支持以下:
    Layout:FrameLayout、LinearLayout、RelativeLayout、GridLayout。
    View:Button、ImageButton、ImageView、ProgressBar、TextView、ListView、GridView、ViewStub等(例如EditText是不允許在RemoveViews中使用的,使用會拋異常)。
  3. RemoteView沒有findViewById方法,因此無法訪問里面的View元素,而必須通過RemoteViews所提供的一系列set方法來完成,這是通過反射調用的。
  4. 通知欄和小組件分別由NotificationManager(NM)和AppWidgetManager(AWM)管理,而NM和AWM通過Binder分別和SystemService進程中的NotificationManagerService以及AppWidgetService中加載的,而它們運行在系統的SystemService中,這就和我們進程構成了跨進程通訊。
  5. 工作流程:首先RemoteViews會通過Binder傳遞到SystemService進程,因為RemoteViews實現了Parcelable接口,因此它可以跨進程傳輸,系統會根據RemoteViews的包名等信息拿到該應用的資源;然后通過LayoutInflater去加載RemoteViews中的布局文件。接著系統會對View進行一系列界面更新任務,這些任務就是之前我們通過set來提交的。set方法對View的更新并不會立即執行,會記錄下來,等到RemoteViews被加載以后才會執行。
  6. 為了提高效率,系統沒有直接通過Binder去支持所有的View和View操作。而是提供一個Action概念,Action同樣實現Parcelable接口。系統首先將View操作封裝到Action對象并將這些對象跨進程傳輸到SystemService進程,接著SystemService進程執行Action對象的具體操作。遠程進程通過RemoteViews的apply方法來進行View的更新操作,RemoteViews的apply方法會去遍歷所有的Action對象并調用他們的apply方法。這樣避免了定義大量的Binder接口,也避免了大量IPC操作。
  7. apply和reApply的區別在于:apply會加載布局并更新界面,而reApply則只會更新界面。
  8. 關于單擊事件,RemoteViews中只支持發起PendingIntent,不支持onClickListener那種模式。setOnClickPendingIntent用于給普通的View設置單擊事件,不能給集合(ListView/StackView)中的View設置單擊事件(開銷大,系統禁止了這種方式)。如果要給ListView/StackView中的item設置單擊事件,必須將setPendingIntentTemplate和setOnClickFillInIntent組合使用才可以。

5.3 RemoteViews的意義

RemoteViews最大的意義在于方便的跨進程更新UI。

  • 當一個應用需要更新另一個應用的某個界面,我們可以選擇用AIDL來實現,但如果更新比較頻繁,效率會有問題,同時AIDL接口就可能變得很復雜。如果采用RemoteViews就沒有這個問題,但RemoteViews僅支持一些常用的View,如果界面的View都是RemoteViews所支持的,那么就可以考慮采用RemoteViews。
  • 利用RemoteViews加載其他App的布局文件與資源。
    final String pkg = "cn.hudp.remoteviews";//需要加載app的包名
    Resources resources = null;
    try {
        resources = getPackageManager().getResourcesForApplication(pkg);
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    if (resources != null) {
        int layoutId = resources.getIdentifier("activity_main", "layout", pkg); //獲取對于布局文件的id
        RemoteViews remoteViews = new RemoteViews(pkg, layoutId);
        View view = remoteViews.apply(this, llRemoteViews);//llRemoteViews是View所在的父容器
        llRemoteViews.addView(view);
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容