RemoteViews用于跨进程更新View,常用使用场景有:通知栏和桌面widget。
通知栏的简单使用:
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, SecondActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews remoteViews = new RemoteViews(this.getPackageName(),R.layout.notify_content);
Notification notification = builder.setTicker("Ticker")
.setOngoing(true)
.setTicker("Ticker",remoteViews)
.setSmallIcon(R.drawable.ic)
.setContentText("ContentText")
.setWhen(System.currentTimeMillis())
.setContentTitle("ContentTitle")
.setContentIntent(intent)
.build();
remoteViews.setTextViewText(R.id.textView,"IIIIIIIIIIIIIII");
notification.contentView = remoteViews;
notificationManager.notify(0,notification);
RemoteView没有像普通View那样有着findViewById方法,只提供了特定的方法来更新,例如 SetTextViewText(R.id.text,”Text”) 后面说明原因。
AppWigetProvider实质是一个广播,更新Wiget也用到了RemoteViews,使用步骤:
<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>
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/my_app_widget"
android:initialLayout="@layout/my_app_widget"
android:minHeight="40dp"
android:minWidth="40dp"
android:previewImage="@drawable/example_appwidget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen" />
public class MyAppWidget extends AppWidgetProvider {
private static final String TAG = "MyAppWidget";
static int index = 0;
void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);
views.setTextViewText(R.id.appwidget_text, "Start");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,new Intent("xml.onclick"),PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_text,pendingIntent);
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
//widget更新就会调用一次,包括添加以及自动
Log.e(TAG,"onUpdate,appWidgetIds = " + Arrays.toString(appWidgetIds));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onEnabled(Context context) {
//第一个widget开启就会被调用
Log.e(TAG,"onEnabled");
}
@Override
public void onDisabled(Context context) {
//最后一个widget被删除就会被调用
Log.e(TAG,"onDisabled");
}
@Override
public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
Log.e(TAG,"onRestored,oldWidgetIds = " + Arrays.toString(oldWidgetIds) + ",newWidgetIds = " + Arrays.toString(newWidgetIds));
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
//每删除一个widget就会调用一次
Log.e(TAG,"onDeleted,appWidgetIds = " + Arrays.toString(appWidgetIds));
}
@Override
public void onReceive(Context context, Intent intent) {
//必须调用父类的方法用于分发
super.onReceive(context, intent);
Log.e(TAG, "onReceive");
if("xml.onclick".equals(intent.getAction())) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,new Intent("xml.onclick"),PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_text,pendingIntent);
views.setTextViewText(R.id.appwidget_text, "Index = " + index++);
AppWidgetManager.getInstance(context).updateAppWidget(new ComponentName(context,MyAppWidget.class),views);
}
}
}
<receiver android:name="xml.MyAppWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="xml.onclick" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver>
功能很简单,就是添加了widget之后,点击widget就会不断地更新wiget上面的文字,我们也可以通过在Activity或者Service里面发广播去更新widget。
AndroidStudio 可以直接新建AppWiget并给我们创建模版,上面代码就是 AndroidStudio 给生成后添加的一小段逻辑,如果要着手开发Wiget的话,建议详细阅读官方的教程。
PendingIntent可以理解为即将到来的Intent,不是即时发生的,就像上面代码里面给 RemoteViews 设置点击事件,点击之后就发送一个广播。通过 cancel 方法来取消PendingIntent。
PendingIntent支持三种意图: 启动Activity , 启动Service ,发送广播,对应的方法 getActivity,getService,getBroadcast,并且都有4个参数, (Context context,int requestCode,Intent intent,flags) 。
PendingIntent匹配规则为内部的Intent(只比较CompontName和intent-filter,不包括Extras)和requestCode相同即可。
flags的可选参数:
对于NotifycationManget#notify(int id, Notification notification), 如果第一个参数是一个常量,多次调用notify只能弹出一个通知,后续的通知就会被替换掉,如果不是常量,多次调用就会弹出多个通知。
A class that describes a view hierarchy that can be displayed in another process. The hierarchy is inflated from a layout resource file, and this class provides some basic operations for modifying the content of the inflated hierarchy.
目前RemoteViews支持的类型:(不支持自定义View的他们的子类)
Layout
FrameLayout LineraLayout RelativeLayout GridLayout
View
AnalogClock Button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper ViewStub
使用其他的不支持的控件会抛InflateException。
RemoteViews的典型方法:
setTextView(int viewId,Charsquence text); //设置TextView的文本
setInt(int viewId,String methodName,int value);//反射调用View对象参数为int类型的方法
setOnClickPendingIntent(int viewId,PendingIntent pendingIntent);//为View添加单击事件,事件类型只能为PendingIntent
我们先看一下updateWidget的调用过程:
MyAppWidget.java:
void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);
views.setTextViewText(R.id.appwidget_text, "Start");
appWidgetManager.updateAppWidget(appWidgetId, views);
}
RemoteViews.java:
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
private void addAction(Action a) {
//...
if (mActions == null) {
mActions = new ArrayList<Action>();
}
mActions.add(a);
//...
}
AppWidgetManager.java:
private final IAppWidgetService mService;
public void updateAppWidget(int appWidgetId, RemoteViews views) {
if (mService == null) {
return;
}
updateAppWidget(new int[] { appWidgetId }, views);
}
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
if (mService == null) {
return;
}
try {
mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
可以看得出来我们调用set方法给RemoteViews更新的时候,并没有真正的更新,而是把我们的操作添加到了一个 Action 的List里面,并且Action与RemoteViews都实现了Parcelable接口,就是用来进程间通信的。真正更新的的操作就是调用IAppWidgetService服务的操作,Notification也是利用这种类似的机制。
Notification和Widget是由NotificationManager和AppWidgetManager管理的,而NotificationManager和AppWidgetManager内部通过Binder机制(调用Service)和运行在SystemServer进程的NotifycationManagerService和AppWidgetService进行交互,完成我们的界面加载和更新。实质上我们定义的Notification和Widget在被SystemService加载和更新的。
这种批量更新的方式减轻了IPC的负担,因为我们很有可能一次更新View的很多内容。
上面只是介绍了客户端的基本调用行为,服务端则会调用apply或reapply来批量我们的操作并使用反射调用我们set的方法来更新我们的View,书中讲的不是很透彻,需要深入研究可以参考这几篇博客。
apply会加载布局并更新界面,reapply则只会更新界面
RemoteViews的其他用途就是进程间更新View,例如Service和Activity不在同一个进程,有Service更新Activity的需求,就可以使用RemoteViews,感觉实际开发中不是很常用,具体参考作者的示例代码, 核心就是通过Intent的Extras存储我们的添加Action的RemoteViews,在接收方获取这个对象,调用apply或reapply就可以得到或者更新View。