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

转[原文]

前面一篇文章,分析了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);
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注册服务的时候获取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);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这就是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());
       }
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

而AppWidgetService在SystemServer.Java中使用:


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

  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

该方法首先会做些安全性检查,以及图片大小限制检查。最后会针对每一个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;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

实际上它是从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 + ‘}‘;
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

而是从什么时候把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;
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

实际上这里还没有添加对应的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));
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  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;
       }
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这里实际上就是调用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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  1. 然后调用AppWidgetHost的updateAppWidgetView方法:

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  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();
       }
   }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

代码有点长,但我觉得值得认真一看,可以从源码中发现当我们的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才能解决好问题,预测代码中可能潜在的问题。

时间: 2024-10-14 07:18:44

AppWidget源码分析---updateAppWidget过程分析的相关文章

AppWidget源码分析---接口类

最近项目中接触到AppWidget,相对来说这部分比较简单,所以趁着空余时间详细阅读了AppWidget的源码.这篇文章主要是从源码上分析AppWidget中API类的相关原理,相关类的简单功能介绍和实现原理.关于使用,建议看指导文档. 简述 AppWidget相关的API类(供我们应用开发者使用的类)主要有: AppWidgetProvider:继承这个类,来提供Appwidget. AppWidgetManager:提供了AppWidget的管理接口,比如更新,绑定AppWidget id,

AppWidget源码分析(2)---updateAppWidget过程分析.md

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

【转】Android 4.0 Launcher2源码分析——启动过程分析

Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.launcher">  <original-package android:name="com.android.launcher2" /> .

Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析

在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever you want LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 2. you're in some View class, then jus

Android布局文件的加载过程分析:Activity.setContentView()源码分析

大家都知道在Activity的onCreate()中调用Activity.setContent()方法可以加载布局文件以设置该Activity的显示界面.本文将从setContentView()的源码谈起,分析布局文件加载所涉及到的调用链.本文所用的源码为android-19. Step 1  .Activity.setContentView(intresId) public void setContentView(int layoutResID) { getWindow().setConten

Android应用程序启动过程——Launcher源码分析

当我们在Launcher界面单击一个应用程序图标时就会启动一个程序,那这一个过程究竟发生了些哪样呢?让我们跟踪Launcher源码来分析一下吧. 先上流程图: step1.追踪Launcher  从源码中我们可以发现Launcher其实也是一个程序,它继承于Activity.找到该文件中的onCreate()方法,代码片段如下: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceSta

Android视图View绘制流程与源码分析(全)

来源:[工匠若水 http://blog.csdn.net/yanbober] 1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系: 看见没有,如上图中id为content的内容就是整个View树的结构,所以对每个具体View对象的操作,其实就是个递归的实现. 前面<Android触摸屏事件派发机制详解与源码分析一(

Cocos2d-X3.0 刨根问底(九)----- 场景切换(TransitionScene)源码分析

上一章我们分析了Scene与Layer相关类的源码,对Cocos2d-x的场景有了初步了解,这章我们来分析一下场景变换TransitionScene源码. 直接看TransitionScene的定义 class CC_DLL TransitionScene : public Scene { public: /** Orientation Type used by some transitions */ enum class Orientation { /// An horizontal orie

Android异步消息传递机制源码分析&amp;&amp;相关知识常被问的面试题

1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.postDelay(Runnable r, time)来在指定时间执行msg. 2).线程间通信:在执行较为耗时操作的时候,在子线程中执行耗时任务,然后handler(主线程的)把执行的结果通过sendmessage的方式发送给UI线程去执行用于更新UI. 3.handler源码分析 一.在Activ