AppWidget源码分析---updateAppWidget过程分析.md

Posted _houzhi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AppWidget源码分析---updateAppWidget过程分析.md相关的知识,希望对你有一定的参考价值。

前面一篇文章,分析了AppWidgetProvider和RemoteView的源码,从中我们可以知道它们的实现原理,AppWidgetProvider是一个BroadcastReceiver,所以它是通过广播接收通知的,收到更新通知后,AppWidgetProvider需要去提供View供远程进程显示,而提供的View则是使用RemoteView来代替,通过RemoteView(是一个Parcelable,可跨进程传输数据类型)来作为媒介去传递给远程进程。由远程进程解析RemoteView,然后显示RemoteView表示的真正的View。在这篇文章将分析这个传输过程。

流程

还是先看一下上一篇给出的一个过程图示:

实际上,AppWidgetProvider是通过AppWidgetManager来更新View的,而AppWidgetManager里面是有一个IAppWidgetService,一看就知道这是一个idle生成的,是一个Binder通信。关于Binder通信我之前也写过一篇文章叫Android源码代理模式—Binder,可以参考一下。而服务端的AppWidgetService是AppWidgetServiceImpl,AppWidgetServiceImpl又会通过一个IAppWidgetHost来跨进程通知AppWidgetHost,AppWidgetHost内部的IAppWidgetHost是AppWidgetHost.Callback。这样就到了显示我们的AppWidget的进程(大部分是Launcher应用)。

分清楚每个部分是在什么进程运行的对于理解整个流程是非常有帮助的,AppWidgetProvider是在我们自己的应用程序进程当中,而AppWidgetService是运行在SystemServer进程,AppWidgetHost则是运行在显示AppWidget的进程中,比如Launcher应用(桌面)。整个流程相当于是跨越了三个进程。

updateAppWidgetIds过程分析

这是AppWidgetManager的一个接口函数,根据id来更新AppWidget。先把整个更新过程的时序图拿出来看一下:

从我们普通的调用AppWidgetManager的updateAppWidget看起吧:

    appWidgetManager.updateAppWidget(appwidgetids,remoteViews);
  1. 这里进入updateAppWidget方法:

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);
   

mService是一个IAppWidgetService类型,初始化是在SystemServiceRegistry(6.0才出现的,之前在ContextImpl里面)里面:


registerService(Context.APPWIDGET_SERVICE, AppWidgetManager.class,
       new CachedServiceFetcher<AppWidgetManager>() 
   @Override
   public AppWidgetManager createService(ContextImpl ctx) 
       IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
       return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
   );

注册服务的时候获取APPWIDGET_SERVICE。所以mService.updateAppWidgetIds最后会调用到远程进程,而IAppWidgetService的实现者是AppWidgetServiceImpl,所以最终会调用AppWidgetServiceImpl的updateAppWidgetIds。

  1. AppWidgetServiceImpl中的updateAppWidgetIds:

@Override
public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
       RemoteViews views) 
   if (DEBUG) 
       Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());
   

   updateAppWidgetIds(callingPackage, appWidgetIds, views, false);

这就是AppWidgetServiceImpl的updateAppWidgetIds方法。程序已经开始进入到AppWidgetServiceImpl所在的进程了,实际上是SystemServer进程。怎么看出AppWidgetServiceImpl是运行在SystemServer进程呢?AppWidgetService类new了一个AppWidgetServiceImpl,并且注册到ServiceManager中:


public class AppWidgetService extends SystemService 
   private final AppWidgetServiceImpl mImpl;

   public AppWidgetService(Context context) 
       super(context);
       mImpl = new AppWidgetServiceImpl(context);
   

   @Override
   public void onStart() 
       publishBinderService(Context.APPWIDGET_SERVICE, mImpl); //注册mImpl到ServiceManager当中
       AppWidgetBackupBridge.register(mImpl);
   

   @Override
   public void onBootPhase(int phase) 
       if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) 
           mImpl.setSafeMode(isSafeMode());
       
   

而AppWidgetService在SystemServer.java中使用:


private static final String APPWIDGET_SERVICE_CLASS =
       "com.android.server.appwidget.AppWidgetService";

SystemServer是通过反射的方式new一个AppWidgetService对象,然后调用它的start函数。很多service类都是这样启动的。

回到updateAppWidgetIds方法,最后它会调用四个参数的updateAppWidgetIds方法:


private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
       RemoteViews views, boolean partially) 
   final int userId = UserHandle.getCallingUserId();

   if (appWidgetIds == null || appWidgetIds.length == 0) 
       return;
   

   // Make sure the package runs under the caller uid.
   mSecurityPolicy.enforceCallFromPackage(callingPackage);

   final int bitmapMemoryUsage = (views != null) ? views.estimateMemoryUsage() : 0;
   if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) 
       throw new IllegalArgumentException("RemoteViews for widget update exceeds"
               + " maximum bitmap memory usage (used: " + bitmapMemoryUsage
               + ", max: " + mMaxWidgetBitmapMemory + ")");
   

   synchronized (mLock) 
       ensureGroupStateLoadedLocked(userId);

       final int N = appWidgetIds.length;
       for (int i = 0; i < N; i++) 
           final int appWidgetId = appWidgetIds[i];

           // NOTE: The lookup is enforcing security across users by making
           // sure the caller can only access widgets it hosts or provides.
           Widget widget = lookupWidgetLocked(appWidgetId,
                   Binder.getCallingUid(), callingPackage);

           if (widget != null) 
               updateAppWidgetInstanceLocked(widget, views, partially);
           
       
   

该方法首先会做些安全性检查,以及图片大小限制检查。最后会针对每一个appWidgetId,通过lookupWidgetLocked找到其对应的Widget。

  1. Widget是一个带有很多信息的类,我们看看lookupWidgetLocked方法:

private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) 
   final int N = mWidgets.size();
   for (int i = 0; i < N; i++) 
       Widget widget = mWidgets.get(i);
       if (widget.appWidgetId == appWidgetId
               && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) 
           return widget;
       
   
   return null;

实际上它是从mWidgets找到对应的Widget,先看看Widget类,它是AppWidgetServiceImpl的非静态内部类:


private static final class Widget 
   int appWidgetId;
   int restoredId;  // tracking & remapping any restored state
   Provider provider; // 对应AppWidgetProvider,里面有AppWidgetProvider信息。
   RemoteViews views; //表示View的RemoteView
   Bundle options;
   Host host; //显示的地方

   @Override
   public String toString() 
       return "AppWidgetId" + appWidgetId + ':' + host + ':' + provider + '';
   

而是从什么时候把Widget添加到mWidgets的呢?主要有三个地方,一个是绑定AppWidgetProvider跟id时,初始化时加载AppWidget与对应的host;一个是第一次添加AppWidget到桌面时,给AppWidget分配id的时候;一个是restore AppWidget的时候。我们看看分配id时,添加Widget的代码:


@Override

public int allocateAppWidgetId(String callingPackage, int hostId) 
   final int userId = UserHandle.getCallingUserId();

   // Make sure the package runs under the caller uid.
   mSecurityPolicy.enforceCallFromPackage(callingPackage);

   synchronized (mLock) 
       ensureGroupStateLoadedLocked(userId);

       if (mNextAppWidgetIds.indexOfKey(userId) < 0) 
           mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1);
       

       final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId); //增量分配一个id,保证不冲突

       // NOTE: The lookup is enforcing security across users by making
       // sure the caller can only access hosts it owns.
       HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage); //得到hostid
       Host host = lookupOrAddHostLocked(id); //根据id获取host

       Widget widget = new Widget();
       widget.appWidgetId = appWidgetId;
       widget.host = host;

       host.widgets.add(widget);   //把widget添加到host的widgets列表中
       addWidgetLocked(widget); //添加

       saveGroupStateAsync(userId);

       return appWidgetId;
   

实际上这里还没有添加对应的provider,所以在Launcher开发的时候,我们需要先调用allocateAppWidgetId方法,然后调用bindAppWidgetId方法绑定id与AppWidgetProvider。而当View有变化的时候,Host需要接收AppWidgetServiceImpl的通知,如何实现的呢?Host(Launcher应用)会跨进程调用AppWidgetServiceImpl的startListening方法,将AppWidgetHost端的Callback服务传递给AppWidgetServiceImpl:


// 在AppWidgetHost类当中,AppWidgetHost是Host端的代码

public void startListening() 
   int[] updatedIds;
   ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
   try 
       updatedIds = sService.startListening(mCallbacks, mContextOpPackageName, mHostId,
               updatedViews); //把AppWidgetHost端的mCallbacks传递给AppWidgetService,mCallbacks是Binder对象。
   
   catch (RemoteException e) 
       throw new RuntimeException("system server dead?", e);
   

   final int N = updatedIds.length;
   for (int i = 0; i < N; i++) 
       updateAppWidgetView(updatedIds[i], updatedViews.get(i));
   

在AppWidgetServiceImpl里面会将mCallbacks保存在对应的Host当中。

  1. 从mWidgets里面找到Widget后,会调用updateAppWidgetInstanceLocked方法来更新Widget。

private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
       boolean isPartialUpdate) 
   if (widget != null && widget.provider != null
           && !widget.provider.zombie && !widget.host.zombie)  // 保证widget有效,并且host也有效

       if (isPartialUpdate && widget.views != null) 
           // For a partial update, we merge the new RemoteViews with the old. 这里是对于partial update的。
           widget.views.mergeRemoteViews(views);
        else 
           // For a full update we replace the RemoteViews completely.
           widget.views = views;
       

       scheduleNotifyUpdateAppWidgetLocked(widget, views);
   

  1. 而scheduleNotifyUpdateAppWidgetLocked 则是使用handler发送一个消息给主线程处理:

private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) 
   if (widget == null || widget.provider == null || widget.provider.zombie
           || widget.host.callbacks == null || widget.host.zombie) 
       return;
   

   SomeArgs args = SomeArgs.obtain();
   args.arg1 = widget.host;
   args.arg2 = widget.host.callbacks; //callbacks 是host的跨进程调用接口,来自于startListening

   args.arg3 = updateViews;
   args.argi1 = widget.appWidgetId;

   mCallbackHandler.obtainMessage(
           CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
           args).sendToTarget();

  1. 使用mCallbackHandler发送一条MSG_NOTIFY_UPDATE_APP_WIDGET的消息,mCallbackHandler是AppWidgetServiceImpl.CallbackHandler的实例。如果处理,具体就到CallbackHandler的handleMessage方法中,就是一个Handler机制,让代码运行在主线程:

@Override
public void handleMessage(Message message) 
   switch (message.what) 
       case MSG_NOTIFY_UPDATE_APP_WIDGET: 
           SomeArgs args = (SomeArgs) message.obj;
           Host host = (Host) args.arg1;
           IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
           RemoteViews views = (RemoteViews) args.arg3;
           final int appWidgetId = args.argi1;
           args.recycle();

           handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);
        break;

       ...

   


  1. handleNotifyUpdateAppWidget方法:

private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
       int appWidgetId, RemoteViews views) 
   try 
       callbacks.updateAppWidget(appWidgetId, views); //通知AppWidgtHost
    catch (RemoteException re) 
       synchronized (mLock) 
           Slog.e(TAG, "Widget host dead: " + host.id, re);
           host.callbacks = null;
       
   

这里实际上就是调用IAppWidgetHost类型的updateAppWidget,进行跨进程调用。而callbacks就是在AppWidgetServiceImpl的startListening设置的:


@Override
public int[] startListening(IAppWidgetHost callbacks, String callingPackage,
       int hostId, List<RemoteViews> updatedViews) 
   final int userId = UserHandle.getCallingUserId();

   if (DEBUG) 
       Slog.i(TAG, "startListening() " + userId);
   

   // Make sure the package runs under the caller uid.
   mSecurityPolicy.enforceCallFromPackage(callingPackage);

   synchronized (mLock) 
       ensureGroupStateLoadedLocked(userId);

       // NOTE: The lookup is enforcing security across users by making
       // sure the caller can only access hosts it owns.
       HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
       Host host = lookupOrAddHostLocked(id);

       host.callbacks = callbacks; //设置callbacks

       updatedViews.clear();

       ArrayList<Widget> instances = host.widgets;
       int N = instances.size();
       int[] updatedIds = new int[N];
       for (int i = 0; i < N; i++) 
           Widget widget = instances.get(i);
           updatedIds[i] = widget.appWidgetId;
           updatedViews.add(cloneIfLocalBinder(widget.views));
       

       return updatedIds;
   

  1. 最终updateAppWidget的实现代码是:

static class Callbacks extends IAppWidgetHost.Stub 
   private final WeakReference<Handler> mWeakHandler;

   public Callbacks(Handler handler) 
       mWeakHandler = new WeakReference<>(handler);
   

   public void updateAppWidget(int appWidgetId, RemoteViews views) 
       if (isLocalBinder() && views != null) 
           views = views.clone();
       
       Handler handler = mWeakHandler.get();
       if (handler == null) 
           return;
       
       Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
       msg.sendToTarget();
   

  1. 最后使用Handler发送消息给主线程,然后在handleMessage中有具体的处理程序:

class UpdateHandler extends Handler 
   public UpdateHandler(Looper looper) 
       super(looper);
   

   public void handleMessage(Message msg) 
       switch (msg.what) 
           case HANDLE_UPDATE: 
               updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
               break;
           
       
   

  1. 然后调用AppWidgetHost的updateAppWidgetView方法:

void updateAppWidgetView(int appWidgetId, RemoteViews views) 
   AppWidgetHostView v;
   synchronized (mViews) 
       v = mViews.get(appWidgetId);
   
   if (v != null) 
       v.updateAppWidget(views);
   

  1. 根据appWidgetId找到对应的AppWidgetHostView,然后调用AppWidgetHostView的updateAppWidget来根据RemoteView来更新AppWidgetHostView:


/**
* 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;

   // Capture the old view into a bitmap so we can do the crossfade.
    ... 省去old view to bitmap

   if (remoteViews == null) 
       if (mViewMode == VIEW_MODE_DEFAULT) 
           // We've already done this -- nothing to do.
           return;
       
       content = getDefaultView();  // 默认的View
       mLayoutId = -1;
       mViewMode = VIEW_MODE_DEFAULT;
    else 
       // Prepare a local reference to the remote Context so we're ready to
       // inflate any requested LayoutParams.
       mRemoteContext = getRemoteContext();
       int layoutId = remoteViews.getLayoutId();

       // If our stale view has been prepared to match active, and the new
       // layout matches, try recycling it
       if (content == null && layoutId == mLayoutId) 
           try 
               remoteViews.reapply(mContext, mView, mOnClickHandler);
               content = mView;
               recycled = true;
               if (LOGD) Log.d(TAG, "was able to recycled existing layout");
            catch (RuntimeException e) 
               exception = e;
           
       

       // 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 (content == null) 
       if (mViewMode == VIEW_MODE_ERROR) 
           // We've already done this -- nothing to do.
           return ;
       
       Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
       content = getErrorView();  // 在失败的情况下,会使用ErrorView。
       mViewMode = VIEW_MODE_ERROR;
   

   if (!recycled) 
       prepareView(content);
       addView(content);
   

   if (mView != content) 
       removeView(mView);
       mView = content;
   

   if (CROSSFADE) 
       if (mFadeStartTime < 0) 
           // if there is already an animation in progress, don't do anything --
           // the new view will pop in on top of the old one during the cross fade,
           // and that looks okay.
           mFadeStartTime = SystemClock.uptimeMillis();
           invalidate();
       
   



代码有点长,但我觉得值得认真一看,可以从源码中发现当我们的AppWidget在桌面显示异常时究竟可能是什么原因。更新view的整个策略主要就是:

  1. 如果remoteView为空,则看是否已经使用了默认视图,如果已经使用了直接返回,如果没有则使用默认的视图。

  2. 如果remoteView不为空,则看layoutid是否跟现在已经使用的视图的layoutid一致,一致则重用旧的视图,调用remoteView.reapply方法重用视图,并且设置视图内容。

  3. 如果layoutid跟现使用的视图不一致,则调用remoteView.apply方法得到新的视图。

  4. 如果上面的步骤都没有得到视图,则使用错误视图。

  5. 如果新的视图与旧的视图不一致,则添加新的视图,删除旧的视图。

其实这里更新视图的策略非常简单,尽量重用已有的视图。里面会有三种视图,一种是我们自己设置的,一种是默认的,一种是有错误的情况的。当我们在桌面上看到我们的AppWidget显示异常时,应该还是有两种不同的表现的,一种是错误情况,一种是是用来额默认的视图,如果remoteView为null的时候会使用默认视图,如果是从remoteView中读取视图失败时,则会使用错误视图。

所以看到错误视图时,我们可能需要考虑remoteView里面的View设置是否合理。如果看到的是默认视图,我们应该想想是否在AppWidgetProvider中调用了AppWidgetManager.updateAppWidget方法,是否remoteView参数为null?可能有的手机两种视图是一样的。

总结

整个updateAppWidget的分析就到这里了,AppWidget其他的更新也是一样的,整个更新过程跨越了三个进程,而RemoteView作为一种View跨进程传递的媒介。另外我觉得从AppWidget去理解Binder机制的使用可能也是一个非常好的切入点。因为这部分我们在应用开发当中经常使用,而且是View,能够看到效果。接下来我想写一篇关于Binder多线程的理解。我看AppWidget这部分的源码,也是项目中的AppWidget存在问题,熟悉AppWidget才能解决好问题,预测代码中可能潜在的问题。

以上是关于AppWidget源码分析---updateAppWidget过程分析.md的主要内容,如果未能解决你的问题,请参考以下文章

AppWidget源码分析---updateAppWidget过程分析

AppWidget源码分析---updateAppWidget过程分析.md

AppWidget源码分析---updateAppWidget过程分析.md

Android 4.0 Launcher2源码分析——桌面快捷图标的拖拽

Android 4.0 Launcher2源码分析——桌面快捷图标的拖拽

桌面小部件AppWidgetProvider简单分析