Android 7.0Settings加載主界面流程

新人一枚,沒有整機環境,有什麼寫的不對歡迎批評指正,萬分感謝!

Settings主界面加載時序圖(這裏很多判斷邏輯我省略掉了。更多的是想把加載主界面流程跑通。)


這張流程圖將主fragment DashboardSummary啓動,RecyclerView數據加載刷新 顯示得較爲明白。但是對於主界面tile分類,tile排序,tile對象屬性是無法得知的。所以接下我就主要講講這兩個。

(1)先看一下主界面佈局(用繪圖畫的,比較醜見諒)


這裏有一個值得學習的是RecycleView分類加載不同的佈局,值得學習。方法在DashboardAdapter.Java裏面。

主界面對象介紹:

(1)主界面(除了Suggestion,condition)其他對象都在List<DashBoardCagtory> Categories裏面

(2) Categories 有 4個對象。4個DashBoardCagtory的title值分別是 Wireless&networks,Device,Personal,System

(3)各個Cagtories都有List<Tile> tiles 是各個DashBoardCagtorytitle下面各個tile的集合

         組裝這些對象是時序圖中方法4,5,6,7,8。


5 getTilesForAction 

(1)組裝Intent對象

(2)調用方法6

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


6 getTilesForIntent

(1)利用PM查詢所有含有方法5生成的intent對象的ResolveInfo集合

(2)獲取集合中每一個Acitvity manifest配置的meta標籤name爲SETTINGS_ACTION的value值

(3)把activity name 和package  生成Intent對象

(4)將2步驟的值賦值給Tile  category

(5)獲取action爲SETTINGS_ACTION的intent-filter的priority屬性

(6)將解析meta-data標籤的bundle數據賦值給Tile metaData

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();
        List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                PackageManager.GET_META_DATA, user.getIdentifier());
        for (ResolveInfo resolved : results) {
            if (!resolved.system) {
                continue;
            }
            ActivityInfo activityInfo = resolved.activityInfo;
            Bundle metaData = activityInfo.metaData;
            String categoryKey = defaultCategory;
            if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
                    && categoryKey == null) {
                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;
                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);
            }
        }
    }


7 updateTileData

(1) 解析meta-data標籤 獲取name爲 META_DATA_PREFERENCE_ICON的value值賦值給icon

(2)解析meta-data標籤 獲取name爲 META_DATA_PREFERENCE_TITLE的resource值賦值給title

(3)解析meta-data標籤 獲取name爲 META_DATA_PREFERENCE_SUMMARY的value值賦值給summary

(4)如果title爲空就獲取acitvity標籤label屬性賦值給title

(5)如果icon等於0就獲取acitvity標籤icon屬性賦值給icon

  private static boolean updateTileData(Context context, Tile tile,
            ActivityInfo activityInfo, ApplicationInfo applicationInfo, PackageManager pm) {
        if (applicationInfo.isSystemApp()) {
            int icon = 0;
            CharSequence title = null;
            String summary = null;
            try {
                Resources res = pm.getResourcesForApplication(
                        applicationInfo.packageName);
                Bundle metaData = activityInfo.metaData;

                if (res != null && metaData != null) {
                    if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
                        icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
                    }
                    if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
                        if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
                            title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
                        } else {
                            title = metaData.getString(META_DATA_PREFERENCE_TITLE);
                        }
                    }
                    if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
                        if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
                            summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
                        } else {
                            summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
                        }
                    }
                }
            } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
                if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
            }
            if (TextUtils.isEmpty(title)) {
                title = activityInfo.loadLabel(pm).toString();
            }
            if (icon == 0) {
                icon = activityInfo.icon;
            }
            tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
            tile.title = title;
            tile.summary = summary;
            tile.intent = new Intent().setClassName(activityInfo.packageName,
                    activityInfo.name);

            return true;
        }

        return false;
    }


8 createCategory

(1)創建DashboardCategory對象

(2)利用PM查詢所有含有Tile對象categoriyKey生成的intent對象的ResolveInfo集合

(3)把acitivity label值賦值給category title屬性

(4)把解析intent-filter標籤的priority值賦值給category屬性

    private static DashboardCategory createCategory(Context context, String categoryKey) {
        DashboardCategory category = new DashboardCategory();
        category.key = categoryKey;
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
        if (results.size() == 0) {
            return null;
        }
        for (ResolveInfo resolved : results) {
            if (!resolved.system) {
                // Do not allow any app to add to settings, only system ones.
                continue;
            }
            category.title = resolved.activityInfo.loadLabel(pm);
            category.priority = SETTING_PKG.equals(
                    resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
            if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
        }

        return category;
    }


4 getCategories

1)調用5

2)新建categoryMap集合

3)遍歷tiles,判斷集合中是否有元素含有tile.category(當時我看見這個覺得google多此一舉每次這個對象都是需要new但是我後來整        manifest發現    含有相同categorytile很多。可以看我下面整理的manifest的表格)。如果沒有就執行方法8

4)將擁有相同屬性categorytile加入到對象DashboardCategory category對象的list<tile>集合中(我覺得google寫這個自己也蒙    蔽了。兩個相同名字,           一個是Tile裏面變量,一個是DashboardCategory對象)

5)將categoryMap的值賦值給List <DashboardCategory>cagtories以便執行排序算法

6)遍歷cagtories利用Collections函數和比較器TILE_COMPARATORcategory.tiles按照priority從大到小排序

7)利用Collections函數和比較器CATEGORY_COMPARATORcategories按照priority從大到小排序


public static List<DashboardCategory> getCategories(Context context,
            HashMap<Pair<String, String>, Tile> cache) {
        final long startTime = System.currentTimeMillis();
        boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
                != 0;
        ArrayList<Tile> tiles = new ArrayList<>();
        UserManager userManager = UserManager.get(context);
        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);
                getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
                        OPERATOR_DEFAULT_CATEGORY, tiles, false);
                getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
                        MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
            }
            if (setup) {
                getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
            }
        }
        HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
        for (Tile tile : tiles) {
            DashboardCategory category = categoryMap.get(tile.category);
            if (category == null) {
                category = createCategory(context, tile.category);
                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;
    }




圖表:tile



圖表:DashboardCategory


問題:summary是無法從manifest文件裏面獲取的。因爲不含有meta-data name屬性爲“com.android.settings.summary”的標籤。summary獲取方法是時序圖中方法16,17,18,31,32

16 new SummaryLoader

(1) 創建Handler對象
(2) 創建異步線程(大家可以去看一下線程優先級,做app還是挺有用處的)
(3) 遍歷categories裏面每一個DashboardCategory對象裏面集合Tiles裏面每一個tile對象
(4) 發送消息執行方法17

    public SummaryLoader(Activity activity, List<DashboardCategory> categories) {
        mHandler = new Handler();
        mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
        mWorkerThread.start();
        mWorker = new Worker(mWorkerThread.getLooper());
        mActivity = activity;
        for (int i = 0; i < categories.size(); i++) {
            List<Tile> tiles = categories.get(i).tiles;
            for (int j = 0; j < tiles.size(); j++) {
                Tile tile = tiles.get(j);
                mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
            }
        }
    }

17 makeProviderW

(1)執行方法18

(2)將方法18返回的SummaryProvider對象存入到mSummaryMap中

    private synchronized void makeProviderW(Tile tile) {
        SummaryProvider provider = getSummaryProvider(tile);
        if (provider != null) {
            if (DEBUG) Log.d(TAG, "Creating " + tile);
            mSummaryMap.put(provider, tile.intent.getComponent());
        }
    }


18 getSummaryProvider(真的是厲害了,我的歌)

(1)獲取title中的meataData變量

(2)將bundle數據metaData鍵爲SettingsActivity.META_DATA_KEY_FRAGMENT的alue值賦值給clsName

(3)根據clsName反射創建class

(4)獲取name爲SUMMARY_PROVIDER_FACORY的變量Field

(5)拿到Field的值強制轉換爲SummaryProviderFactory對象

(6)返回反射類中SummaryProvider對象

private SummaryProvider getSummaryProvider(Tile tile) {
        if (!mActivity.getPackageName().equals(tile.intent.getComponent().getPackageName())) {
            // Not within Settings, can't load Summary directly.
            // TODO: Load summary indirectly.
            return null;
        }
        Bundle metaData = getMetaData(tile);
        if (metaData == null) {
            if (DEBUG) Log.d(TAG, "No metadata specified for " + tile.intent.getComponent());
            return null;
        }
        String clsName = metaData.getString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
        if (clsName == null) {
            if (DEBUG) Log.d(TAG, "No fragment specified for " + tile.intent.getComponent());
            return null;
        }
        try {
            Class<?> cls = Class.forName(clsName);
            Field field = cls.getField(SUMMARY_PROVIDER_FACTORY);
            SummaryProviderFactory factory = (SummaryProviderFactory) field.get(null);
            return factory.createSummaryProvider(mActivity, this);
        } catch (ClassNotFoundException e) {
            if (DEBUG) Log.d(TAG, "Couldn't find " + clsName, e);
        } catch (NoSuchFieldException e) {
            if (DEBUG) Log.d(TAG, "Couldn't find " + SUMMARY_PROVIDER_FACTORY, e);
        } catch (ClassCastException e) {
            if (DEBUG) Log.d(TAG, "Couldn't cast " + SUMMARY_PROVIDER_FACTORY, e);
        } catch (IllegalAccessException e) {
            if (DEBUG) Log.d(TAG, "Couldn't get " + SUMMARY_PROVIDER_FACTORY, e);
        }
        return null;
    }

以NotificationApps.java爲例

public class NotificationApps extends ManageApplications {

    private static class SummaryProvider implements SummaryLoader.SummaryProvider {

        private final Context mContext;
        private final SummaryLoader mLoader;
        private final NotificationBackend mNotificationBackend;

        private SummaryProvider(Context context, SummaryLoader loader) {
            mContext = context;
            mLoader = loader;
            mNotificationBackend = new NotificationBackend();
        }

        @Override
        public void setListening(boolean listening) {
            if (listening) {
                new AppCounter(mContext) {
                    @Override
                    protected void onCountComplete(int num) {
                        updateSummary(num);
                    }

                    @Override
                    protected boolean includeInCount(ApplicationInfo info) {
                        return mNotificationBackend.getNotificationsBanned(info.packageName,
                                info.uid);
                    }
                }.execute();
            }
        }

        private void updateSummary(int count) {
            if (count == 0) {
                mLoader.setSummary(this, mContext.getString(R.string.notification_summary_none));
            } else {
                mLoader.setSummary(this, mContext.getResources().getQuantityString(
                        R.plurals.notification_summary, count, count));
            }
        }
    }

    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
            = new SummaryLoader.SummaryProviderFactory() {
        @Override
        public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
                                                                   SummaryLoader summaryLoader) {
            return new SummaryProvider(activity, summaryLoader);
        }
    };
}



31 setListening

(1) 移除消息Worker.MSG_SET_LISTENING
(2) 發送消息Worker.MSG_SET_LISTENING執行方法32

public void setListening(boolean listening) {
        if (mListening == listening) return;
        mListening = listening;
        // Unregister listeners immediately.
        for (int i = 0; i < mReceivers.size(); i++) {
            mActivity.unregisterReceiver(mReceivers.valueAt(i));
        }
        mReceivers.clear();
        mWorker.removeMessages(Worker.MSG_SET_LISTENING);
        mWorker.obtainMessage(Worker.MSG_SET_LISTENING, listening ? 1 : 0, 0).sendToTarget();
    }


32 setListeningW(注意鎖)

(1)遍歷方法18生成的mSummaryMap鍵SummaryProvider

(2)將反射fragment類中靜態內部類執行setListening方法

    private synchronized void setListeningW(boolean listening) {
        if (mWorkerListening == listening) return;
        mWorkerListening = listening;
        if (DEBUG) Log.d(TAG, "Listening " + listening);
        for (SummaryProvider p : mSummaryMap.keySet()) {
            try {
                p.setListening(listening);
            } catch (Exception e) {
                Log.d(TAG, "Problem in setListening", e);
            }
        }
    }
到此界面第一次刷新完成。Suggestion這個類我還沒研究透徹。所以就沒有畫到時序圖裏面。主要類是SuggestionParser.java

再介紹一下Settings.java的作用


正常我們寫很多activity。是不是該這樣寫:

for(int i = 1 ; i <=需要新建activity的個數;i++) {
   新建class Activityi extends SettingsActivity
}

這樣做沒有什麼不對,也不會對性能有影響。但是SettingsActivity真的是爸爸,兒子們需要做的都被爸爸幹完了,所以你會發現許多空白文件。

Google就利用java靜態內部類的機制(可以去百度一下)編譯就會生成他的宿主類名$靜態內部類名 也就是我們manifest看見類似於Settings$WirelessSettings。這樣寫就避免大量白癡文件。


Google 這次7.0Settings加載邏輯大改其實對於新界面開發更容易了。只要去manifest將你的activity屬性配置好。Settings.java聲明。目前代碼邏輯就能直接把你界面加載完畢。然後你去對應的fragment實現邏輯。這樣新界面開發基本不需要改動現有邏輯。真的是厲害了,我的歌!






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章