Setting 主界面加载以及外部注入
Settings的入口是SettingsActivity
protected void onCreate(Bundle savedState) {
...
if (savedState != null) {
// We are restarting from a previous saved state; used that to initialize, instead
280 // of starting fresh.
281 setTitleFromIntent(intent);
282
283 ArrayList<DashboardCategory> categories =
284 savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);
285 if (categories != null) {
286 mCategories.clear();
287 mCategories.addAll(categories);
288 setTitleFromBackStack();
289 }
290 } else {
291 launchSettingFragment(initialFragmentName, isSubSettings, intent);
292 }
...
}
我们看到SettingActivity的onCreate函数,初始化时savedState为空,所以我们直接看到launchSettingFragment。
382 void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {
383 if (!mIsShowingDashboard && initialFragmentName != null) {
384 setTitleFromIntent(intent);
385
386 Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
387 switchToFragment(initialFragmentName, initialArguments, true, false,
388 mInitialTitleResId, mInitialTitle, false);
389 } else {
390 // Show search icon as up affordance if we are displaying the main Dashboard
391 mInitialTitleResId = R.string.dashboard_title;
392
393 switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,
394 mInitialTitleResId, mInitialTitle, false);
395 }
396 }
switchTofragment会调用dashboardSummary这个类,我们直接看到DashboardSummary的 onCreateView方法。
196 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
197 long startTime = System.currentTimeMillis();
198 final View root = inflater.inflate(R.layout.dashboard, container, false);
199 mDashboard = root.findViewById(R.id.dashboard_container);
200 mLayoutManager = new LinearLayoutManager(getContext());
201 mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
202 if (bundle != null) {
203 int scrollPosition = bundle.getInt(STATE_SCROLL_POSITION);
204 mLayoutManager.scrollToPosition(scrollPosition);
205 }
206 mDashboard.setLayoutManager(mLayoutManager);
207 mDashboard.setHasFixedSize(true);
208 mDashboard.setListener(this);
209 mDashboard.setItemAnimator(new DashboardItemAnimator());
210 mAdapter = new DashboardAdapter(getContext(), bundle,
211 mConditionManager.getConditions(), mSuggestionControllerMixin, getLifecycle());
212 mDashboard.setAdapter(mAdapter);
213 mSummaryLoader.setSummaryConsumer(mAdapter);
214 ActionBarShadowController.attachToRecyclerView(
215 getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);
216 rebuildUI();
217 if (DEBUG_TIMING) {
218 Log.d(TAG, "onCreateView took "
219 + (System.currentTimeMillis() - startTime) + " ms");
220 }
221 return root;
222 }
这里为fragment设置一些基础属性,像监听器、适配器等。而数据的核心加载在rebuildUI处。
225 void rebuildUI() {
226 ThreadUtils.postOnBackgroundThread(() -> updateCategory());
227 }
......
273 void updateCategory() {
274 final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
275 CategoryKey.CATEGORY_HOMEPAGE);
276 mSummaryLoader.updateSummaryToCache(category);
277 mStagingCategory = category;
278 if (mSuggestionControllerMixin == null) {
279 ThreadUtils.postOnMainThread(() -> mAdapter.setCategory(mStagingCategory));
280 return;
281 }
282 if (mSuggestionControllerMixin.isSuggestionLoaded()) {
283 Log.d(TAG, "Suggestion has loaded, setting suggestion/category");
284 ThreadUtils.postOnMainThread(() -> {
285 if (mStagingSuggestions != null) {
286 mAdapter.setSuggestions(mStagingSuggestions);
287 }
288 mAdapter.setCategory(mStagingCategory);
289 });
290 } else {
291 Log.d(TAG, "Suggestion NOT loaded, delaying setCategory by " + MAX_WAIT_MILLIS + "ms");
292 mHandler.postDelayed(() -> mAdapter.setCategory(mStagingCategory), MAX_WAIT_MILLIS);
293 }
294 }
295}
我们先看一下CATEGORY_HOMEPAGE的值:
public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";
这是activity的meta-data中的值,settings会根据此字段筛选出要显示到主界面的activity。
getTilesByCategory方法我们先等等再看,直接来到CategoryManager#tryInitCategories
120 private synchronized void tryInitCategories(Context context, boolean forceClearCache,
121 String settingPkg) {
122 if (mCategories == null) {
123 if (forceClearCache) {
124 mTileByComponentCache.clear();
125 }
126 mCategoryByKeyMap.clear();
127 mCategories = TileUtils.getCategories(context, mTileByComponentCache,
128 false /* categoryDefinedInManifest */, mExtraAction, settingPkg);
129 for (DashboardCategory category : mCategories) {
130 mCategoryByKeyMap.put(category.key, category);
131 }
132 backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
133 sortCategories(context, mCategoryByKeyMap);
134 filterDuplicateTiles(mCategoryByKeyMap);
135 }
136 }
我们返回到dashboardsummary的就是这个mCategoryByKeyMap,所以我们直接看TileUtils#getCategories这个方法就行了。
215 public static List<DashboardCategory> getCategories(Context context,
216 Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,
217 String extraAction, String settingPkg) {
218 final long startTime = System.currentTimeMillis();
219 boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)
220 != 0;
221 ArrayList<Tile> tiles = new ArrayList<>();
222 UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
223 for (UserHandle user : userManager.getUserProfiles()) {
224 // TODO: Needs much optimization, too many PM queries going on here.
225 if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
226 // Only add Settings for this user.
227 getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true,
228 settingPkg);
229 getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
230 OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
231 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
232 MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
233 }
234 if (setup) {
235 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false,
236 settingPkg);
237 if (!categoryDefinedInManifest) {
238 getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false,
239 settingPkg);
240 if (extraAction != null) {
241 getTilesForAction(context, user, extraAction, cache, null, tiles, false,
242 settingPkg);
243 }
244 }
245 }
246 }
247
248 HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
249 for (Tile tile : tiles) {
250 DashboardCategory category = categoryMap.get(tile.category);
251 if (category == null) {
252 category = createCategory(context, tile.category, categoryDefinedInManifest);
253 if (category == null) {
254 Log.w(LOG_TAG, "Couldn't find category " + tile.category);
255 continue;
256 }
257 categoryMap.put(category.key, category);
258 }
259 category.addTile(tile);
260 }
261 ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
262 for (DashboardCategory category : categories) {
263 category.sortTiles();
264 }
265 Collections.sort(categories, CATEGORY_COMPARATOR);
266 if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
267 + (System.currentTimeMillis() - startTime) + " ms");
268 return categories;
269 }
我们首先会根据activity的action进行筛选,下列是筛选条件的值:
68 public static final String EXTRA_SETTINGS_ACTION =
69 "com.android.settings.action.EXTRA_SETTINGS";
74 private static final String IA_SETTINGS_ACTION =
75 "com.android.settings.action.IA_SETTINGS";
81 private static final String SETTINGS_ACTION =
82 "com.android.settings.action.SETTINGS";
84 private static final String OPERATOR_SETTINGS =
85 "com.android.settings.OPERATOR_APPLICATION_SETTING";
90 private static final String MANUFACTURER_SETTINGS =
91 "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
92
Settings里面的activity基本上用的是SETTINGS,如果要外部注入的话我们一般用EXTRA_SETTINGS。traceur用的是IA_SETTINGS,不过我还没研究过具体区别。接下来我们直接看到getTilesForIntent方法:
public static void getTilesForIntent(
334 Context context, UserHandle user, Intent intent,
335 Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
336 boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,
337 boolean shouldUpdateTiles) {
338 PackageManager pm = context.getPackageManager();
339 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
340 PackageManager.GET_META_DATA, user.getIdentifier());
341 Map<String, IContentProvider> providerMap = new HashMap<>();
342 for (ResolveInfo resolved : results) {
343 if (!resolved.system) {
344 // Do not allow any app to add to settings, only system ones.
345 continue;
346 }
347 ActivityInfo activityInfo = resolved.activityInfo;
348 Bundle metaData = activityInfo.metaData;
349 String categoryKey = defaultCategory;
350
351 // Load category
352 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
353 && categoryKey == null) {
354 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
355 + intent + " missing metadata "
356 + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
357 continue;
358 } else {
359 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
360 }
361
362 Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
363 activityInfo.name);
364 Tile tile = addedCache.get(key);
365 if (tile == null) {
366 tile = new Tile();
367 tile.intent = new Intent().setClassName(
368 activityInfo.packageName, activityInfo.name);
369 tile.category = categoryKey;
370 tile.priority = usePriority ? resolved.priority : 0;
371 tile.metaData = activityInfo.metaData;
372 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
373 pm, providerMap, forceTintExternalIcon);
374 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
375 addedCache.put(key, tile);
376 } else if (shouldUpdateTiles) {
377 updateSummaryAndTitle(context, providerMap, tile);
378 }
379
380 if (!tile.userHandle.contains(user)) {
381 tile.userHandle.add(user);
382 }
383 if (!outTiles.contains(tile)) {
384 outTiles.add(tile);
385 }
386 }
387 }
我们首先通过pms获取所有包的信息。
338 PackageManager pm = context.getPackageManager();
339 List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
340 PackageManager.GET_META_DATA, user.getIdentifier());
settings只接受系统应用外部注入到settings中
343 if (!resolved.system) {
344 // Do not allow any app to add to settings, only system ones.
345 continue;
346 }
我们接着往下看
352 if (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
353 && categoryKey == null) {
354 Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "
355 + intent + " missing metadata "
356 + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
357 continue;
358 } else {
359 categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
360 }
105 private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
当metadata中必须有 "com.android.settings.category"时,才会将该activity存进map中。后面是给tile设置一些基本属性,这里我们注意需要注意一下的是设置优先级的位置:
tile.priority = usePriority ? resolved.priority : 0;
这个我们需要回到最开始筛选action时:
225 if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
226 // Only add Settings for this user.
227 getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true,
228 settingPkg);
229 getTilesForAction(context, user, OPERATOR_SETTINGS, cache,
230 OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
231 getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
232 MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
233 }
234 if (setup) {
235 getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false,
236 settingPkg);
237 if (!categoryDefinedInManifest) {
238 getTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false,
239 settingPkg);
240 if (extraAction != null) {
241 getTilesForAction(context, user, extraAction, cache, null, tiles, false,
242 settingPkg);
243 }
244 }
245 }
246 }
我们看倒数第二个参数就行了,外部注入的usePriority都是false。所以外部注入的应用的优先级都为0,排在界面最下面。
回到getCategories方法:
248 HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
249 for (Tile tile : tiles) {
250 DashboardCategory category = categoryMap.get(tile.category);
251 if (category == null) {
252 category = createCategory(context, tile.category, categoryDefinedInManifest);
253 if (category == null) {
254 Log.w(LOG_TAG, "Couldn't find category " + tile.category);
255 continue;
256 }
257 categoryMap.put(category.key, category);
258 }
259 category.addTile(tile);
260 }
261 ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
262 for (DashboardCategory category : categories) {
263 category.sortTiles();
264 }
265 Collections.sort(categories, CATEGORY_COMPARATOR);
这里将我们刚刚获得到的tiles装到category中,然后对categories从大到小排列。
我们回到跳过没看的getTilesByCategory方法。
76 public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey,
77 String settingPkg) {
78 tryInitCategories(context, settingPkg);
79
80 return mCategoryByKeyMap.get(categoryKey);
81 }
这里会通过"com.android.settings.category.ia.homepage"去查询我们刚刚插入的mCategoryByKeyMap。
主界面的加载到这就差不多了。
二级界面以及外部注入
我们接着getTilesByCategory继续讲一下,我们的子界面外部注入加载流程和主界面其实是一样的,都是通过这个接口,只不过使用的categoryKey不一样。子界面是通过DashboardFragment来管理的。
DashboardFragment#refreshDashboardTiles
333 void refreshDashboardTiles(final String TAG) {
334 final PreferenceScreen screen = getPreferenceScreen();
335
336 final DashboardCategory category =
337 mDashboardFeatureProvider.getTilesForCategory(getCategoryKey());
......
399 }
233 public String getCategoryKey() {
234 return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
235 }
这里通过类名获取对应的category值
64 PARENT_TO_CATEGORY_KEY_MAP.put(
65 NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
66 PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),
67 CategoryKey.CATEGORY_CONNECT);
68 PARENT_TO_CATEGORY_KEY_MAP.put(AdvancedConnectedDeviceDashboardFragment.class.getName(),
69 CategoryKey.CATEGORY_DEVICE);
70 PARENT_TO_CATEGORY_KEY_MAP.put(AppAndNotificationDashboardFragment.class.getName(),
71 CategoryKey.CATEGORY_APPS);
72 PARENT_TO_CATEGORY_KEY_MAP.put(PowerUsageSummary.class.getName(),
73 CategoryKey.CATEGORY_BATTERY);
74 PARENT_TO_CATEGORY_KEY_MAP.put(DefaultAppSettings.class.getName(),
75 CategoryKey.CATEGORY_APPS_DEFAULT);
76 PARENT_TO_CATEGORY_KEY_MAP.put(DisplaySettings.class.getName(),
77 CategoryKey.CATEGORY_DISPLAY);
78 PARENT_TO_CATEGORY_KEY_MAP.put(SoundSettings.class.getName(),
79 CategoryKey.CATEGORY_SOUND);
80 PARENT_TO_CATEGORY_KEY_MAP.put(StorageDashboardFragment.class.getName(),
81 CategoryKey.CATEGORY_STORAGE);
82 PARENT_TO_CATEGORY_KEY_MAP.put(SecuritySettings.class.getName(),
83 CategoryKey.CATEGORY_SECURITY);
84 PARENT_TO_CATEGORY_KEY_MAP.put(AccountDetailDashboardFragment.class.getName(),
85 CategoryKey.CATEGORY_ACCOUNT_DETAIL);
86 PARENT_TO_CATEGORY_KEY_MAP.put(AccountDashboardFragment.class.getName(),
87 CategoryKey.CATEGORY_ACCOUNT);
88 PARENT_TO_CATEGORY_KEY_MAP.put(
89 SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
90 PARENT_TO_CATEGORY_KEY_MAP.put(LanguageAndInputSettings.class.getName(),
91 CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
92 PARENT_TO_CATEGORY_KEY_MAP.put(DevelopmentSettingsDashboardFragment.class.getName(),
93 CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
94 PARENT_TO_CATEGORY_KEY_MAP.put(ConfigureNotificationSettings.class.getName(),
95 CategoryKey.CATEGORY_NOTIFICATIONS);
96 PARENT_TO_CATEGORY_KEY_MAP.put(LockscreenDashboardFragment.class.getName(),
97 CategoryKey.CATEGORY_SECURITY_LOCKSCREEN);
98 PARENT_TO_CATEGORY_KEY_MAP.put(ZenModeSettings.class.getName(),
99 CategoryKey.CATEGORY_DO_NOT_DISTURB);
100 PARENT_TO_CATEGORY_KEY_MAP.put(GestureSettings.class.getName(),
101 CategoryKey.CATEGORY_GESTURES);
102 PARENT_TO_CATEGORY_KEY_MAP.put(NightDisplaySettings.class.getName(),
103 CategoryKey.CATEGORY_NIGHT_DISPLAY);
这里是不同的子页面的category
24 public static final String CATEGORY_HOMEPAGE = "com.android.settings.category.ia.homepage";
25
26 // Top level category.
27 public static final String CATEGORY_NETWORK = "com.android.settings.category.ia.wireless";
28 public static final String CATEGORY_CONNECT = "com.android.settings.category.ia.connect";
29 public static final String CATEGORY_DEVICE = "com.android.settings.category.ia.device";
30 public static final String CATEGORY_APPS = "com.android.settings.category.ia.apps";
31 public static final String CATEGORY_APPS_DEFAULT =
32 "com.android.settings.category.ia.apps.default";
33 public static final String CATEGORY_BATTERY = "com.android.settings.category.ia.battery";
34 public static final String CATEGORY_DISPLAY = "com.android.settings.category.ia.display";
35 public static final String CATEGORY_SOUND = "com.android.settings.category.ia.sound";
36 public static final String CATEGORY_STORAGE = "com.android.settings.category.ia.storage";
37 public static final String CATEGORY_SECURITY = "com.android.settings.category.ia.security";
38 public static final String CATEGORY_SECURITY_LOCKSCREEN =
39 "com.android.settings.category.ia.lockscreen";
40 public static final String CATEGORY_ACCOUNT = "com.android.settings.category.ia.accounts";
41 public static final String CATEGORY_ACCOUNT_DETAIL =
42 "com.android.settings.category.ia.account_detail";
43 public static final String CATEGORY_SYSTEM = "com.android.settings.category.ia.system";
44 public static final String CATEGORY_SYSTEM_LANGUAGE =
45 "com.android.settings.category.ia.language";
46 public static final String CATEGORY_SYSTEM_DEVELOPMENT =
47 "com.android.settings.category.ia.development";
48 public static final String CATEGORY_NOTIFICATIONS =
49 "com.android.settings.category.ia.notifications";
50 public static final String CATEGORY_DO_NOT_DISTURB = "com.android.settings.category.ia.dnd";
51 public static final String CATEGORY_GESTURES = "com.android.settings.category.ia.gestures";
52 public static final String CATEGORY_NIGHT_DISPLAY =
53 "com.android.settings.category.ia.night_display";