什么是RemoteViews
RemoteViews 是一个远程 View,所谓的远程指的是这个 View 显示在其他的进程中(在该应用程序的进程之外),最常见的用法是显示在 SystemServer 进程中,例如通知栏的自定义 View 以及桌面小部件。同时,RemoteViews 表示的是一个 View 结构,它提供了一组基础的操作(set方法)用于跨进程更新它的界面。
RemoteViews的内部机制
由于RemoteViews主要用于通知栏和桌面小部件之中,这里就通过它们来分析 RemoteViews 的工作过程。我们知道,通知栏和桌面小部件分别由 NotificationManager 和 APPWidgetManager 管理,而 NotificationManager 和 APPWidgetManager 通过 Binder 分别和 SystemServer 进程中的 NotificationManagerService 和 APPWidgetService 进行通信。由此可见,通知栏和桌面小部件中的布局文件实际上是在 NotificationManagerService 和 APPWidgetService 中被加载的,而它们运行在系统的 SystemServer 中,这就和我们的进程构成了跨进程通信的场景。
1.首先 RemoteViews 会通过Binder传递到 SystemServer 进程中
RemoteViews 这个类是实现了Parcelable接口的,因此可以跨进程传输。
public class RemoteViews implements Parcelable, Filter {
2.然后系统会根据RemoteViews中包含的信息去得到该应用的资源,从而加载RemoteViews中的布局文件
比如最常见的构造函数中传入了应用程序的包名以及布局文件的资源ID:
public RemoteViews(String packageName, int layoutId) {
this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
}
3.接着系统会对View执行一系列界面更新任务
这里的更新任务也就是 RemoteViews 提供的一系列set方法,比如setTextViewText、setImageViewResource、setOnClickPendingIntent 等方法。但是set方法对View所做的更新并不是立刻执行的,在 RemoteViews 内部会有一个集合用于记录所有的更新操作(Action的集合),具体的执行时机要等到 RemoteViews 被加载以后才能执行。
private ArrayListmActions;
4.当需要更新 RemoteViews 时
先通过set方法设置需要变更的内容,然后通过 NotificationManager 和 APPWidgetManager 来提交更新任务(NotificationManager 的 notify方法以及 APPWidgetManager 的updateAppWidget方法),具体的更新操作也是在SystemServer进程中完成的。
用于记录View操作的Action
之前有说道通过set方法对View所做的更新是以集合的形式存在的(mActions)。其中,每一个Action代表一个View操作,Action同样也实现了 Parcelable 接口,同时它也是RemoteViews的内部类。
private abstract static class Action implements Parcelable {
系统首先将 View 操作封装到Action对象,并将这些对象通过集合mActions绑定在 RemoteViews 上,然后跨进程传输到远程进程,最后在远程进程中执行Action对象中的具体操作。具体点说,就是在我们的应用中每调用一次 set 方法,RemoteViews 的mActions 集合中就会新增一个Action个对象,然后当我们通过 NotificationManager 和 APPWidgetManager 提交更新时,这些Action对象就会传输到远程进程中,最后远程进程通过调用 RemoteViews 的 apply 方法来进行View的更新操作。
RemoteViews 的 apply 方法内部则会去遍历所有的 Action 对象并调用它们的 apply 方法,具体的 View 更新操作是由 Action 对象的 apply 方法来完成的。
从源码角度分析RemoteViews的工作流程
首先看一下RemoteViews的set系列方法,这里看一个设置TextView文本的方法,如下:
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
可以看到其调用了setCharSequence方法,并额外传入了一个参数“setText”,即方法名,viewId则是需要更新的 View 的 id,text则是要给TextView设置的具体文本。然后我们再去看setCharSequence方法内部:
public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
可以看到,它的内部并没有直接对View进行操作,而是添加了一个Action对象(ReflectionAction)。再来看看addAction的实现:
private void addAction(Action a) {
......
if (mActions == null) {
mActions = new ArrayList();
}
mActions.add(a);
// update the memory usage stats
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
从上述代码可以知道,在RemoteViews内部有一个ArrayList成员变量mActions。外界每调用一次 set 方法,RemoteViews 就会为其创建一个Action对象并添加到这个集合中。一个 set 方法的流程到这里就结束了,但值得注意的是, 到目前为止,系统仍未对View进行实际操作。
接下来我们再看 RemoteViews 的apply方法,如下:
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result = inflateView(context, rvToApply, parent);
loadTransitionOverride(context, handler);
rvToApply.performApply(result, parent, handler);
return result;
}
从上面代码可以看出,首先会通过 inflateView 方法去加载 RemoteViews 中的布局文件,然后通过 performApply 去执行一些更新操作。其中 inflateView 方法的代码如下:
private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
final Context contextForResources = getContextForResources(context);
Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources);
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
View v = inflater.inflate(rv.getLayoutId(), parent, false);
v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
return v;
}
可以看到,它是通过 RemoteViews 的 getLayoutId 方法获取的布局ID值。然后 performApply 方法的代码如下:
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}
可以看到,它的作用就是遍历 mActions 这个集合并执行每个 Action 对象的 apply 方法。
看到这里之后,我们再回过头来分析,RemoteViews 在通知栏和桌面小工具中的工作过程中,我们先调用 RemoteViews 的 set 方法时,并不会立刻更新它们的界面,而必须通过 NotificationManager 的 notify 方法以及 AppWidgetManager 的 updateAppWidget 才能更新它们的界面。是因为在 notify 和 updateAppWidget 的内部实现中是调用了RemoteViews的 apply 以及 reapply 方法来加载或者更新界面的。其中 reapply 与 apply 的区别在于:apply 会加载布局并更新界面,而 reapply 则只会更新界面。