Android讀書筆記(5)—— 理解RemoteViews

一、RemoteViews基礎認知

RemoteViews是一種可以在其他進程中顯示的View,提供了一組基礎操作用于跨進程更新它的界面。主要兩個場景:通知欄和桌面小部件。

1窃躲、Notification

1.1、分類

1)普通的notification

1.內容標題
2.大圖標
3.內容
4.內容附加信息
5.小圖標
6.時間

2)大布局Notification

圖1

大布局notification是在android4.1以后才增加的钦睡,大布局notification與小布局notification只在7部分有區(qū)別蒂窒,其它部分都一致。大布局notification只有在所有notification的最上面時才會顯示大布局荞怒,其它情況下顯示小布局洒琢。你也可以用手指將其擴展為大布局(前提是它是大布局)。
大布局notification有三種類型:圖1為NotificationCompat.InboxStyle類型挣输。圖2左部為NotificationCompat.BigTextStyle纬凤。圖2右部 為:NotificationCompat.BigPictureStyle

圖2

3)自定義布局notification

1.2、基本操作

Notification的基本操作主要有創(chuàng)建撩嚼、更新停士、取消這三種挖帘。一個Notification的必要屬性有三項,如果不設置則在運行時會拋出異常:

  • 小圖標恋技,通過 setSmallIcon() 方法設置
  • 標題拇舀,通過 setContentTitle 方法設置
  • 內容,通過 setContentText() 方法設置

1)創(chuàng)建Notification

Notification.Builer: 使用建造者模式構建Notification對象蜻底。由于Notification.Builder 僅支持 Android 4.1及之后的版本骄崩,為了解決兼容性問題, Google 在 Android Support v4 中加入了 NotificationCompat.Builder 類薄辅。對于某些在 Android 4.1 之后才特性要拂,即使 NotificationCompat.Builder 支持該方法,在之前的版本中也不能運行站楚。
Notification : 通知對應類脱惰,保存通知相關的數(shù)據(jù)。NotificationManager 向系統(tǒng)發(fā)送通知時會用到窿春。
NotificationManager : NotificationManager是通知管理類拉一,它是一個系統(tǒng)服務。調用notify(int id, Notification notification)方法可以向系統(tǒng)發(fā)送通知(取消通知:cancel(int id)旧乞,id就是這么用的)蔚润。

//獲取PendingIntent
Intent mainIntent = new Intent(this, MainActivity.class);
PendingIntent mainPendingIntent = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//創(chuàng)建 Notification.Builder 對象
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
    .setSmallIcon(R.mipmap.ic_launcher)
    //點擊通知后自動清除
    .setAutoCancel(true)
    .setContentTitle("我是帶Action的Notification")
    .setContentText("點我會打開MainActivity")
    .setContentIntent(mainPendingIntent);
//發(fā)送通知
mNotifyManager.notify(3, builder.build());

2)更新Notification

更新通知很簡單,只需要再次發(fā)送相同ID的通知即可尺栖,如果之前的通知還未被取消嫡纠,則會直接更新該通知相關的屬性;如果之前的通知已經被取消决瞳,則會重新創(chuàng)建一個新通知货徙。更新通知跟發(fā)送通知使用相同的方式。

3)取消Notification

取消通知有如下 5 種方式:

  • 點擊通知欄的清除按鈕皮胡,會清除所有可清除的通知
  • 設置了 setAutoCancel() 或 FLAG_AUTO_CANCEL 的通知,點擊該通知時會清除它
  • 通過 NotificationManager 調用 cancel(int id) 方法清除指定 ID 的通知
  • 通過 NotificationManager 調用 cancel(String tag, int id) 方法清除指定 TAG 和 ID 的通知
  • 通過 NotificationManager 調用 cancelAll() 方法清除所有該應用之前發(fā)送的通知

如果你是通過 NotificationManager.notify(String tag, int id, Notification notify) 方法創(chuàng)建的通知赏迟,那么只能通過 NotificationManager.cancel(String tag, int id) 方法才能清除對應的通知屡贺,調用NotificationManager.cancel(int id) 無效。

1.3锌杀、設置Notification的通知效果

Notification 有震動甩栈、響鈴、呼吸燈三種響鈴效果糕再,可以通過 setDefaults(int defualts) 方法來設置量没。 Default 屬性有以下四種,一旦設置了 Default 效果突想,自定義的效果就會失效殴蹄。

//設置系統(tǒng)默認提醒效果究抓,一旦設置默認提醒效果,則自定義的提醒效果會全部失效袭灯。具體可看源碼
//添加默認震動效果,需要申請震動權限
//<uses-permission android:name="android.permission.VIBRATE" />
Notification.DEFAULT_VIBRATE
//添加系統(tǒng)默認聲音效果刺下,設置此值后,調用setSound()設置自定義聲音無效
Notification.DEFAULT_SOUND
//添加默認呼吸燈效果稽荧,使用時須與 Notification.FLAG_SHOW_LIGHTS 結合使用,否則無效
Notification.DEFAULT_LIGHTS
//添加上述三種默認提醒效果
Notification.DEFAULT_ALL

除了以上幾種設置 Notification 默認通知效果畅卓,還可以通過以下幾種 FLAG 設置通知效果蟋恬。

//提醒效果常用 Flag
//三色燈提醒髓介,在使用三色燈提醒時候必須加該標志符
Notification.FLAG_SHOW_LIGHTS
//發(fā)起正在運行事件(活動中)
Notification.FLAG_ONGOING_EVENT
//讓聲音、振動無限循環(huán)唐础,直到用戶響應 (取消或者打開)
Notification.FLAG_INSISTENT
//發(fā)起Notification后矾飞,鈴聲和震動均只執(zhí)行一次
Notification.FLAG_ONLY_ALERT_ONCE
//用戶單擊通知后自動消失
Notification.FLAG_AUTO_CANCEL
//只有調用NotificationManager.cancel()時才會清除
Notification.FLAG_NO_CLEAR
//表示正在運行的服務
Notification.FLAG_FOREGROUND_SERVICE
/**
* 最普通的通知效果
*/
private void showNotifyOnlyText() {
   NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setLargeIcon(mLargeIcon)
           .setContentTitle("我是只有文字效果的通知")
           .setContentText("我沒有鈴聲一膨、震動、呼吸燈,但我就是一個通知");
   mManager.notify(1, builder.build());
}

/**
* 展示有自定義鈴聲效果的通知
* 補充:使用系統(tǒng)自帶的鈴聲效果:Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");
*/
private void showNotifyWithRing() {
   NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentTitle("我是伴有鈴聲效果的通知")
           .setContentText("美妙么?安靜聽~")
           //調用系統(tǒng)默認響鈴,設置此屬性后setSound()會無效
           //.setDefaults(Notification.DEFAULT_SOUND)
           //調用系統(tǒng)多媒體褲內的鈴聲
           //.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2"));
           //調用自己提供的鈴聲豹绪,位于 /res/values/raw 目錄下
           .setSound(Uri.parse("android.resource://com.littlejie.notification/" + R.raw.sound));
   //另一種設置鈴聲的方法
   //Notification notify = builder.build();
   //調用系統(tǒng)默認鈴聲
   //notify.defaults = Notification.DEFAULT_SOUND;
   //調用自己提供的鈴聲
   //notify.sound = Uri.parse("android.resource://com.littlejie.notification/"+R.raw.sound);
   //調用系統(tǒng)自帶的鈴聲
   //notify.sound = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2");
   //mManager.notify(2,notify);
   mManager.notify(2, builder.build());
}

/**
* 展示有震動效果的通知,需要在AndroidManifest.xml中申請震動權限
* <uses-permission android:name="android.permission.VIBRATE" />
* 補充:測試震動的時候,手機的模式一定要調成鈴聲+震動模式,否則你是感受不到震動的
*/
private void showNotifyWithVibrate() {
   //震動也有兩種設置方法,與設置鈴聲一樣,在此不再贅述
   long[] vibrate = new long[]{0, 500, 1000, 1500};
   NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentTitle("我是伴有震動效果的通知")
           .setContentText("顫抖吧,凡人~")
           //使用系統(tǒng)默認的震動參數(shù),會與自定義的沖突
           //.setDefaults(Notification.DEFAULT_VIBRATE)
           //自定義震動效果
           .setVibrate(vibrate);
   //另一種設置震動的方法
   //Notification notify = builder.build();
   //調用系統(tǒng)默認震動
   //notify.defaults = Notification.DEFAULT_VIBRATE;
   //調用自己設置的震動
   //notify.vibrate = vibrate;
   //mManager.notify(3,notify);
   mManager.notify(3, builder.build());
}

/**
* 顯示帶有呼吸燈效果的通知,但是不知道為什么,自己這里測試沒成功
*/
private void showNotifyWithLights() {
   final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentTitle("我是帶有呼吸燈效果的通知")
           .setContentText("一閃一閃亮晶晶~")
           //ledARGB 表示燈光顏色瞒津、 ledOnMS 亮持續(xù)時間括尸、ledOffMS 暗的時間
           .setLights(0xFF0000, 3000, 3000);
   Notification notify = builder.build();
   //只有在設置了標志符Flags為Notification.FLAG_SHOW_LIGHTS的時候,才支持呼吸燈提醒屁柏。
   notify.flags = Notification.FLAG_SHOW_LIGHTS;
   //設置lights參數(shù)的另一種方式
   //notify.ledARGB = 0xFF0000;
   //notify.ledOnMS = 500;
   //notify.ledOffMS = 5000;
   //使用handler延遲發(fā)送通知,因為連接usb時,呼吸燈一直會亮著
   Handler handler = new Handler();
   handler.postDelayed(new Runnable() {
       @Override
       public void run() {
           mManager.notify(4, builder.build());
       }
   }, 10000);
}

/**
* 顯示帶有默認鈴聲淌喻、震動雀摘、呼吸燈效果的通知
* 如需實現(xiàn)自定義效果,請參考前面三個例子
*/
private void showNotifyWithMixed() {
   NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentTitle("我是有鈴聲+震動+呼吸燈效果的通知")
           .setContentText("我是最棒的~")
           //等價于setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE);
           .setDefaults(Notification.DEFAULT_ALL);
   mManager.notify(5, builder.build());
}

/**
* 通知無限循環(huán),直到用戶取消或者打開通知欄(其實觸摸就可以了),效果與FLAG_ONLY_ALERT_ONCE相反
* 注:這里沒有給Notification設置PendingIntent,也就是說該通知無法響應,所以只能手動取消
*/
private void showInsistentNotify() {
   NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentTitle("我是一個死循環(huán),除非你取消或者響應")
           .setContentText("啦啦啦~")
           .setDefaults(Notification.DEFAULT_ALL);
   Notification notify = builder.build();
   notify.flags |= Notification.FLAG_INSISTENT;
   mManager.notify(6, notify);
}

/**
* 通知只執(zhí)行一次,與默認的效果一樣
*/
private void showAlertOnceNotify() {
   NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
           .setSmallIcon(R.mipmap.ic_launcher)
           .setContentTitle("仔細看,我就執(zhí)行一遍")
           .setContentText("好了,已經一遍了~")
           .setDefaults(Notification.DEFAULT_ALL);
   Notification notify = builder.build();
   notify.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
   mManager.notify(7, notify);
}

/**
* 清除所有通知
*/
private void clearNotify() {
   mManager.cancelAll();
}

2阵赠、PendingIntent

PendingIntent是一種延遲的肌稻,即將發(fā)生的Intent灯萍,用于在某個特定時刻執(zhí)行特定的Action每聪。而Intent是立刻發(fā)生。
PendingIntent 是 Android 系統(tǒng)管理并持有绑洛,即便創(chuàng)建該PendingIntent對象的進程被殺死了童本,這個PendingItent對象在其他進程中還是可用的。PendingIntent通過sendcancel方法來發(fā)送和取消特定的Intent绑蔫。

PendingIntent 主要可以通過以下三種方式獲缺枚睢:

//獲取一個用于啟動Activity的PendingIntent 對象
public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags);
//獲取一個用于啟動Service的PendingIntent對象
public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags);
//獲取一個用于向BroadcastReceiver廣播的PendingIntent 對象
public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)

其中requestCode多數(shù)情況下設為0即可嫁盲,requestCode會影響flags的效果。

Intent的匹配規(guī)則

如果兩個intent的ComponentName和intent-filter相同缸托,那么這兩個intent相同瘾蛋。Extras不參與匹配過程瘦黑。

PendingIntent的匹配規(guī)則

如果兩個PendingIntent,它們內部的Intent相同且requestCode也相同,那這兩個PendingIntent就是相同的咬扇。

PendingIntent 具有以下幾種 flag:

  • FLAG_ONE_SHOT
    當前的PendingIntent只能被使用一次,然后就會被自動cancel经窖,如果后續(xù)還有相同的PendingIntent,它們的send方法會調用失敗冰悠。對于通知欄來說配乱,同類的通知只能使用一次,后續(xù)的通知將無法打開搬泥。
  • FLAG_CANCEL_CURRENT
    當前的PendingIntent如果存在(匹配的PendingIntent)忿檩,那么它們都會被cancel,然后系統(tǒng)創(chuàng)建一個新的PendingIntent沙咏。對于通知欄來說班套,那些被cancel的消息單擊后將無法打開。
  • FLAG_UPDATE_CURRENT
    當前PendingIntent如果已經存在(匹配的PendingIntent)窖壕,那么它們都會被更新瞻讽。即intent中的extras會被替換成最新的熏挎。

notify(id,notification)中,如果id是相同的常量烦磁,那么多次調用notify只能彈出一個通知哼勇,后續(xù)的通知會把前面的通知完全替代积担。而如果每次id都不同,那么會彈出多個通知先誉。
如果id每次都不同且PendingIntent不匹配,那么flags不會對通知之間造成干擾诈闺。
如果id不同且PendingIntent匹配:

  • 如果采用了FLAG_ONE_SHOT標記位雅镊,那么后續(xù)通知中的PendingIntent會和第一條通知完全一致杨帽,包括extras,單擊任何一條通知后晃危,剩下的通知均無法再打開僚饭,當所有的通知被清除后胧砰,會再次重復這一過程。
  • 如果采用FLAG_CANCEL_CURRENT偿乖,那么只有最新的通知可以打開贪薪。
  • 如果采用FLAG_UPDATE_CURRENT眠副,那么之前彈出的通知中的PendingIntent會被更新,與最新一條的通知完全一致霍弹,包括extras典格,并且這些通知都可以打開台丛。

3、RemoteViews在通知欄上的應用

我們用到自定義通知私恬,首先要提供一個布局文件本鸣,然后通過RemoteViews來加載硅蹦。更新view時,通過RemoteViews提供的一系列方法涮瞻。如果給一個控件加點擊事件署咽,要使用PendingIntent生音。

//獲取PendingIntent
Intent intent = new Intent(this, SecondActivity.class);
PendingIntent mainPendingIntent = PendingIntent.getActivity(this, 0, intent,
    PendingIntent.FLAG_UPDATE_CURRENT);
//獲取NotificationManager實例
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//創(chuàng)建RemoteViews
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
remoteViews.setTextViewText(R.id.tv_content, "這是新的文本");//更新TextView
remoteViews.setImageViewResource(R.id.iv_content, R.mipmap.tencent);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
    SecondActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);//設置點擊事件
//實例化NotificationCompat.Builder并設置相關屬性
Notification.Builder builder = new Notification.Builder(this)
    //設置小圖標缀遍,部分rom可能不生效
    .setSmallIcon(R.mipmap.tencent)
    //點擊通知后自動清除
    .setAutoCancel(true)
    //設置通知標題
    .setContentTitle("我是帶Action的Notification")
    //設置通知內容
    .setContentText("點我會打開MainActivity")
    .setContentIntent(mainPendingIntent);
    Notification notification = builder.build();
    notification.contentView = remoteViews;
    //設置通知時間域醇,默認為系統(tǒng)發(fā)出通知的時間,通常不用設置
    //.setWhen(System.currentTimeMillis());
    notifyManager.notify(1, builder.build());

4锅铅、RemoteViews在桌面小部件上的應用

AppWidgetProvider是實現(xiàn)桌面小部件的類狠角,本質是一個BroadcastReceiver

  • 定義小部件界面
    在res/layout下新建一個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/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon1" />
</LinearLayout>
  • 定義小部件配置信息
    在res/xml下新建一個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="84dp"
    android:minWidth="84dp"
    android:updatePeriodMillis="86400000" >//自動更新的周期蚪腋,單位為ms
</appwidget-provider>
  • 定義小部件實現(xiàn)類屉凯,繼承AppWidgetProvider
public class MyAppWidgetProvider extends AppWidgetProvider {
    public static final String TAG = "MyAppWidgetProvider";
    public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK";

    public MyAppWidgetProvider() {
        super();
    }

    @Override
    public void onReceive(final Context context, Intent intent) {
        super.onReceive(context, intent);
        Log.i(TAG, "onReceive : action = " + intent.getAction());

        // 這里判斷是自己的action悠砚,做自己的事情,比如小工具被點擊了要干啥绑咱,這里是做一個動畫效果
        if (intent.getAction().equals(CLICK_ACTION)) {
            Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Bitmap srcbBitmap = BitmapFactory.decodeResource(
                            context.getResources(), R.drawable.icon1);
                    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
                    for (int i = 0; i < 37; i++) {
                        float degree = (i * 10) % 360;
                        RemoteViews remoteViews = new RemoteViews(context
                                .getPackageName(), R.layout.widget);
                        remoteViews.setImageViewBitmap(R.id.imageView1,
                                rotateBitmap(context, srcbBitmap, degree));
                        Intent intentClick = new Intent();
                        intentClick.setAction(CLICK_ACTION);
                        PendingIntent pendingIntent = PendingIntent
                                .getBroadcast(context, 0, intentClick, 0);
                        remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
                        appWidgetManager.updateAppWidget(new ComponentName(
                                context, MyAppWidgetProvider.class),remoteViews);
                        SystemClock.sleep(30);
                    }

                }
            }).start();
        }
    }

    /**
     * 每次窗口小部件被點擊更新都調用一次該方法
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
            int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        Log.i(TAG, "onUpdate");

        final int counter = appWidgetIds.length;
        Log.i(TAG, "counter = " + counter);
        for (int i = 0; i < counter; i++) {
            int appWidgetId = appWidgetIds[i];
            onWidgetUpdate(context, appWidgetManager, appWidgetId);
        }

    }

    /**
     * 窗口小部件更新
     * 
     * @param context
     * @param appWidgeManger
     * @param appWidgetId
     */
    private void onWidgetUpdate(Context context,
            AppWidgetManager appWidgeManger, int appWidgetId) {

        Log.i(TAG, "appWidgetId = " + appWidgetId);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                R.layout.widget);

        // "窗口小部件"點擊事件發(fā)送的Intent廣播
        Intent intentClick = new Intent();
        intentClick.setAction(CLICK_ACTION);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                intentClick, 0);
        remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
        appWidgeManger.updateAppWidget(appWidgetId, remoteViews);
    }

    private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) {
        Matrix matrix = new Matrix();
        matrix.reset();
        matrix.setRotate(degree);
        Bitmap tmpBitmap = Bitmap.createBitmap(srcbBitmap, 0, 0,
                srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true);
        return tmpBitmap;
    }
}
  • 在AndroidManifest.xml中聲明
receiver android:name=".MyAppWidgetProvider" >
     <meta-data
         android:name="android.appwidget.provider"
         android:resource="@xml/appwidget_provider_info" >
     </meta-data>

     <intent-filter>
         <action android:name="com.ryg.chapter_5.action.CLICK" />
         <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
     </intent-filter>
 </receiver>

第一個action用于識別小部件的單擊铝噩,第二個action作為小部件的標識必須存在骏庸。
AppWidgetProvider除了onUpdate方法年叮,還有一系列方法只损。這些方法會自動被onReceive方法調用。當廣播到來以后啸蜜,AppWidgetProvider會自動根據(jù)廣播的action通過onReceive方法分發(fā)廣播辈挂。

  • onEnable:該小部件第一次添加到桌面時調用终蒂,添加多次只在第一次調用
  • onUpdate:小部件被添加或者每次小部件更新時調用,更新時機由updatePeriodMillis指定噪叙,每個周期小部件都會自動更新一次霉翔。
  • onDeleted:每刪除一次桌面小部件都會調用一次
  • onDisabled:最后一個該類型的桌面小部件被刪除時調用
  • onReceive:內置方法债朵,用于分發(fā)具體事件給以上方法

上面的例子實現(xiàn)了一個簡單地桌面小部件,在小部件上顯示一張圖片臭杰,點擊后會旋轉一周谚中。

二、RemoteViews的內部機制

1磁奖、相關方法

構造方法:public RemoteViews(String packageName,int layoutId)
第一個參數(shù)是當前應用的包名点寥,第二個參數(shù)是待加載的布局文件。

RemoteViews并不支持所有的view類型

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元素搀缠,必須通過一系列set方法完成:

  • setTextViewText(int viewId,CharSequence text)
  • setTextViewTextSize(int viewId,int units,float size)
  • setTextColor(int viewId,int color)
  • setImageViewResource(int viewId,int srcId)
  • setInt(int viewId,String methodName,int value)反射調用View對象的參數(shù)類型為Int的方法 比如上述的setImageViewResource的方法內部就是這個方法實現(xiàn) 因為srtId為int型參數(shù)
  • setLong setBoolean 類似于setInt
  • setOnClickPendingIntent(int viewId,PendingIntent pendingIntent) 添加點擊事件的方法
    大部分set方法是通過反射來完成的艺普。

2鉴竭、RemoteViews內部機制

通知欄和小組件分別由NotificationManager(NM)和AppWidgetManager(AWM)管理搏存,而NM和AWM通過Binder分別和SystemService進程中的NotificationManagerService以及AppWidgetService中加載的,而它們運行在系統(tǒng)的SystemService中缩焦,這就和我們進程構成了跨進程通訊袁滥。

首先RemoteViews會通過Binder傳遞到SystemService進程灾螃,因為RemoteViews實現(xiàn)了Parcelable接口睦焕,因此它可以跨進程傳輸,系統(tǒng)會根據(jù)RemoteViews的包名等信息拿到該應用的資源猾普;然后通過LayoutInflater去加載RemoteViews中的布局文件本谜。接著系統(tǒng)會對View進行一系列界面更新任務乌助,這些任務就是之前我們通過set來提交的。set方法對View的更新并不會立即執(zhí)行掖肋,會記錄下來赏参,等到RemoteViews被加載以后才會執(zhí)行。

為了提高效率腰涧,系統(tǒng)沒有直接通過Binder去支持所有的View和View操作衡载。而是提供一個Action概念费彼,Action同樣實現(xiàn)Parcelable接口敌买。系統(tǒng)首先將View操作封裝到Action對象并將這些對象跨進程傳輸?shù)絊ystemService進程阶界,接著SystemService進程執(zhí)行Action對象的具體操作膘融。遠程進程通過RemoteViews的apply方法來進行View的更新操作祭玉,RemoteViews的apply方法會去遍歷所有的Action對象并調用他們的apply方法脱货。這樣避免了定義大量的Binder接口,也避免了大量IPC操作臼疫。


apply和reApply的區(qū)別在于:apply會加載布局并更新界面烫堤,而reApply則只會更新界面凤价。RemoteViews在初始化界面時會調用apply方法利诺,后續(xù)更新界面調用reApply方法。

關于單擊事件立倍,RemoteViews中只支持發(fā)起PendingIntent帐萎,不支持onClickListener那種模式。setOnClickPendingIntent用于給普通的View設置單擊事件赁项,不能給集合(ListView/StackView)中的View設置單擊事件(開銷大悠菜,系統(tǒng)禁止了這種方式)败富。如果要給ListView/StackView中的item設置單擊事件兽叮,必須將setPendingIntentTemplate和setOnClickFillInIntent組合使用才可以。

三账阻、RemoteViews的意義

當一個應用需要更新另一個應用的某個界面泽本,我們可以選擇用AIDL來實現(xiàn)规丽,但如果更新比較頻繁,效率會有問題冰抢,同時AIDL接口就可能變得很復雜晒屎。如果采用RemoteViews就沒有這個問題缓升,但RemoteViews僅支持一些常用的View港谊,如果界面的View都是RemoteViews所支持的,那么就可以考慮采用RemoteViews燥狰。

參考文獻

Android Notification 詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末龙致,一起剝皮案震驚了整個濱河市目代,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌在讶,老刑警劉巖构哺,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曙强,死亡現(xiàn)場離奇詭異途茫,居然都是意外死亡慈省,警方通過查閱死者的電腦和手機眠菇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門笑窜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來登疗,“玉大人辐益,你說我怎么就攤上這事∪险郑” “怎么了续捂?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長间校。 經常有香客問我憔足,道長差购,這世上最難降的妖魔是什么欲逃? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任稳析,我火速辦了婚禮彰居,結果婚禮上,老公的妹妹穿的比我還像新娘陈惰。我一直安慰自己畦徘,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布抬闯。 她就那樣靜靜地躺著井辆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溶握。 梳的紋絲不亂的頭發(fā)上杯缺,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音睡榆,去河邊找鬼。 笑死胀屿,一個胖子當著我的面吹牛塘揣,可吹牛的內容都是我干的。 我是一名探鬼主播碉纳,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼勿负,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起奴愉,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤琅摩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锭硼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體房资,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年檀头,在試婚紗的時候發(fā)現(xiàn)自己被綠了轰异。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡暑始,死狀恐怖搭独,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情廊镜,我是刑警寧澤牙肝,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站嗤朴,受9級特大地震影響配椭,放射性物質發(fā)生泄漏。R本人自食惡果不足惜雹姊,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一股缸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吱雏,春花似錦敦姻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至得滤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盒犹,已是汗流浹背懂更。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留急膀,地道東北人沮协。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像卓嫂,于是被迫代替她去往敵國和親慷暂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容