RemoteViews表示的是一個(gè)View結(jié)構(gòu),它可以在其他進(jìn)程中顯示,它提供了一組基礎(chǔ)的操作用于跨進(jìn)程更新它的界面.
支持的布局:
- 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è)重要的方法:
- onUpdate: 小部件被添加時(shí)或者每次更新時(shí)調(diào)用。更新時(shí)間由第二步配置中updatePeriodMills來(lái)決定挨措,單位為毫秒挖滤。
- onReceive: 廣播內(nèi)置方法,用于分發(fā)接收到的事件浅役。
- onEnable: 當(dāng)該窗口小部件第一次添加時(shí)調(diào)用斩松。
- onDelete:每刪除一次調(diào)用一次。
- 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)完成的.
通知欄以及小部件分別由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í)行。
在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ā)送廣播
這三個(gè)方法的參數(shù)意義是相同的,第一個(gè)和第三個(gè)參數(shù)比較好理解,主要是第二個(gè)和第四個(gè);
第二個(gè)參數(shù)requestCode茅逮,requestCode表示PendingIntent方的請(qǐng)求碼,多數(shù)情況設(shè)為0即可判哥。requestCode會(huì)影響到flags的效果.
第四個(gè)參數(shù)flags有四種類型:
- FLAG_ONE_SHOT
PendingIntent只被使用一次献雅,然后被自動(dòng)cancel,后續(xù)如果還有相同的PendIntent塌计,那么它們的send方法調(diào)用失敗挺身。對(duì)通知欄消息來(lái)說(shuō),同類的通知只會(huì)使用一次,后續(xù)的通知單擊后將無(wú)法打開(kāi)。 - FLAG_NO_CREATE
當(dāng)前描述的PendIntent不會(huì)主動(dòng)創(chuàng)建,如果當(dāng)前PendIntent之前不存在,那么getActivity,getService,getBroadcast方法會(huì)直接返回null,即獲取PendingIntent失敗锌仅。 - FLAG_CANCEL_CURRENT
當(dāng)前描述的PendIntent如果已經(jīng)存在章钾,那么它們會(huì)被cancel,然后系統(tǒng)創(chuàng)建一個(gè)新的PendingIntent热芹。對(duì)通知欄消息來(lái)說(shuō),那些被cancel的消息單擊后將無(wú)法打開(kāi)贱傀。 - 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è)通知;這也分兩種情況
- PendingIntent不匹配
不管采用何種標(biāo)記位,這些通知之間不會(huì)相互干擾;
- PendingIntent不匹配
- 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)的.
- PendingIntent匹配狀態(tài),標(biāo)記位為: