Settings中动态插入菜单

原文链接:https://blog.csdn.net/wangwei6227/article/details/130935976

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);
            }

3、增加搜索

继承SearchIndexablesProvider(framework/./base/core/java/android/provider/SearchIndexablesProvider.java)

可在设置中增加搜索

<provider
            android:name=".search.SettingsSearchIndexablesProvider"
            android:authorities="com.android.settings"
            android:multiprocess="false"
            android:grantUriPermissions="true"
            android:permission="android.permission.READ_SEARCH_INDEXABLES"
            android:exported="true">
            <intent-filter>
                <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
            </intent-filter>
        </provider>

分析SearchIndexablesProvider源码,主要重写以下几个方法:

queryXmlResources   返回xml(android.provider.SearchIndexablesContract.XmlResource)资源类型的菜单信息
queryRawData     返回RawData(android.provider.SearchIndexablesContract.RawData)菜单元数据
queryNonIndexableKeys   返回不加入搜索的菜单key信息
queryDynamicRawData   返回动态菜单元数据

   @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {
        try {
            switch (mMatcher.match(uri)) {
                case MATCH_RES_CODE:
                    return queryXmlResources(null);
                case MATCH_RAW_CODE:
                    return queryRawData(null);
                case MATCH_NON_INDEXABLE_KEYS_CODE:
                    return queryNonIndexableKeys(null);
                case MATCH_SITE_MAP_PAIRS_CODE:
                    return querySiteMapPairs();
                case MATCH_SLICE_URI_PAIRS_CODE:
                    return querySliceUriPairs();
                case MATCH_DYNAMIC_RAW_CODE:
                    return queryDynamicRawData(null);
                default:
                    throw new UnsupportedOperationException("Unknown Uri " + uri);
            }
        } catch (UnsupportedOperationException e) {
            throw e;
        } catch (Exception e) {
            Log.e(TAG, "Provider querying exception:", e);
            return null;
        }
    }

                       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值