Android開(kāi)發(fā)五《理解RemoteViews》

RemoteViews表示的是一個(gè)View結(jié)構(gòu),它可以在其他進(jìn)程中顯示,它提供了一組基礎(chǔ)的操作用于跨進(jìn)程更新它的界面.


1.png

支持的布局:

  • AdapterViewFlipper
  • FrameLayout
  • GridLayout
  • GridView
  • LinearLayout
  • ListView
  • RelativeLayout
  • StackView
  • ViewFlipper
    支持的控件:
  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextClock
  • TextView

一券盅、RemoteViews的應(yīng)用

使用場(chǎng)景:
1辫秧、通知欄:通過(guò)NotiicationManager的notify方法實(shí)現(xiàn)

 Intent intent = new Intent(this, NotiActivity.class);
 PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

 String id = "my_channel_01";
 CharSequence name = "channel";
 String description = "description";
 int importance = NotificationManager.IMPORTANCE_DEFAULT;
 NotificationChannel mChannel = new NotificationChannel(id, name, importance);

 mChannel.setDescription(description);
 mChannel.enableLights(true);
 mChannel.setLightColor(Color.RED);
 mChannel.enableVibration(true);
 mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});

 NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
 manager.createNotificationChannel(mChannel);


 RemoteViews remoteView = new RemoteViews(getPackageName(), R.layout.layout_notification);
 remoteView.setTextColor(R.id.re_text, Color.RED);
 remoteView.setTextViewText(R.id.re_text, "remote view demo");
 remoteView.setImageViewResource(R.id.re_image, R.drawable.btn_me_share);
 remoteView.setOnClickPendingIntent(R.id.notification, pendingIntent);

 Notification notification = new Notification.Builder(this, id)
 .setAutoCancel(false)
 .setContentTitle("title")
 .setContentText("describe")
 .setContentIntent(pendingIntent)
 .setSmallIcon(R.drawable.btn_me_share)
 .setOngoing(true)
 .setCustomContentView(remoteView)
 .setWhen(System.currentTimeMillis())
 .build();
 manager.notify(1, notification);

從上面代碼發(fā)現(xiàn)懈万,RemoteViews的方法使用起來(lái)很簡(jiǎn)單。利用構(gòu)造函數(shù)new RemoteViews(packagename, layoutId) 來(lái)關(guān)聯(lián)一個(gè)view的布局,并通過(guò)一些set 方法更新布局,最后利用notification.Builder().setCustomContentView(RemoteViews) 來(lái)設(shè)置通知欄的view

2、桌面小部件:通過(guò)AppWidgetProvider(本質(zhì)是一個(gè)廣播)來(lái)實(shí)現(xiàn);
桌面小部件主要是利用RemoteViews和AppWidgetProvider結(jié)合使用,而AppWidgetProvider又是extends BroadcastReceiver, 所以再使用的時(shí)候拖叙,多了一些關(guān)于廣播的知識(shí)。

public class NewAppWidget extends AppWidgetProvider {
  private static final String CLICK_ACTION = "com.taohuahua.action.click";

  static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) {
    final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget);

    Intent anIntent = new Intent();
    anIntent.setAction(CLICK_ACTION);
    PendingIntent anPendingIntent = PendingIntent.getBroadcast(context, 0, anIntent, 0);
    views.setOnClickPendingIntent(R.id.appwidget_text, anPendingIntent);
    appWidgetManager.updateAppWidget(appWidgetId, views);
  }

  @Override
 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    for (int appWidgetId : appWidgetIds) {
      updateAppWidget(context, appWidgetManager, appWidgetId);
    }
}

  @Override
  public void onEnabled(Context context) {
  // Enter relevant functionality for when the first widget is created
  }

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

  @Override
  public void onReceive(Context context, Intent intent) {
    super.onReceive(context, intent);
    final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget);

    if (Objects.equals(intent.getAction(), CLICK_ACTION)) {
    Toast.makeText(context, "hello world", Toast.LENGTH_SHORT).show();

    //獲得appwidget管理實(shí)例赂乐,用于管理appwidget以便進(jìn)行更新操作
    AppWidgetManager manger = AppWidgetManager.getInstance(context);
    // 相當(dāng)于獲得所有本程序創(chuàng)建的appwidget
    ComponentName thisName = new ComponentName(context, NewAppWidget.class);
    //更新widget
    manger.updateAppWidget(thisName, views);
  }
}

從代碼中可以看出薯鳍,里面有幾個(gè)重要的方法:

  1. onUpdate: 小部件被添加時(shí)或者每次更新時(shí)調(diào)用。更新時(shí)間由第二步配置中updatePeriodMills來(lái)決定挨措,單位為毫秒挖滤。
  2. onReceive: 廣播內(nèi)置方法,用于分發(fā)接收到的事件浅役。
  3. onEnable: 當(dāng)該窗口小部件第一次添加時(shí)調(diào)用斩松。
  4. onDelete:每刪除一次調(diào)用一次。
  5. onDisabled:最后一個(gè)該桌面小部件被刪除時(shí)調(diào)用觉既。
    所以在onUpdate方法中利用RemoteViews來(lái)顯示了新的布局惧盹,并利用pendingIntent來(lái)實(shí)現(xiàn)點(diǎn)擊小部件控件跳轉(zhuǎn)的方法。

二瞪讼、RemoteViews的內(nèi)部機(jī)制

RemoteView的作用是在其他進(jìn)程中顯示并更新View的界面,最常用的構(gòu)造方法:

public RemoteViews(String packageName,int layoutId)

RemoteViews沒(méi)有提供findViewById方法,必須通過(guò)RemoteViews所提供的一系列set方法來(lái)完成更新;這些set方法通過(guò)反射來(lái)完成的.


RemoteViews的部分set方法

通知欄以及小部件分別由NotificationManager和AppWidgetManager管理钧椰,而NotificationManager以及AppWidgetManager通過(guò)Binder分別和SystemServer進(jìn)行中的NotificationManagerService以及AppWidgetService進(jìn)行通信,因此符欠,通知欄以及桌面小部件中的布局文件是在NotificationManagerService以及AppWidgetService中被加載的嫡霞,而它們運(yùn)行在系統(tǒng)的systemServer中,這就和我們的進(jìn)行構(gòu)成了跨進(jìn)程通信的場(chǎng)景希柿。
這里沒(méi)有使用Binder進(jìn)行進(jìn)程通信诊沪,由于View的方法太多大量的IPC操作會(huì)影響效率养筒,這里提供了Action的概念,Action代表一個(gè)View的操作端姚,系統(tǒng)將Action操作封裝到Action對(duì)象并將這些對(duì)象跨進(jìn)程傳輸?shù)竭h(yuǎn)程進(jìn)程中闽颇,接著直接Action對(duì)象中的Action操作。我們使用RemoteViews時(shí)寄锐,每調(diào)用一個(gè)set方法,就會(huì)添加一個(gè)Action對(duì)象尖啡,當(dāng)我們通過(guò)NotificationManager和AppWidgetManager提交更新時(shí)橄仆,這些Action對(duì)象就會(huì)傳輸?shù)竭h(yuǎn)程進(jìn)程中并依次執(zhí)行。


內(nèi)部機(jī)制

在RemoteViews的源碼中衅斩,可以看到定義了一個(gè)Action對(duì)象的列表

 /**
 * An array of actions to perform on the view tree once it has been
 * inflated
 */
 private ArrayList<Action> mActions;</pre>

而Action的是對(duì)遠(yuǎn)程視圖進(jìn)行的操作的一個(gè)封裝盆顾。因?yàn)槲覀儫o(wú)法通過(guò)RemoteViews的findViewById方法來(lái)操作視圖,所以RemoteViews每次視圖的操作都會(huì)創(chuàng)建一個(gè)action對(duì)象添加到列表中畏梆。

/**
 * Base class for all actions that can be performed on an
 * inflated view.
 *
 *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
 */
 private abstract static class Action implements Parcelable {
   public abstract void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException;

   public static final int MERGE_REPLACE = 0;
   public static final int MERGE_APPEND = 1;
   public static final int MERGE_IGNORE = 2;

   public int describeContents() {
     return 0;
   }

   /**
   * Overridden by each class to report on it's own memory usage
   */
   public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
    // We currently only calculate Bitmap memory usage, so by default,
    // don't do anything here
   }

   public void setBitmapCache(BitmapCache bitmapCache) {
   // Do nothing
   }

   public int mergeBehavior() {
    return MERGE_REPLACE;
   }

   public abstract String getActionName();

   public String getUniqueKey() {
     return (getActionName() + viewId);
   }

   /**
     * This is called on the background thread. It should perform any non-ui computations
   * and return the final action which will run on the UI thread.
   * Override this if some of the tasks can be performed async.
   */
   public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
     return this;
   }

   public boolean prefersAsyncApply() {
     return false;
   }

   /**
   * Overridden by subclasses which have (or inherit) an ApplicationInfo instance
   * as member variable
   */
   public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
     return true;
   }
  
   int viewId;
 }

從源碼中可以看出您宪,action提供了一個(gè)抽象的方法

 public abstract void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException;

在RemoteViews.java中發(fā)現(xiàn)了有很多Action的子類, 這里重點(diǎn)講解一個(gè)類

 /**
 * Base class for the reflection actions.
 */
 private final class ReflectionAction extends Action {
 ...
 }

因?yàn)楹芏喔乱晥D的方法最后都走到

addAction(new ReflectionAction(viewId, methodName, type, value));

可以發(fā)現(xiàn),當(dāng)RemoteViews通過(guò)set方法來(lái)更新一個(gè)視圖時(shí)奠涌,并沒(méi)有立即更新宪巨,而是添加到action列表中。這樣可以大大提高跨進(jìn)程通信的性能溜畅,至于什么時(shí)候更新捏卓,對(duì)于自定義通知欄,需要NotificationManager調(diào)用notify()之后慈格;而對(duì)于桌面小部件怠晴,則需要AppWidgetManager調(diào)用updateAppWidget()之后。
最后進(jìn)入ReflectionAction類中的apply方法看一下浴捆,發(fā)現(xiàn)內(nèi)部就是利用反射機(jī)制設(shè)置相關(guā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);
 }
}

注意:當(dāng)我們調(diào)用RemoteViews的set方法時(shí),并不會(huì)立刻更新它們的界面选泻,必須通過(guò)NotificationManager的notify方法以及AppWidgetManager的updateAppWidget方法才會(huì)更新他們的界面冲粤。

三、PendingIntent概述

peddingIntent表示一種處于pending狀態(tài)的意圖滔金,pending狀態(tài)表示一種待定色解、等待、即將發(fā)生餐茵。就是說(shuō)接下來(lái)有一個(gè)Intent將要在某個(gè)待定的時(shí)刻發(fā)生科阎。PendingIntent和Intent的區(qū)別在于,PendingIntent是在將來(lái)某個(gè)時(shí)刻發(fā)生忿族,而Intent是立刻發(fā)生锣笨。PendingIntent典型的使用場(chǎng)景是給RemoteViews添加單機(jī)事件蝌矛。由于RemoteViews運(yùn)行在遠(yuǎn)程進(jìn)程中,無(wú)法直接調(diào)用setOnClickListener方法來(lái)設(shè)置單擊事件错英,就需要使用pendingIntent入撒,PendingIntent通過(guò)send和cancel來(lái)發(fā)送和取消待定的Intent。

PendingIntent支持的三種待定意圖:?jiǎn)?dòng)Activity椭岩、啟動(dòng)Service以及發(fā)送廣播


PendingIntent的主要方法

這三個(gè)方法的參數(shù)意義是相同的,第一個(gè)和第三個(gè)參數(shù)比較好理解,主要是第二個(gè)和第四個(gè);
第二個(gè)參數(shù)requestCode茅逮,requestCode表示PendingIntent方的請(qǐng)求碼,多數(shù)情況設(shè)為0即可判哥。requestCode會(huì)影響到flags的效果.
第四個(gè)參數(shù)flags有四種類型:

  1. FLAG_ONE_SHOT
    PendingIntent只被使用一次献雅,然后被自動(dòng)cancel,后續(xù)如果還有相同的PendIntent塌计,那么它們的send方法調(diào)用失敗挺身。對(duì)通知欄消息來(lái)說(shuō),同類的通知只會(huì)使用一次,后續(xù)的通知單擊后將無(wú)法打開(kāi)。
  2. FLAG_NO_CREATE
    當(dāng)前描述的PendIntent不會(huì)主動(dòng)創(chuàng)建,如果當(dāng)前PendIntent之前不存在,那么getActivity,getService,getBroadcast方法會(huì)直接返回null,即獲取PendingIntent失敗锌仅。
  3. FLAG_CANCEL_CURRENT
    當(dāng)前描述的PendIntent如果已經(jīng)存在章钾,那么它們會(huì)被cancel,然后系統(tǒng)創(chuàng)建一個(gè)新的PendingIntent热芹。對(duì)通知欄消息來(lái)說(shuō),那些被cancel的消息單擊后將無(wú)法打開(kāi)贱傀。
  4. FLAG_UPDATE_CURRENT
    當(dāng)前描述的PendIntent如果已經(jīng)存在,那么它們都會(huì)被更新。即它們的Intent中的Extras會(huì)被替換成最新的伊脓。

PendingIntent的匹配規(guī)則為:
如果兩個(gè)PendingIntent它們內(nèi)部的Intent相同并且 requestCode也相同則相同窍箍。
Intent的匹配規(guī)則為:
如果兩個(gè)Intent的ComponentName和intent-filter都相同,則它們兩個(gè)就是相同的。

通過(guò)通知欄消息理解這四個(gè)標(biāo)記,分兩種情況
manager.notify(1,notification)
1丽旅、通知ID是常量
多次調(diào)用notify只能彈出一個(gè)通知,后續(xù)的通知會(huì)把前面的通知完全替代掉;
2椰棘、通知ID是變量
多次調(diào)用notify會(huì)彈出多個(gè)通知;這也分兩種情況

    1. PendingIntent不匹配
      不管采用何種標(biāo)記位,這些通知之間不會(huì)相互干擾;
    1. PendingIntent匹配狀態(tài),標(biāo)記位為:
      FLAG_ONE_SHOT:后續(xù)通知中的PendingIntent會(huì)和第一條通知保持完全一致,包括其中的Extras,單擊任何一條通知后,剩下的通知均無(wú)法再打開(kāi),當(dāng)所有的通知都被清除后,會(huì)再次重復(fù)這個(gè)過(guò)程
      FLAG_CANCEL_CURRENT:只有最新的通知可以打開(kāi),之前彈出的所有通知均無(wú)法打開(kāi);
      FLAG_UPDATE_CURRENT:之前彈出的通知中的PendingIntent會(huì)被更新,最終它們和最新一條通知保持完全一致,包括其中的Extras,并且這些通知都是可以打開(kāi)的.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榄笙,隨后出現(xiàn)的幾起案子邪狞,更是在濱河造成了極大的恐慌,老刑警劉巖茅撞,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帆卓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡米丘,警方通過(guò)查閱死者的電腦和手機(jī)剑令,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拄查,“玉大人吁津,你說(shuō)我怎么就攤上這事《榉觯” “怎么了碍脏?”我有些...
    開(kāi)封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵梭依,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我典尾,道長(zhǎng)役拴,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任钾埂,我火速辦了婚禮河闰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘褥紫。我一直安慰自己淤击,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布故源。 她就那樣靜靜地躺著,像睡著了一般汞贸。 火紅的嫁衣襯著肌膚如雪绳军。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天矢腻,我揣著相機(jī)與錄音门驾,去河邊找鬼。 笑死多柑,一個(gè)胖子當(dāng)著我的面吹牛奶是,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播竣灌,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼聂沙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了初嘹?” 一聲冷哼從身側(cè)響起及汉,我...
    開(kāi)封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屯烦,沒(méi)想到半個(gè)月后坷随,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驻龟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年温眉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翁狐。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡类溢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出露懒,到底是詐尸還是另有隱情豌骏,我是刑警寧澤龟梦,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站窃躲,受9級(jí)特大地震影響计贰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒂窒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一访娶、第九天 我趴在偏房一處隱蔽的房頂上張望破托。 院中可真熱鬧,春花似錦、人聲如沸件炉。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)香伴。三九已至,卻和暖如春呛踊,著一層夾襖步出監(jiān)牢的瞬間砾淌,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工谭网, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留汪厨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓愉择,卻偏偏與公主長(zhǎng)得像劫乱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锥涕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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