1. 程式人生 > >Android Settings 6.0 流程與定製修改

Android Settings 6.0 流程與定製修改

概述

android 6.0 版本的設定程式碼相比4.4就沒有那麼直白,但仍然可以用啟動activity,介面佈局,邏輯控制流程的順序來閱讀理解。

  1. 入口 匯入Settings原始碼同樣以搜尋android.intent.category.LAUNCHER的方式,可以找到啟動activity名為Settings,開啟程式碼:
public class Settings extends SettingsActivity {

    /*
    * Settings subclasses for launching independently.
    */
    public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }
    public static class WirelessSettingsActivity extends SettingsActivity { /* empty */ }
    public static class SimSettingsActivity extends SettingsActivity { /* empty */ }
    public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }
    public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }
    

這裡可以看到與4.4的不同,即通過常規啟動activity形式找到的Settings並不是所熟悉的包含onCreate等生命週期,xml佈局檔案,一些控制邏輯這樣形式的類,其主體定義的都是一些空實現的靜態內部類,但這裡有個比較明顯的繼承關係來自 SettingsActivity,其實從命名也可以猜測得到,這個類才是需要閱讀理解的所在,開啟檢視:

public class SettingsActivity extends Activity

這就來到了熟悉的標準activity模式,這裡同樣結合執行介面找一下佈局實現,自然先從onCreate開始:

setContentView(mIsShowingDashboard ?
                R.layout.settings_main_dashboard : R.layout.settings_main_prefs);

當以點選Launcher圖示的形式進來,這裡的mIsShowingDashboard為true,即這個主activity引用的佈局檔案為 R.layout.settings_main_dashboard:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:id="@+id/main_content"
             android:layout_height="match_parent"
             android:layout_width="match_parent"
             />

這裡可以看到這個主佈局,並沒有太多確切內容,繼續看可以發現:

if (!mIsShowingDashboard) {
                mDisplaySearch = false;
                // UP will be shown only if it is a sub settings
                if (mIsShortcut) {
                    mDisplayHomeAsUpEnabled = isSubSettings;
                } else if (isSubSettings) {
                    mDisplayHomeAsUpEnabled = true;
                } else {
                    mDisplayHomeAsUpEnabled = false;
                }
                setTitleFromIntent(intent);

                Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
                switchToFragment(initialFragmentName, initialArguments, true, false,
                        mInitialTitleResId, mInitialTitle, false);
            } else {
                // No UP affordance if we are displaying the main Dashboard
                mDisplayHomeAsUpEnabled = false;
                // Show Search affordance
                mDisplaySearch = true;
                mInitialTitleResId = R.string.dashboard_title;
                switchToFragment(DashboardSummary.class.getName(), null, false, false,
                        mInitialTitleResId, mInitialTitle, false);
            }

之前提過mIsShowingDashboard為true,這裡會走下面的分子,而switchToFragment運用的是FragmentTransaction替換Fragment的形式來填入實際內容,相信凡是接觸過android應用開發對fragment的使用都不陌生,而這裡正是標準的activity對應一個空的父佈局,然後具體內容由具體的fragment填充;

這裡不得不說android原始碼時常有讓人無法理解的冗長呼叫關聯,一個簡單的功能能秀一二十個類或lib的呼叫,但又時常能發現這種最為基礎,google大神程式設計師竟然也寫了和自己這種初學者類似的程式碼而欣喜。

這裡開啟DashboardSummary,這裡的流程又夾雜了一些呼叫,但仍然是從各個生命週期來分析,並且優先關注onCreateView介面到底如何實現,流程如下:

  1. onCreateView裡主要是對整個大框架的定義,關鍵內容為mDashboard;
  2. 然後在onResume()找到了比較明顯的sendRebuildUI()方法;
  3. 發現其實也就是經過handle機制執行到了rebuildUI(context);

而看到rebuildUI的程式碼便豁然開朗:

List<DashboardCategory> categories =
                ((SettingsActivity) context).getDashboardCategories(true);

關鍵就在這裡獲取了具體的DashboardCategory列表,也就是主介面一項項功能資訊的內容,後續便是簡單的迴圈解析,填充view,生成介面,而這裡的來源竟然還是在SettingsActivity中: getDashboardCategories > buildDashboardCategories:

    private void buildDashboardCategories(List<DashboardCategory> categories) {
        categories.clear();
        loadCategoriesFromResource(R.xml.dashboard_categories, categories, this);
        updateTilesList(categories);
    }

到這裡就可以知道具體的功能項資訊由loadCategoriesFromResource 以XML解析的形式將R.xml.dashboard_categories內容封裝到列表變數中,而下面的 updateTilesList(categories)就是4.4也有的針對內容在某些條件下移除一些選項;

對R.xml.dashboard_categories:

<dashboard-categories
        xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- WIRELESS and NETWORKS -->
    <dashboard-category
            android:id="@+id/wireless_section"
            android:key="@string/category_key_wireless"
            android:title="@string/header_category_wireless_networks" >

        <!-- Wifi -->
        <dashboard-tile
                android:id="@+id/wifi_settings"
                android:title="@string/wifi_settings_title"
                android:fragment="com.android.settings.wifi.WifiSettings"
                android:icon="@drawable/ic_settings_wireless"
                />

        <!-- Bluetooth -->
        <dashboard-tile
                android:id="@+id/bluetooth_settings"
                android:title="@string/bluetooth_settings_title"
                android:fragment="com.android.settings.bluetooth.BluetoothSettings"
                android:icon="@drawable/ic_settings_bluetooth"
                />

這就是和4.4的header類似的控制元件了,依然是每項對應一個功能項,並標有fragment屬性,最終在DashboardSummary中封裝具體內容,並新增到SettingsActivity的佈局中去。

  1. 點選事件

可以發現在SettingsActivity以及DashboardSummary中都未發現明顯的點選事件實現,那麼點選每一項是如何進入子功能介面的呢;

其實在DashboardSummary的rebuildUI中可以發現,拿到List後,最終的一個個功能子項為DashboardTileView物件,這裡開啟其程式碼:

public class DashboardTileView extends FrameLayout implements View.OnClickListener {

可以發現其繼承基本的FrameLayout,並清楚寫明瞭點選事件呼叫到Utils.startWithFragment,最終即封裝帶有fragment資訊的intent啟動SubSettings,這也就是為什麼每個功能項點選進入的都是SubSettings,而具體內容就根據fragment資訊來實現。

3.定製化 常見的定製化即增刪功能項,如果熟悉了以上流程,那麼對於刪減功能項就有太多的方式,從佈局檔案,到解析,到封裝到列表,到updateTilesList,到addView,整個流程任何地方進行一下截斷,添加個if之類的就可以去掉,這裡不一一列出;

如果要增加一個功能項,這裡同樣類似於4.4如果要加一個自動開關機功能上去,那麼就可以:

  1. 佈局檔案中dashboard_categories.xml中:
 2.        <!-- Schedule Power Alarm -->
 3.        <dashboard-tile
 4.                android:id="@+id/schedule_power_settings"
 5.                android:title="@string/schedule_power_settings_title"
 6.                android:fragment="com.android.settings.schedulePower.SchedulePowerSettings"
 7.                android:icon="@drawable/ic_settings_display"
 8.                />
 
         <!-- About Device -->
         <dashboard-tile

這裡對應的資原始檔另外新增,fragment可以先建一個空的類不寫邏輯;

2.SettingsActivity加上新加的id以及fragment類名:

@@ -277,6 +278,7 @@ public class SettingsActivity extends Activity
             R.id.accessibility_settings,
             R.id.print_settings,
             R.id.nfc_payment_settings,
 1.            R.id.schedule_power_settings,
             R.id.home_settings,
             R.id.dashboard
     };
@@ -354,6 +356,7 @@ public class SettingsActivity extends Activity
             ProcessStatsSummary.class.getName(),
             DrawOverlayDetails.class.getName(),
             WriteSettingsDetails.class.getName(),
 2.            SchedulePowerSettings.class.getName()
     };

這樣只要圖片字串資源都有定義,跳轉的fragment不為空,編譯apk push到機器後,即可看見新加的功能項,點選進入空的fragment,之後再具體完善邏輯功能即可。