本文主要从系统层怎样加载一个widget分析,不包含怎样创建一个含有widget的app。
所谓widget,梗概流程就是App开发者传给系统一个自定义的RemoteView,锁屏或者桌面把这个RemotView解析出来放在自己的界面上,以widget的形式显示出来。
文章有点长,没耐心只想看大概结构可以翻到最后看小结中的图。
widget相关类
相关的类主要有以下几个
AppWidgetHostView
AppWodgetHostView是一个View,是容纳App传来的View的容器。
由于RemoteView并不是真正的View,只是一个View的描述,所以需要通过updateAppWidget(RemoteViews views)这个方法把View创建出来,然后加到AppWidgetView里。
public void updateAppWidget(RemoteViews remoteViews) {
if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
...//此处有省略,详见源码
// Try normal RemoteView inflation
if (content == null) {
try {
/// M: add for using customer view, migrated from GB to ICS to JB
remoteViews.setHasUsedCustomerView(mInfo.hasUsedCustomerView);
content = remoteViews.apply(mContext, this, mOnClickHandler);//重点,在这里apply"
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}
...//此处有省略,详见源码
if (!recycled) {
prepareView(content);
addView(content);
}
...//此处有省略,详见源码
}
- 以上代码中的mView就是要加到AppWidgetHostView中的View,remoteViews.apply创建了实际的View.
AppWidgetHost
是容纳appwidget的地方,它有以下功能:
- 监听来自AppWidgetService的事件,这是主要处理update和provider_changed两个事件,根据这两个事件更新widget。(此处加代码)
- 另外一个功能就是创建AppWidgetHostView。这里先创建AppWidgetHostView,然后通过AppWidgetService查询 appWidgetId对应的RemoteViews,最后把RemoteViews传递给AppWidgetHostView去 updateAppWidget。(此处加代码)
以上两个类是基类,用户可以根据这两个类来定制自己的widget的。
AppWidgetProvider
AppWidgetProvider,其本质BroadcastReceiver。用户用这个类去创建自己的widget,用户可以通过继承AppwidgetProvider的一个或者几个方法来定义自己的widget以及控制widget的更新
主要的function以及作用可以参照开发者文档
AppWidgetService
Widget的核心类,首先在系统启动以后在systemReady做了以下工作:
- loadAppWidgetListLocked()通过PackageManager从Android系统中查找所有已经被安装的AppWidget(包含"android.appwidget.action.APPWIDGET_UPDATE" 的Action和meta-data标签),解析AppWidget的配置信息,封闭成对象,保存到List集合。
- addProviderLocked 从/data/system/appwidgets.xml文件读取已经被添加到Launcher的AppWidget信息,封闭成对象,保存到List集合中。
- systemRunning(boolean safeMode) 注册四个广播接收器:第一. Android系统启动完成,第二. Android配置信息改变,第三. 添加删除应用,第四. sdcard的安装与缷载。
AppWidgetService承担着所有widget的管理工作。Widget安装,删除,更新等等都需要经过AppWidgetService,它是开机就启动的.
AppWidgetManager
Updates AppWidget state; gets information about installed AppWidget providers and other AppWidget related state.
这是代码中对这个类的注释,可以看出,这个类的主要作用是更新AppWidget的状态以及得到appwidget的信息。
-
这个类中定义了一些很重要的intent,例如
- ACTION_KEYGUARD_APPWIDGET_PICK:Setting中创建这个Activity,会列出当前系统中可以供添加到锁屏的所有widget,用户可以在列表中选择一个widget添加到锁屏上。具体实现参照Setting中的类KeyguardAppWidgetPickActivity。
- ACTION_APPWIDGET_BIND:允许绑定一个widget到系统中。参见Setting中的AllowBindAppWidgetActivity
等等。
-
另外这个类中有一些很重要的接口,定义了widget的加载。更新等操作,如
- updateAppWidget(int appWidgetId, RemoteViews views)//更新widget,在第一次加载widget的时候,加载widget
- notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId)//监听widget的改变。
- bindAppWidgetId(int appWidgetId, ComponentName provider) //绑定widget到系统中。需要BIND_APPWIDGET权限
接口的实现都在AppWidgetService中,使用这个类的主要是锁屏和桌面。
AppWidgetProviderInfo
每个AppWidget都有一个AppWidgetProviderInfo对象,该对象描述了每个AppWidget的基本数据(meta-data)信息 ,其定义在
示例如下:
不做赘述。
存储位置:
上文可知,系统中所有的widget是通过packageManager从package里查找的(包含"android.appwidget.action.APPWIDGET_UPDATE" 的Action和meta-data标签),那么已经被添加的锁屏的widget的信息或者id存储在哪里呢?
我们再来看KeyguardAppWidgetPickActivity
,在添加appwidget以后选择一个widget的时候会调到
mLockPatternUtils.addAppWidget(appWidgetId, 0);
最终调到
private void writeAppWidgets(int[] appWidgetIds) {
Settings.Secure.putStringForUser(mContentResolver,
Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS,
combineStrings(appWidgetIds, ","),
UserHandle.USER_CURRENT);
}
也就是说,我们锁屏相关的widget是通过settings存储在数据库里。(具体存储这块内容在研究)
addWidget流程(绑定一个widget并最终存储到数据库)
以KeyguardHostView为例,添加一个View到数据库的流程
在Keyguard中,添加一个widget是通过start一个Settings中的Activity来实现的,上文已经提及KeyguardAppWidgetPickActivity。
- 当用户点击添加widget的button的时候,会弹出Activity:KeyguardAppWidgetPickActivity,然后Activity会通过PackageManager得到所有的widget并显示出来。
我们需要关注的是,当某个widget被点击以后发生的事情。
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Item item = mItems.get(position);
Intent intent = item.getIntent();
int result;
if (item.extras != null) {
// If these extras are present it's because this entry is custom.
// Don't try to bind it, just pass it back to the app.
result = RESULT_OK;
setResultData(result, intent);
} else {
try {
if (mAddingToKeyguard && mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
// Found in KeyguardHostView.java
final int KEYGUARD_HOST_ID = 0x4B455947;
int userId = ActivityManager.getCurrentUser();
* mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForPackage(KEYGUARD_HOST_ID,
userId, "com.android.keyguard");//重点:关键步骤1
}
*mAppWidgetManager.bindAppWidgetId(
mAppWidgetId, intent.getComponent(), mExtraConfigureOptions);//重点:关键步骤2
} catch (IllegalArgumentException e) {
// This is thrown if they're already bound, or otherwise somehow
// bogus. Set the result to canceled, and exit. The app *should*
// clean up at this point. We could pass the error along, but
// it's not clear that that's useful -- the widget will simply not
// appear.
result = RESULT_CANCELED;
}
setResultData(result, null);
}
if (mAddingToKeyguard) {
onActivityResult(REQUEST_PICK_APPWIDGET, result, mResultData);//重点
} else {
finish();
}
}
- allocateAppWidgetIdForPackage最终会调用到AppWidgetServiceImpl的allocateAppWidgetId。
public int allocateAppWidgetId(String packageName, int hostId) {
int callingUid = enforceSystemOrCallingUid(packageName);
Slog.d(TAG, "allocateAppWidgetId uid"+callingUid+" packageName "+packageName);
synchronized (mAppWidgetIds) {
if (!mHasFeature) {
return -1;
}
ensureStateLoadedLocked();
int appWidgetId = mNextAppWidgetId++;//1.创建一个id
Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
AppWidgetId id = new AppWidgetId();
id.appWidgetId = appWidgetId;
id.host = host;
host.instances.add(id);
mAppWidgetIds.add(id);2.//添加这个id
saveStateAsync();
if (DBG) log("Allocating AppWidgetId for " + packageName + " host=" + hostId
+ " id=" + appWidgetId);
return appWidgetId;//3.返回创建的id
}
}
然后就给这个widget分配了唯一的id。
2.bindAppWidgetId最终调用到AppWidgetServiceImpl的bindAppWidgetIdImp
private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) {
if (DBG) log("bindAppWidgetIdImpl appwid=" + appWidgetId
+ " provider=" + provider);
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mAppWidgetIds) {
...//此处有省略,详见源码
id.provider = p;
if (options == null) {
options = new Bundle();
}
id.options = options;
// We need to provide a default value for the widget category if it is not specified
if (!options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
}
p.instances.add(id);
int instancesSize = p.instances.size();
if (instancesSize == 1) {
// tell the provider that it's ready
sendEnableIntentLocked(p);
}
// send an update now -- We need this update now, and just for this appWidgetId.
// It's less critical when the next one happens, so when we schedule the next one,
// we add updatePeriodMillis to its start time. That time will have some slop,
// but that's okay.
sendUpdateIntentLocked(p, new int[] { appWidgetId });
// schedule the future updates
registerForBroadcastsLocked(p, getAppWidgetIds(p));
saveStateAsync();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
这个方法主要就是把刚刚得到的id与这个widget的信息绑定在一起
注意,当id与widget绑定以后需要调用
sendUpdateIntentLocked(p, new int[] { appWidgetId })
来更新这个widget的内容,
void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) {
log("sendUpdateIntentLocked");
if (appWidgetIds != null && appWidgetIds.length > 0) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
///M: added by mtk for debugging @{
Exception e = new Exception();
e.printStackTrace();
/// @}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(p.info.provider);
mContext.sendBroadcastAsUser(intent, new UserHandle(mUserId));
}
}
这个方法主要是发送一个广播,widget的开发者接收到广播以后来更新这个widget的内容。发广播的时间是比widget添加到界面上的时间稍微提前,不过开发者可以在接收到广播以后再更新,这个时间差并不会影响widget接收广播。
3.当获取了id并且与widget绑定以后,再接着看onItemClick,
会调用到setResultData(int code, Intent intent)
,这个方法里有这两句
mLockPatternUtils.addAppWidget(appWidgetId, 0);//前文已经提到这个方法的作用是把id添加到数据库里。
finishDelayedAndShowLockScreen(appWidgetId);//重启锁屏
- 至此,把一个widget的加到数据库的流程已经理清,主要有以下几点:
- mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForPackage//申请id
- mAppWidgetManager.bindAppWidgetId//绑定id
- mLockPatternUtils.addAppWidget(appWidgetId, 0);//添加到数据库
getWidget流程(数据库中的得到widget列表并显示在锁屏)
同样以KeyguardHostView为起点。
上文可知,在添加完widget以后会重启锁屏,这个时候会走到KeyguardHostView的onFinishInflate() ——> updateAndAddWidgets();——>addWidgetsFromSettings()
private void addWidgetsFromSettings() {
if (mSafeModeEnabled || widgetsDisabled()) {
addDefaultStatusWidget(0);//添加一些默认的widget
return;
}
int insertionIndex = getInsertPageIndex();
// Add user-selected widget
final int[] widgets = mLockPatternUtils.getAppWidgets();
if (widgets == null) {
Log.d(TAG, "Problem reading widgets");
} else {
for (int i = widgets.length -1; i >= 0; i--) {
if (widgets[i] == LockPatternUtils.ID_DEFAULT_STATUS_WIDGET) {
addDefaultStatusWidget(insertionIndex);
} else {
// We add the widgets from left to right, starting after the first page after
// the add page. We count down, since the order will be persisted from right
// to left, starting after camera.
addWidget(widgets[i], insertionIndex, true);
}
}
}
}
由前文可知,添加widget到数据库的操作是LockPatternUtils.addAppWidget(int widgetId, int index);
同理,取出数据库中锁屏的widgetid的方法是getAppWidgets
LockPatternUtils.java
private int[] getAppWidgets(int userId) {
String appWidgetIdString = Settings.Secure.getStringForUser(
mContentResolver, Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS, userId);
String delims = ",";
if (appWidgetIdString != null && appWidgetIdString.length() > 0) {
String[] appWidgetStringIds = appWidgetIdString.split(delims);
int[] appWidgetIds = new int[appWidgetStringIds.length];
for (int i = 0; i < appWidgetStringIds.length; i++) {
String appWidget = appWidgetStringIds[i];
try {
appWidgetIds[i] = Integer.decode(appWidget);
} catch (NumberFormatException e) {
Log.d(TAG, "Error when parsing widget id " + appWidget);
return null;
}
}
return appWidgetIds;
}
return new int[0];
}
以上,取出了已经添加到锁屏上的widget的列表,继续看KeyguardHostView的addWidget(widgets[i], insertionIndex, true);
private boolean addWidget(int appId, int pageIndex, boolean updateDbIfFailed) {
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appId);//通过取到widget对应的AppWidgetProviderInfo
if (appWidgetInfo != null) {
AppWidgetHostView view = mAppWidgetHost.createView(mContext, appId, appWidgetInfo);通过调用AppWidgetHost的CreateView方法将remoteview apply为view再添加到AppWidgetHostView中
addWidget(view, pageIndex);
return true;
} else {
if (updateDbIfFailed) {//widget信息为空或无效,删除id
Log.w(TAG, "*** AppWidgetInfo for app widget id " + appId + " was null for user"
+ mUserId + ", deleting");
mAppWidgetHost.deleteAppWidgetId(appId);
mLockPatternUtils.removeAppWidget(appId);
}
return false;
}
}
AppWidgetHost是通过AppWidgetHostView的view.updateAppWidget(views);方法来获取view的
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
Log.d(TAG, "createView appWidgetId "+appWidgetId);
final int userId = mContext.getUserId();
AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
view.setUserId(userId);
view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
Log.d(TAG, "createView mViews put "+this);
mViews.put(appWidgetId, view);
}
RemoteViews views;
try {
views = sService.getAppWidgetViews(appWidgetId, userId);
if (views != null) {
views.setUser(new UserHandle(mContext.getUserId()));
}
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
view.updateAppWidget(views);
return view;
}
具体请注意以下两句
public void updateAppWidget(RemoteViews remoteViews) {
...//省略,详细请查看源码
// Try normal RemoteView inflation
if (content == null) {
try {
/// M: add for using customer view, migrated from GB to ICS to JB
remoteViews.setHasUsedCustomerView(mInfo.hasUsedCustomerView);
content = remoteViews.apply(mContext, this, mOnClickHandler);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}
...//省略,详细请查看源码
if (mView != content) {
removeView(mView);
mView = content;//mView即AppWidgetHostView中添加的View
}
...//省略,详细请查看源码
}
经过以上步骤,则remoteView被通过id得到且创建为View放进AppWidgetHostView中。
继续看KeyguardHostView的addWidget(view, pageIndex);
会调用到mAppWidgetContainer.addWidget(view, pageIndex),mAppWidgetContainer是一个KeyguardWidgetPager的实例,我们可以随便定义一个父View,不一定要放在KeyguardWidgetPager里。
KeyguardWidgetPager.java
public void addWidget(View widget, int pageIndex) {
KeyguardWidgetFrame frame;
...//省略,详细请看源码
frame.addView(widget, lp);
...//省略,详细请看源码
if (pageIndex == -1) {
addView(frame, pageLp);
} else {
addView(frame, pageIndex, pageLp);
}
...//省略,详细请看源码
}
这里只是添加上,还不算完成,
注意,在KeyguardHostView中的onAttachedToWindow中还有这么一句
if (!KeyguardViewMediator.isKeyguardInActivity) {
mAppWidgetHost.startListening();
}
startListening一定要写,widget才会被定时更新。
至此,我们的widget被成功添加到锁屏的界面。
- 主要流程总结如下:
- widgets = mLockPatternUtils.getAppWidgets();//从数据库中读取
- appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appId);//通过id取到widget对应的AppWidgetProviderInfo
- AppWidgetHostView view = mAppWidgetHost.createView(mContext, appId, appWidgetInfo);通过调用AppWidgetHost的CreateView方法将remoteview apply为view再添加到AppWidgetHostView中
- addWidget(view, pageIndex);//将AppWidgetHostView添加到我们的父View中
注意在view加载完成以后mAppWidgetHost.startListening().
updatewidget流程(widget的更新)
上文我们提到两个地方,有关widget的更新
- 在bindAppWidgetId完成以后,调用 sendUpdateIntentLocked(p, new int[] { appWidgetId });发送广播来更新,这个流程比较清晰,不赘述。
-
在view加载以后mAppWidgetHost.startListening();来保持View一直会更新
。
AppWidgetHost的startListening会调用到AppWidgetService的startListening,AppWidgetService的startListening会返回一个需要更新的widgetid的数组。AppWidgetHost会在得到这个数组以后更新一次这个Host上的所有widget。
当然,要保持widget持续刷新,更新一次肯定是不够的,所有我们继续看
第一小步
AppWidgetService:
public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
List updatedViews) {
Slog.d(TAG, "startListening hostId " + hostId);
。。。//省略
Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
。。。//省略
return updatedIds;
}
}
再来看lookupOrAddHostLocked里做了如下操作
Host lookupOrAddHostLocked(int uid, String packageName, int hostId) {
final int N = mHosts.size();
for (int i = 0; i < N; i++) {
Host h = mHosts.get(i);
if (h.hostId == hostId && h.packageName.equals(packageName)) {
return h;
}
}
Host host = new Host();
host.packageName = packageName;
Slog.d(TAG, "lookupOrAddHostLocked h.uid"+uid);
host.uid = uid;
host.hostId = hostId;
mHosts.add(host);//重点
return host;
}
这里是把需要更新widget的host加入到AppWidgetService的mHost列表里。
第二小步
由于添加widget导致android配置信息改变会进入AppwidgetService的onConfiguartionchanged,这个方法中做了如下操作
void onConfigurationChanged() {
if (DBG) log("Got onConfigurationChanged()");
Locale revised = Locale.getDefault();
if (revised == null || mLocale == null || !(revised.equals(mLocale))) {
mLocale = revised;
synchronized (mAppWidgetIds) {
ensureStateLoadedLocked();
// Note: updateProvidersForPackageLocked() may remove providers, so we must copy the
// list of installed providers and skip providers that we don't need to update.
// Also note that remove the provider does not clear the Provider component data.
ArrayList installedProviders =
new ArrayList(mInstalledProviders);
HashSet removedProviders = new HashSet();
int N = installedProviders.size();
for (int i = N - 1; i >= 0; i--) {
Provider p = installedProviders.get(i);
ComponentName cn = p.info.provider;
if (!removedProviders.contains(cn)) {
updateProvidersForPackageLocked(cn.getPackageName(), removedProviders);//重点1
}
}
saveStateAsync();//重点2
}
}
}
继续看源码(这里不再粘贴)会发现
updateProvidersForPackageLocked(cn.getPackageName(), removedProviders)
做了一个操作就是根据配置的更新间隔定时发出更新广播。
updateProvidersForPackageLocked——>getAppWidgetIds——>sendUpdateIntentLocked(p, appWidgetIds);
saveStateAsync();
会重新更新需要更新的widget的列表的xml。
saveStateAsync()——>writeStateToFileLocked——>parseProviderInfoXml(component, ri);
saveStateAsync就把上文startListening更新的mHost更新到xml里,下次定时更新widet的时候就遍历到这里然后把对应的widget进行更新.
- 至此,widget的更新流程算是走完了,主要有一下两个
- onconfiguarationchange的时候定时读取xml——>向app发送更希widget的广播——>更新xml,写入需要update的widgetHost的列表。
- 2.startListening的时候把当前Host的Id通知给AppWidgetService保存在mHost列表里等待下次写进xml。
小结:
所以添加一个插件的核心流程为