Android版本:8.1
appwidget桌面小控件是以广播接收器的方式,通过远程调用view,实现app在桌面显示view的控件。
APP要实现小控件功能,需要实现一下步骤:
第一步:新建一个类继承AppWidgetProvider,然后实现其中的方法,onEnabled,onReceive,onUpdate,onDeleted,onDisabled,。。
第二步:在app的AndroidManifast.xml里声明这个receiver,并且要给receiver添加meta-data
例如:
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/dialer_calllog_widget_xml">
</meta-data>
在resource里,添加xml资源文件,格式如下
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_dialer_calllog_layout" //初始化时加载的布局
android:minHeight="160dip" //最小显示高度
android:minWidth="100dip" //最小显示宽度
android:resizeMode="horizontal|vertical" //拖动拉伸时可以拖动的方向
android:widgetCategory="home_screen|keyguard"
android:previewImage="@drawable/empty_call_log" //小控件在launcher添加时的图标
android:updatePeriodMillis="86400000"> //默认更新频率
<!-- 70*n-30 width = 180 height = 250-->
</appwidget-provider>
经过这样的设置,我们就可以在launcher桌面添加小控件的时候看到我们自定义的小控件了。
Appwidget是怎么通过广播接收并更新桌面上的控件view的?
当我们发送广播之后,AppwidgetProvider的onreceive就会收到广播。
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
}
}
由于我们重载了onupdate方法,所以会调用子类的onupdate,
三个参数分别是context,appwidgetmanager,和appwidgetIDs,
我们要更新我们的小控件view,需要创建remoteview,也就是把我们指定布局里的view给发送出去,让appwidgetmanager去更新远程桌面上的view。
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_dialer_calllog_layout);
appWidgetManager.updateAppWidget(appWidgetIds,mRemoteViews);
}
在AppWidgetManager里
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
if (mService == null) {
return;
}
try {
mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
---
private final IAppWidgetService mService;
调用的是远程AIDL, 追踪可以发现,
AppWidgetServiceImpl extends IAppWidgetService.Stub
在前面AppwidgetProvider的update里第二个参数,我们传递了一个AppWidgetManager.getInstance(context);
AppWidgetManager就是在这里做的初始化,用的单例模式。源码是这样的
public static AppWidgetManager getInstance(Context context) {
return (AppWidgetManager) context.getSystemService(Context.APPWIDGET_SERVICE);
}
获得的是系统服务,
public class AppWidgetService extends SystemService
这个服务是在Systemserver里启动的,和其他系统服务一起启动。
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
|| context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
traceBeginAndSlog("StartAppWidgerService");
mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
traceEnd();
}
具体启动流程,不做跟踪。
而AppWidgetService的实现类是
AppWidgetServiceImpl extends IAppWidgetService.Stub
于是,我们的update更新则是在AppWidgetServiceImpl里实现的,
@Override
public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views) {
if (DEBUG) {
Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());
}
updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
}
---
private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
RemoteViews views, boolean partially) {
...
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
final int appWidgetId = appWidgetIds[i];
Widget widget = lookupWidgetLocked(appWidgetId,
Binder.getCallingUid(), callingPackage);
if (widget != null) {
updateAppWidgetInstanceLocked(widget, views, partially);
}
}
}
}
---
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
boolean isPartialUpdate) {
if (widget != null && widget.provider != null
&& !widget.provider.zombie && !widget.host.zombie) {
...
scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
}
}
---
private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = (updateViews != null) ? updateViews.clone() : null;
args.arg4 = requestId;
args.argi1 = widget.appWidgetId;
mCallbackHandler.obtainMessage(
CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET, //发送message给handler
args).sendToTarget();
}
在CallbackHandler里
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;
long requestId = (Long) args.arg4;
final int appWidgetId = args.argi1;
args.recycle();
handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestId);
} break;
---
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
int appWidgetId, RemoteViews views, long requestId) {
try {
callbacks.updateAppWidget(appWidgetId, views);
host.lastWidgetUpdateRequestId = requestId;
} catch (RemoteException re) {
synchronized (mLock) {
Slog.e(TAG, "Widget host dead: " + host.id, re);
host.callbacks = null;
}
}
}
最后,调用了IAppWidgetHost的远程对象,来更新对应id的view,
就是前面传递的widget的host,
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
message的第二个参数就是它,本地对象就是AppWidgetHost,在AppWidgetHost里我们继续进行,
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();
}
}
---
↓
case HANDLE_UPDATE: {
updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
break;
}
↓
void updateAppWidgetView(int appWidgetId, RemoteViews views) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.updateAppWidget(views);
}
}
最后调用了AppWidgetHostView里的update
public void updateAppWidget(RemoteViews remoteViews) {
applyRemoteViews(remoteViews, true);
}
也就是把我们最开始要传递更新的remoteview给替换成新的view,
接下去就是进行view的绘画更新阶段,AppWidgetHostView 其实是FrameLayout
public class AppWidgetHostView extends FrameLayout
所以,当我们要更新我们的view时,就在最开始的接收广播那里,把Remoteview给创建好,最后调用
appWidgetManager.updateAppWidget(appWidgetIds,mRemoteViews) 就会更新小控件的显示内容了。