android的桌面小部件使用ListView和StackView刷新数据,找了半天网上了没个说明白的,自己研究了一上午,大概明白了小部件的整体机制。
如何实现StackView的小部件使用RemoteService,这些网上一堆,可以找一下。
如何刷新adapter中的数据先把结果贴上来。
使用如下方式,StackView的远端Adapter就会调用onDataSetChanged()
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(entry.widgetId,R.id.appwidget_stack_view);
源码分析:
RemoteView是跑在系统进程中的,并没有在我们的App中,必须使用进程间通信才能通知真正的View来刷新。RemoteViews这个类只是个数据传输类同时还有些工具函数apply什么的,app调用upateWidget将数据传输到远端Service,之后远端就可以处理了。
OK,这里分析通知刷新,就不用反射一些方法了,直接远端调用onDataSetChanged()就可以了,看一下android是怎么调用的。
正文:
使用notifyAppWidgetViewDataChanged()方法,实际实现是在系统Service,AppWidgetServiceImpl.java中的notifyAppWidgetViewDataChanged()
@Override
public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
int viewId) {
final int userId = UserHandle.getCallingUserId();
if (DEBUG) {
Slog.i(TAG, "notifyAppWidgetViewDataChanged() " + userId);
}
// Make sure the package runs under the caller uid.
mSecurityPolicy.enforceCallFromPackage(callingPackage);
if (appWidgetIds == null || appWidgetIds.length == 0) {
return;
}
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) {
scheduleNotifyAppWidgetViewDataChanged(widget, viewId);
}
}
}
}
在这个方法中查找到要刷新的widget,然后scheduleNotifyAppWidgetViewDataChanged(widget, viewId);看一下这个方法。
private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) {
if (widget == null || widget.host == null || widget.host.zombie
|| widget.host.callbacks == null || widget.provider == null
|| widget.provider.zombie) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.argi1 = widget.appWidgetId;
args.argi2 = viewId;
mCallbackHandler.obtainMessage(
CallbackHandler.MSG_NOTIFY_VIEW_DATA_CHANGED,
args).sendToTarget();
}
case MSG_NOTIFY_VIEW_DATA_CHANGED: {
SomeArgs args = (SomeArgs) message.obj;
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
final int appWidgetId = args.argi1;
final int viewId = args.argi2;
args.recycle();
handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId);
} break;
找到Handler处理的部分,调用handleNotifyAppWidgetViewDataChanged来处理。
case MSG_NOTIFY_VIEW_DATA_CHANGED: {
SomeArgs args = (SomeArgs) message.obj;
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
final int appWidgetId = args.argi1;
final int viewId = args.argi2;
args.recycle();
handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId);
} break;
最后绑定AppWidget的RemoteService这个Service就是为Widget的提供Adapter的Service,只要绑定成功就就回调方法onDataSetChangedAsync()代码如下
private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
int appWidgetId, int viewId) {
try {
callbacks.viewDataChanged(appWidgetId, viewId);
} catch (RemoteException re) {
// It failed; remove the callback. No need to prune because
// we know that this host is still referenced by this instance.
callbacks = null;
}
// If the host is unavailable, then we call the associated
// RemoteViewsFactory.onDataSetChanged() directly
synchronized (mLock) {
if (callbacks == null) {
host.callbacks = null;
Set> keys = mRemoteViewsServicesAppWidgets.keySet();
for (Pair key : keys) {
if (mRemoteViewsServicesAppWidgets.get(key).contains(appWidgetId)) {
final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IRemoteViewsFactory cb = IRemoteViewsFactory.Stub
.asInterface(service);
try {
cb.onDataSetChangedAsync();
} catch (RemoteException e) {
Slog.e(TAG, "Error calling onDataSetChangedAsync()", e);
}
mContext.unbindService(this);
}
@Override
public void onServiceDisconnected(android.content.ComponentName name) {
// Do nothing
}
};
final int userId = UserHandle.getUserId(key.first);
Intent intent = key.second.getIntent();
// Bind to the service and call onDataSetChanged()
bindService(intent, connection, new UserHandle(userId));
}
}
}
}
}
在RemoteViewsServices.java中
public synchronized void onDataSetChangedAsync() {
onDataSetChanged();
}
总结:appWidget,使用进程间通信的方式,来刷新其他进程的View,系统Service (AppWidgetServiceImpl)处理View更新,更新时将RemoteVeiws传输到系统Service中,通过WidgetId获得对应的实际View,调用RmoteView的apply,其实就是将RemoteView中的数据,使用反射的方式,应用到实际View的对应方法中。完成实际View的更新。