在前面提到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; } }