在前面提到app widget的添加流程,最后一步为实例化一个AppWidgetHostView然后添加到Launcher中,我们重点看一下AppWidgetHost.createView方法,代码大致如下:
/**
* Create the AppWidgetHostView for the given widget.
* The AppWidgetHost retains a pointer to the newly-created View.
*/
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
final int userId = mContext.getUserId();
AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
view.setUserId(userId);
view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
mViews.put(appWidgetId, view);
}
RemoteViews views;
try {
views = sService.getAppWidgetViews(appWidgetId, userId);
if (views != null) {
views.setUser(new UserHandle(mContext.getUserId()));
}
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
view.updateAppWidget(views);
return view;
}
AppWidgetHostView实质是一个FrameLayout,在实例化该FrameLayout后,在其中记录了appWidget id和provider等信息。接下来取得该插件的RemoteViews对象然后更新数据。
该插件的RemoteViews对象来自何处?回顾上一篇中bind appWidget id时AppWidgetService会发送广播AppWidgetManager.ACTION_APPWIDGET_UPDATE,此时provider端应用会接收广播然后设定RemoteViews,代码大致如下:
// 1. 实例化RemoteViews
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.notes_appwidget);
// 2. 为新建按钮设定点击行为
Intent intent = null;
intent = new Intent("com.example.note.NoteEditActivity");
views.setOnClickPendingIntent(R.id.new_note, PendingIntent.getActivity(context, 0, intent, 0));
for(int appWidgetId : appWidgetIds){
appWidgetManager.updateAppWidget(appWidgetId, views);
}
其中PendingIntent是一个Parcelable对象,用于保存后续会执行的Intent。具体的介绍参见说说PendingIntent的内部机制 。
RemoteViews也是一个Parcelable对象,保存对应的layout id以及作用在view上的action,这些都可以通过Binder通信传递到AppWidgetService中。
重点关注最后一个appWidgetManager.updateAppWidget(appWidgetId, views)方法。在AppWidgetServiceImpl中的实现如下:
public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
final int N = appWidgetIds.length;
synchronized (mAppWidgetIds) {
ensureStateLoadedLocked();
for (int i = 0; i < N; i++) {
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
updateAppWidgetInstanceLocked(id, views);
}
}
}
void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
updateAppWidgetInstanceLocked(id, views, false);
}
void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) {
// allow for stale appWidgetIds and other badness
// lookup also checks that the calling process can access the appWidgetId
// drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
if (!isPartialUpdate) {
// For a full update we replace the RemoteViews completely.
id.views = views;
} else {
// For a partial update, we merge the new RemoteViews with the old.
id.views.mergeRemoteViews(views);
}
// is anyone listening?
if (id.host.callbacks != null) {
try {
// the lock is held, but this is a oneway call
id.host.callbacks.updateAppWidget(id.appWidgetId, views, mUserId);
} catch (RemoteException e) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
id.host.callbacks = null;
}
}
}
}
先找到该id对应的AppWidgetId对象,然后
更新对应的RemoteViews,如果之前有调用AppWidgetHost.startListening,则会回调AppWidgetHost.mCallback的updateAppWidget方法。
继续跟踪到AppWidgetHost$Callback可以发现最终调用的为AppWidgetHostView.updateAppWidget方法,不过此时对应的AppWidgetHostView应该为null。
可以看到,在添加widget过程的bind appWidget这一步,provider端就在接收update广播时将RemoteViews对象传给了AppWidgetService,AppWidgetService也会通知AppWidgetHost更新HostView,不过此时HostView尚为null。之后回到本文开头,调用AppWidgetHostView.createView生成AppWidgetHostView时会调用AppWidgetHostView.updateAppWidget(views),参数正是之前provider提供给AppWidgetService的。
具体看一下这个updateAppWidget方法
/**
* Process a set of {@link RemoteViews} coming in as an update from the
* AppWidget provider. Will animate into these new views as needed
*/
public void updateAppWidget(RemoteViews remoteViews) {
boolean recycled = false;
View content = null;
Exception exception = null;
if (remoteViews == null) {
...
} else {
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
mRemoteContext = getRemoteContext(remoteViews);
int layoutId = remoteViews.getLayoutId();
// Try normal RemoteView inflation
if (content == null) {
try {
content = remoteViews.apply(mContext, this, mOnClickHandler);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}
mLayoutId = layoutId;
mViewMode = VIEW_MODE_CONTENT;
}
if (!recycled) {
prepareView(content);
addView(content);
}
if (mView != content) {
removeView(mView);
mView = content;
}
}
其中remoteViews.apply方法会inflate view并将之前定义的Action都apply,即设定view的各种属性,包括listener,此处不展开。