在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到手机更新不同步问题解决。