app widget 显示流程

在前面提到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,此处不展开。



猜你喜欢

转载自blog.csdn.net/w_xue/article/details/43341087