在Android系统中,通过长按launcher空白处,会弹出“添加到主屏幕”界面,选择窗口小部件项,弹出“选择窗口小部件”界面,选择任一widget就可将此widget添加到桌面上,截图如下:
此添加AppWidget过程比较繁琐,我想在Launcher屏幕上添加一个按钮,通过点击此按钮直接实现添加widget功能,并使用gallery显示所有的widget icon。通过查看源代码,发现上述添加AppWidget的逻辑写在Setting工程中,于是想自己在Launcher中实现这个功能,下面是获取所有已安装widget信息代码:
public void initData(Context context)
{
tempInfo.clear();
AppWidgetManager am = AppWidgetManager.getInstance(context);
tempInfo = (ArrayList<AppWidgetProviderInfo>)am.getInstalledProviders();
}
Collections.sort(tempInfo, new Comparator<AppWidgetProviderInfo>()
{
Collator collator = Collator.getInstance();
@Override
public int compare(AppWidgetProviderInfo arg0, AppWidgetProviderInfo arg1) {
// TODO Auto-generated method stub
return collator.compare(arg0.label, arg1.label);
}
});
}
关键代码就是tempInfo = (ArrayList<AppWidgetProviderInfo>)am.getInstalledProviders();
数据源TempInfo获取到了,后面的事情就比较简单。将数据源传入gallery的自定义adpter中,并将此gallery绑定到写好的Launcher按钮响应事件上,一切就大功告成。成果如下:
但是在测试时发现,当在系统启动好后,安装新的widget(down APK),gallery不会更新新安装的appwidget,同样,删除一个widget,gallery也不会更新。通过查看Android管理AppWidget的AppWidgetService.java,发现Android在启动时注册了四个BroadcastReceiver:
public void systemReady(boolean safeMode) {
mSafeMode = safeMode;
loadAppWidgetList();
loadStateLocked();
// Register for the boot completed broadcast, so we can send the
// ENABLE broacasts. If we try to send them now, they time out,
// because the system isn't ready to handle them yet.
mContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
// Register for configuration changes so we can update the names
// of the widgets when the locale changes.
mContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null);
// Register for broadcasts about package install, etc., so we can
// update the provider list.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiver(mBroadcastReceiver, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mBroadcastReceiver, sdFilter);
}
注意18-24行代码:
// Register for broadcasts about package install, etc., so we can
// update the provider list.
这段注释很重要,表示Android在安装/删除APK时,AppWidgetService接收系统传过来的ACTION_PACKAGE_ADDED和ACTION_PACKAGE_REMOVED广播,继续看AppWidgetService代码,看Android是在哪里处理这两个广播的(下面以处理ACTION_PACKAGE_ADDED广播为例,ACTION_PACKAGE_REMOVED与其相似)。
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//Slog.d(TAG, "received " + action);
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
sendInitialBroadcasts();
mLocale = Locale.getDefault();
mSkin = context.getResources().getConfiguration().skin;
} else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
Locale revised = Locale.getDefault();
if (revised == null || mLocale == null ||
!(revised.equals(mLocale))) {
mLocale = revised;
updateAllWidgets();
}
String newSkin = context.getResources().getConfiguration().skin;
if (newSkin == null || mSkin == null ||
!(newSkin.equals(mSkin))) {
mSkin = newSkin;
updateAllWidgets();
}
} else {
boolean added = false;
String pkgList[] = null;
if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
added = true;
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
added = false;
} else {
Uri uri = intent.getData();
if (uri == null) {
return;
}
String pkgName = uri.getSchemeSpecificPart();
if (pkgName == null) {
return;
}
pkgList = new String[] { pkgName };
added = Intent.ACTION_PACKAGE_ADDED.equals(action);
}
if (pkgList == null || pkgList.length == 0) {
return;
}
if (added) {
synchronized (mAppWidgetIds) {
Bundle extras = intent.getExtras();
if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
for (String pkgName : pkgList) {
// The package was just upgraded
updateProvidersForPackageLocked(pkgName);
}
} else {
// The package was just added
for (String pkgName : pkgList) {
addProvidersForPackageLocked(pkgName);
}
}
saveStateLocked();
}
} else {
Bundle extras = intent.getExtras();
if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
// The package is being updated. We'll receive a PACKAGE_ADDED shortly.
} else {
synchronized (mAppWidgetIds) {
for (String pkgName : pkgList) {
removeProvidersForPackageLocked(pkgName);
saveStateLocked();
}
}
}
}
}
}
};
注意47行到59行代码,当down一个新的widget到手机,会走addProvidersForPackageLocked()方法:
void addProvidersForPackageLocked(String pkgName) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.setPackage(pkgName);
List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent,
PackageManager.GET_META_DATA);
final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
for (int i=0; i<N; i++) {
ResolveInfo ri = broadcastReceivers.get(i);
ActivityInfo ai = ri.activityInfo;
if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
continue;
}
if (pkgName.equals(ai.packageName)) {
addProviderLocked(ri);
}
}
}
addProvidersForPackageLocked()方法通过PackageManager从Android系统中查找所有已经被安装的AppWidget(包含"android.appwidget.action.APPWIDGET_UPDATE"的Action和meta-data标签),解析AppWidget的配置信息,通过addProviderLocked()方法封闭成对象,添加到mIntalledProviders变量中。下面是addProviderLocked()方法代码:
boolean addProviderLocked(ResolveInfo ri) {
Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName,
ri.activityInfo.name), ri);
if (p != null) {
mInstalledProviders.add(p);
return true;
} else {
return false;
}
}
mInstalledProviders变量就是SDK中通过AppWidgetManager.getInstalledProvider()获取的list。
Down widget APK到手机framework处理逻辑就走完了,简单的说就是Down APK到手机这个动作会发出ACTION_PACKAGE_ADDED广播,系统服务AppWidgetService注册广播监听器监听这个广播,当监听到此广播,AppWidgetService会重新遍历所有已经被安装的package(包含"android.appwidget.action.APPWIDGET_UPDATE"的Action和meta-data标签,这两个标签其实就是表明此package为appwidget),然后将得到的结果放入存放已安装的AppWidget的list中。
现在需要做的就是在自己写的代码中加入两个广播接收器监听ACTION_PACKAGE_ADDED和ACTION_PACKAGE_REMOVED广播,接收到这两个广播后,重新通过AppWidgetManager.getInstalledProvider()获取系统已安装的AppWidget列表,重新更新下gallery。代码如下:
1)新建一个广播接收器,并重写onReceive方法:
//begin:add by caovae
BroadcastReceiver br2 = new BroadcastReceiver() {
@Override
public void onReceive(Context arg0, Intent arg1) {
initData(context);
widgetsAdapter.notifyDataSetChanged();
}
};
//end
2)在代码中注册该广播接收器:
//begin:add by caovae
IntentFilter iFilter = new IntentFilter();
iFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
iFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
iFilter.addDataScheme("package");
launcher.registerReceiver(br2, iFilter);
//end
本以为这样所有的代码应该没有问题了,但是实际效果确还是不能更新gallery。通过打log分析了下原因,在声明的广播接收器中,所得到的已安装的appwidget当down了新的widget时并没有改变,分析原因,可能是AppWidgetService与我自己写的代码同时接收到ACTION_PACKAGE_ADDED广播,而AppWidgetService在接收到此广播后还有一段处理时间。所以我在广播接收器br2的onReceive通过新建线程并让其sleep两秒钟,然后在执行重新获取已安装的AppWidget列表,更新gallery。重写的br2代码如下:
//begin:add by caovae
BroadcastReceiver br2 = new BroadcastReceiver() {
@Override
public void onReceive(Context arg0, Intent arg1) {
Runnable runnable = new Runnable() {
public void run() {
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
initData(context);
widgetsAdapter.notifyDataSetChanged();
}
};
runnable.run();
// System.out.println("--------onreceiver: " + widgetInfo.size());
}
};
//end
OK,添加新的Appwidget到手机更新不同步问题解决。