Android设备中,很多应用需要增加在Settings中增加菜单,作为应用的入口。此时可以仿照google GMS包的应用,采用动态加载的方式。这种方法不需要修改Settings中代码,修改应用本身的AndroidManifest.xml文件就行,实现解耦并自动适配
1、使用方法
1.1、示例:
在AndroidMainfest.xml中增加如下配置
<!--最后的效果是Settings-Display中会增加一个菜单,点击该菜单进入MainActivity--> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!--Settings会检测该action来查找要插入的菜单--> <intent-filter android:priority="6"> <action android:name="com.android.settings.action.EXTRA_SETTINGS" /> </intent-filter> <!--确定在哪个界面添加,这是表示在Settings-display中增加, 具体界面的值可以在CategoryKey.java中查看--> <meta-data android:name="com.android.settings.category" android:value="com.android.settings.category.ia.display" /> <!--菜单标题--> <meta-data android:name="com.android.settings.title" android:value="@string/app_name"/> <!--菜单summary--> <meta-data android:name="com.android.settings.summary" android:resource="@string/summary_text" /> <!--菜单icon--> <meta-data android:name="com.android.settings.icon" android:resource="@drawable/icon_setings" /> <!--菜单显示位置--> <meta-data android:name="com.android.settings.order" android:value="-100" /> </activity>
1.2、权限
动态插入菜单有一个条件,需要插入菜单的应用必须为system应用,若要所有的应用都能插入菜单,需要修改Settings源码中这段逻辑
frameworks/base/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
private static void loadActivityTiles(Context context, UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent) { final PackageManager pm = context.getPackageManager(); //获取meta data final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA, user.getIdentifier()); for (ResolveInfo resolved : results) { if (!resolved.system) { //判断是否为system应用 // Do not allow any app to add to settings, only system ones. //注释此处,则任何应用都可以插入菜单 //continue; } final ActivityInfo activityInfo = resolved.activityInfo; final Bundle metaData = activityInfo.metaData; //加载菜单数据 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo); } }
2、原理
查看Settings源码,分析流程
2.1、数据更新
SettingsActivity.java。Settings中的界面主要为Settings和SubSettings,都需要继承SettingsActivity。故数据更新在该类onResume中
SettingsActivity-onResume
SettingsActivity: @Override protected void onResume() { ... updateTilesList(); //1 } private void updateTilesList() { // Generally the items that are will be changing from these updates will // not be in the top list of tiles, so run it in the background and the // SettingsBaseActivity will pick up on the updates automatically. AsyncTask.execute(() -> doUpdateTilesList()); //2 } private void doUpdateTilesList() { // Final step, refresh categories. if (somethingChanged) { Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories " + changedList.toString()); mCategoryMixin.updateCategories(); //3 } else { Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call"); } } CategoryMixin: /** * Updates dashboard categories. */ public void updateCategories() { updateCategories(false /* fromBroadcast */); //4 } private void updateCategories(boolean fromBroadcast) { // Only allow at most 2 tasks existing at the same time since when the first one is // executing, there may be new data from the second update request. // Ignore the third update request because the second task is still waiting for the first // task to complete in a serial thread, which will get the latest data. if (mCategoriesUpdateTaskCount < 2) { new CategoriesUpdateTask().execute(fromBroadcast); //5 } } CategoryMixin$CategoriesUpdateTask: @Override protected Set<String> doInBackground(Boolean... params) { mPreviousTileMap = mCategoryManager.getTileByComponentMap(); mCategoryManager.reloadAllCategories(mContext); //6 mCategoryManager.updateCategoryFromDenylist(sTileDenylist); return getChangedCategories(params[0]); } CategoryManager: public synchronized void reloadAllCategories(Context context) { final boolean forceClearCache = mInterestingConfigChanges.applyNewConfig( context.getResources()); mCategories = null; tryInitCategories(context, forceClearCache); //7 forceClearCache用来保存Tile } private synchronized void tryInitCategories(Context context, boolean forceClearCache) { ... mCategories = TileUtils.getCategories(context, mTileByComponentCache); //8 ... } TileUtils: /** * Build a list of DashboardCategory. */ public static List<DashboardCategory> getCategories(Context context, Map<Pair<String, String>, Tile> cache) { ... loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true); //9 ... } static void loadTilesForAction(Context context, UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, boolean requireSettings) { final Intent intent = new Intent(action); if (requireSettings) { intent.setPackage(SETTING_PKG); } //10 loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent); loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent); } private static void loadActivityTiles(Context context, UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent) { final PackageManager pm = context.getPackageManager(); final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA, user.getIdentifier()); for (ResolveInfo resolved : results) { if (!resolved.system) { // Do not allow any app to add to settings, only system ones. continue; } final ActivityInfo activityInfo = resolved.activityInfo; final Bundle metaData = activityInfo.metaData; loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo); //11 } } private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData, ComponentInfo componentInfo) {} //12
2.2、插入Preference
DashboardFragment: @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { checkUiBlocker(mControllers); refreshAllPreferences(getLogTag());//1 ... } private void refreshAllPreferences(final String tag) { refreshDashboardTiles(tag); //2 } /** * Refresh preference items backed by DashboardCategory. */ private void refreshDashboardTiles(final String tag) { final PreferenceScreen screen = getPreferenceScreen(); //获取根布局PreferenceScreen //3 获取Category final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(getCategoryKey()); ... if (mDashboardTilePrefKeys.containsKey(key)) { // Have the key already, will rebind. final Preference preference = screen.findPreference(key); observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers( getActivity(), this, forceRoundedIcons, preference, tile, key, mPlaceholderPreferenceController.getOrder()); }else { // Don't have this key, add it. final Preference pref = createPreference(tile); observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers( getActivity(), this, forceRoundedIcons, pref, tile, key, mPlaceholderPreferenceController.getOrder()); screen.addPreference(pref); //4 添加Preference,即插入菜单 registerDynamicDataObservers(observers); mDashboardTilePrefKeys.put(key, observers); }