O版本的设置界面相对有N有了一些变化,O上面增加了顶级类别的菜单,而之前一些在一级菜单的则移动到了二级界面里面,

如"WIFI","移动网络"等之前是在一级界面的,而在O上则移动到了新菜单"网络和互联网"中,但是在数据在加载方面,并未做较大的变化.

(a)一级界面--顶级菜单的数据加载

    在上一篇 <<android O版本 设置(Settings)模块总结--设置的启动界面选择>> 中有说到DashboardSummary是顶级菜单的容器,那么数据的获取和它也就有关系了:

    DashboardSummary.java

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Activity activity = getActivity();
        mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
                .getDashboardFeatureProvider(activity);
        mSuggestionFeatureProvider = FeatureFactory.getFactory(activity)
                .getSuggestionFeatureProvider(activity);

        mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);

        mConditionManager = ConditionManager.get(activity, false);
        getLifecycle().addObserver(mConditionManager);
        mSuggestionParser = new SuggestionParser(activity,
                activity.getSharedPreferences(SUGGESTIONS, 0), R.xml.suggestion_ordering);
        mSuggestionsChecks = new SuggestionsChecks(getContext());
    }

在DashboardSummary的onCreate函数中有获取的有两个很重要参数:mDashboardFeatureProvider,mSuggestionFeatureProvider.这两个是主要的数据提供者,mSuggestionFeatureProvider和mDashboardFeatureProvider的数据获取是有所不同的,这里就不再说明mSuggestionFeatureProvider了,重点说明下mDashboardFeatureProvider.

mDashboardFeatureProvider提供的数据是一级菜单如"电池","显示","网络和互联网"等,实现类为DashboardFeatureProviderImpl.java,而DashboardFeatureProviderImpl中的具体的数据是通过函数getTilesForCategory()从CategoryManager获取的.

public DashboardFeatureProviderImpl(Context context) {
        mContext = context.getApplicationContext();
        mCategoryManager = CategoryManager.get(context, getExtraIntentAction());
        mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
        mPackageManager = context.getPackageManager();
    }

    @Override
    public DashboardCategory getTilesForCategory(String key) {
        return mCategoryManager.getTilesByCategory(mContext, key);
    }

 

CategoryManager是SettingsLib这个静态包中公共类,可以看一下:

 

public static CategoryManager get(Context context, String action) {
        if (sInstance == null) {
            sInstance = new CategoryManager(context, action);
        }
        return sInstance;
    }

    CategoryManager(Context context, String action) {
        mTileByComponentCache = new ArrayMap<>();
        mCategoryByKeyMap = new ArrayMap<>();
        mInterestingConfigChanges = new InterestingConfigChanges();
        mInterestingConfigChanges.applyNewConfig(context.getResources());
        mExtraAction = action;
    }

可以看到CategoryManager是一个单例类型,这里就是真正的数据加载位置,加载是通过函数reloadAllCategories()调用tryInitCategories()获取的.

到这里为止,整个数据获取的流程已经清楚,但是数据加载是在哪里完成的呢,还要回到SettingsDrawerActivity中:

SettingsDrawerActivity.java
    @Override
    protected void onResume() {
        super.onResume();
        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
        filter.addDataScheme("package");
        registerReceiver(mPackageReceiver, filter);

        new CategoriesUpdateTask().execute();
        final Intent intent = getIntent();
        if (intent != null && intent.getBooleanExtra(EXTRA_SHOW_MENU, false)) {
            // Intent explicitly set to show menu.
            showMenuIcon();
        }
    }

 

在SettingsDrawerActivity中注册了安装应用状态变化的广播接收器等,但是这里还进行了一个异步操作:

 

new CategoriesUpdateTask().execute();

private class CategoriesUpdateTask extends AsyncTask<Void, Void, Void> {

        private final CategoryManager mCategoryManager;

        public CategoriesUpdateTask() {
            mCategoryManager = CategoryManager.get(SettingsDrawerActivity.this);
        }

        @Override
        protected Void doInBackground(Void... params) {
            mCategoryManager.reloadAllCategories(SettingsDrawerActivity.this, getSettingPkg());
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            mCategoryManager.updateCategoryFromBlacklist(sTileBlacklist);
            onCategoriesChanged();
        }
    }

在这个AsyncTask中,doInBackground()调用了CategoryManager的reloadAllCategories()函数,而onPostExecute则调用了接口CategoryListener的唯一方法onCategoriesChanged(),那么作为界面容器的DashboardSummary肯定重载了这个接口,实现了onCategoriesChanged()方法:

 

DashboardSummary
     @Override
    public void onCategoriesChanged() {
        // Bypass rebuildUI() on the first call of onCategoriesChanged, since rebuildUI() happens
        // in onViewCreated as well when app starts. But, on the subsequent calls we need to
        // rebuildUI() because there might be some changes to suggestions and categories.
        if (isOnCategoriesChangedCalled) {
            rebuildUI();
        }
        isOnCategoriesChangedCalled = true;
    }

DashboardSummary在方法onCategoriesChanged()中进行了界面的刷新,这里先不了解,后边再说,继续研究数据加载.

mCategoryManager.reloadAllCategories(SettingsDrawerActivity.this, getSettingPkg());

reloadAllCategories函数中调用了tryInitCategories(),此函数中是获取数据以及对数据的处理,来看下函数tryInitCategories:

private synchronized void tryInitCategories(Context context, boolean forceClearCache,
            String settingPkg) {
        if (mCategories == null) {
            if (forceClearCache) {
                mTileByComponentCache.clear();
            }
            //清除缓存
            mCategoryByKeyMap.clear();
            //数据获取
            mCategories = TileUtils.getCategories(context, mTileByComponentCache,
                    false /* categoryDefinedInManifest */, mExtraAction, settingPkg);
            //数据保存
            for (DashboardCategory category : mCategories) {
                mCategoryByKeyMap.put(category.key, category);
            }
            backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
            //数据排序清理重复等
            sortCategories(context, mCategoryByKeyMap);
            filterDuplicateTiles(mCategoryByKeyMap);
        }
    }


 


  SettingsLib/src/com/android/settingslib/drawer/TileUtils.java

public static List<DashboardCategory> getCategories(Context context,
            Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,
            String extraAction, String settingPkg) {
        final long startTime = System.currentTimeMillis();
        boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
                != 0;
        ArrayList<Tile> tiles = new ArrayList<>();
        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
        //此处是整个数据的获取
        for (UserHandle user : userManager.getUserProfiles()) {
            // TODO: Needs much optimization, too many PM queries going on here.
            if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
                // Only add Settings for this user.
                getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true,
                        settingPkg);
                getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
                        OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
                getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
                        MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
            }
            if (setup) {
                getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false,
                        settingPkg);
                if (!categoryDefinedInManifest) {
                    getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false,
                            settingPkg);
                    if (extraAction != null) {
                        getTilesForAction(context, user, extraAction, cache, null, tiles, false,
                                settingPkg);
                    }
                }
            }
        }
        //按照进行meta-data android:name="com.android.settings.category"进行分类
        HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
        for (Tile tile : tiles) {
            DashboardCategory category = categoryMap.get(tile.category);
            if (category == null) {
                category = createCategory(context, tile.category, categoryDefinedInManifest);
                if (category == null) {
                    Log.w(LOG_TAG, "Couldn't find category " + tile.category);
                    continue;
                }
                categoryMap.put(category.key, category);
            }
            category.addTile(tile);
        }
        //对分类进行排序
        ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
        for (DashboardCategory category : categories) {
            Collections.sort(category.tiles, TILE_COMPARATOR);
        }
        Collections.sort(categories, CATEGORY_COMPARATOR);
        if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
                + (System.currentTimeMillis() - startTime) + " ms");
        return categories;
    }

在函数getCategories中,就逐个开始通过PackageManager获取包含有指定Action:

 

private static final String SETTINGS_ACTION =
            "com.android.settings.action.SETTINGS";

    private static final String OPERATOR_SETTINGS =
            "com.android.settings.OPERATOR_APPLICATION_SETTING";

    private static final String MANUFACTURER_SETTINGS =
            "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
    private static final String EXTRA_SETTINGS_ACTION =

            "com.android.settings.action.EXTRA_SETTINGS";

    private static void getTilesForAction(Context context,
            UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
            String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,
            boolean usePriority, String settingPkg) {
        //填充Intent
        Intent intent = new Intent(action);
        if (requireSettings) {
            intent.setPackage(settingPkg);
        }
        getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
                usePriority, true);
    }

    public static void getTilesForIntent(Context context, UserHandle user, Intent intent,
            Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
            boolean usePriority, boolean checkCategory) {
        PackageManager pm = context.getPackageManager();
        //通过PM 在已经安装的应用中获取指定Action的Activity信息.
        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;
            }
            ActivityInfo activityInfo = resolved.activityInfo;
            Bundle metaData = activityInfo.metaData;
            String categoryKey = defaultCategory;

            //通过meta-data android:name="com.android.settings.category"获取菜单分类.
            //例如:com.android.settings.category.ia.homepage就是在一级目录
            //例如:com.android.settings.category.ia.device显示在顶级菜单"设备链接"中

            // Load category
            if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
                    && categoryKey == null) {
                Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
                        + intent + " missing metadata "
                        + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
                continue;
            } else {
                categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
            }
            
            Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
                    activityInfo.name);
            Tile tile = addedCache.get(key);
            if (tile == null) {
                tile = new Tile();
                tile.intent = new Intent().setClassName(
                        activityInfo.packageName, activityInfo.name);
                tile.category = categoryKey;
                tile.priority = usePriority ? resolved.priority : 0;
                tile.metaData = activityInfo.metaData;
                //菜单的标题,图标,概要(summary),对应Activity的获取和赋值,这个可以自行分析下
                updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
                        pm);
                if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);

                addedCache.put(key, tile);
            }
            if (!tile.userHandle.contains(user)) {
                tile.userHandle.add(user);
            }
            if (!outTiles.contains(tile)) {
                outTiles.add(tile);
            }
        }
    }

 

使非设置的应用中添加设置顶级菜单,需要添加一下activity的相关的属性,下面例子可以参考一下:
 

<activity
                   android:name="Activity"
                   android:label="@string/app_name"
             <intent-filter android:priority="6">
                 <action android:name="com.android.settings.action.EXTRA_SETTINGS" />
             </intent-filter>
             <meta-data
                 android:name="com.android.settings.category"
                 android:value="com.android.settings.category.ia.homepage" />
             <meta-data
                 android:name="com.android.settings.icon"
                 android:resource="@drawable/ic_launcher_setting" />
         </activity>

到了这里,数据加载流程基本上就总结完了,下面就要开始进行界面的加载了,要回到之前提到过的DashboardSummary的onCategoriesChanged()方法了.

(b)一级菜单界面加载,此处就比较简单了,是对View的绑定,我只把代码逻辑从头到位贴一下了   >.<

/src/com/android/settings/dashboard/DashboardSummary.java

@Override
    public void onCategoriesChanged() {
        // Bypass rebuildUI() on the first call of onCategoriesChanged, since rebuildUI() happens
        // in onViewCreated as well when app starts. But, on the subsequent calls we need to
        // rebuildUI() because there might be some changes to suggestions and categories.
        if (isOnCategoriesChangedCalled) {
            rebuildUI();
        }
        isOnCategoriesChangedCalled = true;
    }

DashboardSummary.rebuildUI

@VisibleForTesting
    void rebuildUI() {
        if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {
            Log.d(TAG, "Suggestion feature is disabled, skipping suggestion entirely");
            updateCategoryAndSuggestion(null /* tiles */);
        } else {
            new SuggestionLoader().execute();
            // Set categories on their own if loading suggestions takes too long.
            mHandler.postDelayed(() -> {
                updateCategoryAndSuggestion(null /* tiles */);
            }, MAX_WAIT_MILLIS);
        }
    }

DashboardSummary.updateCategoryAndSuggestion

@VisibleForTesting
    void updateCategoryAndSuggestion(List<Tile> suggestions) {
        final Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
                CategoryKey.CATEGORY_HOMEPAGE);
        mSummaryLoader.updateSummaryToCache(category);
        if (suggestions != null) {
            mAdapter.setCategoriesAndSuggestions(category, suggestions);
        } else {
            mAdapter.setCategory(category);
        }
    }

./src/com/android/settings/dashboard/DashboardAdapter.java

public void setCategoriesAndSuggestions(DashboardCategory category,
            List<Tile> suggestions) {
        tintIcons(category, suggestions);

        final DashboardData prevData = mDashboardData;
        mDashboardData = new DashboardData.Builder(prevData)
                .setSuggestions(suggestions.subList(0,
                        Math.min(suggestions.size(), MAX_SUGGESTION_TO_SHOW)))
                .setCategory(category)
                .build();
        notifyDashboardDataChanged(prevData);
        List<Tile> shownSuggestions = null;
        final int mode = mDashboardData.getSuggestionConditionMode();
        if (mode == DashboardData.HEADER_MODE_DEFAULT) {
            shownSuggestions = suggestions.subList(0,
                    Math.min(suggestions.size(), DashboardData.DEFAULT_SUGGESTION_COUNT));
        } else if (mode != DashboardData.HEADER_MODE_COLLAPSED) {
            shownSuggestions = suggestions;
        }
        if (shownSuggestions != null) {
            for (Tile suggestion : shownSuggestions) {
                final String identifier = mSuggestionFeatureProvider.getSuggestionIdentifier(
                        mContext, suggestion);
                mMetricsFeatureProvider.action(
                        mContext, MetricsEvent.ACTION_SHOW_SETTINGS_SUGGESTION, identifier,
                        getSuggestionTaggedData());
                mSuggestionsShownLogged.add(identifier);
            }
        }
    }
    public void setCategory(DashboardCategory category) {
        tintIcons(category, null);
        final DashboardData prevData = mDashboardData;
        Log.d(TAG, "adapter setCategory called");
        mDashboardData = new DashboardData.Builder(prevData)
                .setCategory(category)
                .build();
        notifyDashboardDataChanged(prevData);
    }
@Override
    public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
        if (viewType == R.layout.suggestion_condition_header) {
            return new SuggestionAndConditionHeaderHolder(view);
        }
        if (viewType == R.layout.suggestion_condition_container) {
            return new SuggestionAndConditionContainerHolder(view);
        }
        return new DashboardItemHolder(view);
    }

    @Override
    public void onBindViewHolder(DashboardItemHolder holder, int position) {
        final int type = mDashboardData.getItemTypeByPosition(position);
        switch (type) {
            case R.layout.dashboard_tile:
                final Tile tile = (Tile) mDashboardData.getItemEntityByPosition(position);
                onBindTile(holder, tile);
                holder.itemView.setTag(tile);
                holder.itemView.setOnClickListener(mTileClickListener);
                break;
            case R.layout.suggestion_condition_container:
                onBindConditionAndSuggestion(
                        (SuggestionAndConditionContainerHolder) holder, position);
                break;
            case R.layout.suggestion_condition_header:
                onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder) holder,
                        (SuggestionConditionHeaderData)
                                mDashboardData.getItemEntityByPosition(position));
                break;
            case R.layout.suggestion_condition_footer:
                holder.itemView.setOnClickListener(v -> {
                    mMetricsFeatureProvider.action(mContext,
                            MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);
                    DashboardData prevData = mDashboardData;
                    mDashboardData = new DashboardData.Builder(prevData).setSuggestionConditionMode(
                            DashboardData.HEADER_MODE_COLLAPSED).build();
                    notifyDashboardDataChanged(prevData);
                    mRecyclerView.scrollToPosition(SUGGESTION_CONDITION_HEADER_POSITION);
                });
                break;
        }
    }