转[原文]

前面一篇文章,分析了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才能解决好问题,预测代码中可能潜在的问题。

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

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

    Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="http://schemas. ...

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

    Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="http://schemas. ...

  3. u-boot 源码分析(1) 启动过程分析

    u-boot 源码分析(1) 启动过程分析 文章目录 u-boot 源码分析(1) 启动过程分析 前言 配置 源码结构 api arch board common cmd drivers fs Kbu ...

  4. spring aop 源码分析(二) 代理方法的执行过程分析

    在上一篇aop源码分析时,我们已经分析了一个bean被代理的详细过程,参考:https://www.cnblogs.com/yangxiaohui227/p/13266014.html 本次主要是分析 ...

  5. sping aop 源码分析(-)-- 代理对象的创建过程分析

    测试项目已上传到码云,可以下载:https://gitee.com/yangxioahui/aopdemo.git 具体如下: public interface Calc { Integer add( ...

  6. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  7. Quartz源码分析

    先简单介绍一下quartz,Quartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中 - 从最小的独立应用程序到最大的电子商务系统.quartz可用于创建执行数十,数百甚至数十 ...

  8. ElasticSearch Index操作源码分析

    ElasticSearch Index操作源码分析 本文记录ElasticSearch创建索引执行源码流程.从执行流程角度看一下创建索引会涉及到哪些服务(比如AllocationService.Mas ...

  9. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

随机推荐

  1. ios中沙盒

    打开模拟器沙盒目录 下面看看模拟器的沙盒文件夹在mac电脑上的什么位置. 文件都在个人用户名文件夹下的一个隐藏文件夹里,中文叫资源库,他的目录其实是Library. 2.1 方法1.可以设置显示隐藏文 ...

  2. ZOJ 3331 Process the Tasks(双塔DP)

    Process the Tasks Time Limit: 1 Second      Memory Limit: 32768 KB There are two machines A and B. T ...

  3. Python IDLE或shell中切换路径

    在Python自带的编辑器IDLE中或者python shell中不能使用cd命令,那么跳到目标路径呢.方法是使用os包下的相关函数实现路径切换功能. import os  os.getcwd() # ...

  4. HTTP协议包分析(小马上传大马)

    最近工作内容是分析防火墙日志,看日志是正确,本地实验小马上传大马  抓取http包如下.可以在分析过程中进行借鉴. 该http请求的行为是通过小马,在小马的当前目录创建一个dama.php的文件,文件 ...

  5. ubuntu14.04 编译安装CPU版caffe

      本文,试图中一个干净的ubuntu14.04机器上安装caffe的cpu版本. http://blog.csdn.net/sinat_35188997/article/details/735304 ...

  6. pytho创建二维码简单版

    pytho创建二维码简单版 import qrcode aa = qrcode.make("https://github.com/phygerr/") aa.save('C:\Us ...

  7. requests 中response如何改变编码格式

    查看初始编码 首先查看拿到的response编码格式: (就不放代码了,因为此例需要用到cookie,可自行找个网站具体测试) 可见初始编码为:ISO-8859-1 修改编码 初始编码: 修改后编码: ...

  8. 我的Android进阶之旅------>Android 关于arm64-v8a、armeabi-v7a、armeabi、x86下的so文件兼容问题

    Android 设备的CPU类型通常称为ABIs 问题描述 解决方法 1解决之前的截图 2解决后的截图 3解决方法 4建议 为什么你需要重点关注so文件 App中可能出错的地方 其他地方也可能出错 使 ...

  9. What is tail-recursion

    Consider a simple function that adds the first N integers. (e.g. sum(5) = 1 + 2 + 3 + 4 + 5 = 15). H ...

  10. mui请求数据

    var rh = new Object(); rh.ReqId = "ls123"; rh.Salt = "sssseee"; var rb = new Obj ...