Android 自定義開機嚮導踩坑

開機嚮導簡介

在Android設備第一次上電或者進行恢復出廠設置後第一次啓動時運行的應用.用於對Android設備進行語言,網絡等相關設置.java

Android源碼中的開機嚮導

本文都是基於Android 8.0 系統源碼來講明的.android

DefaultActivity.java

在系統目錄 packages\apps 之下有個 Provision 項目就是開機嚮導.可是裏面只有一個簡單的 DefaultActivity.來看下源碼有什麼內容.網絡

public class DefaultActivity extends Activity {
        @Override
        protected void onCreate(Bundle icicle) {
            super.onCreate(icicle);
            // Add a persistent setting to allow other apps to know the device has been provisioned.
            Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);//1
            Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);
            // remove this activity from the package manager.
            PackageManager pm = getPackageManager();
            ComponentName name = new ComponentName(this, DefaultActivity.class);//2
            pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);//3

            // terminate the activity.
            finish();
        }
    }
}
  1. 在第1個註釋的代碼行中有個關鍵字 Settings.Global.DEVICE_PROVISIONED 是配置全局設置告訴其餘應用設備已經進行過初始化設置.app

  2. 在第2個註釋的代碼行中的構造函數 ComponentName(Context pkg,Class<?> cls) 須要傳遞2個參數,一個是上下文對象,另外一個是class對象.這裏是第一個坑,下面再講.ide

  3. 在第3個註釋的代碼行中 setComponentEnabledSetting(ComponentName componentName,int newState,int flags) 是來設置組件的狀態的API,如下是對參數的說明:函數

  • ComponentName 組件名this

  • newState 組件新狀態有如下三個狀態:spa

    不可用狀態:COMPONENT_ENABLED_STATE_DISABLED 
       可用狀態:COMPONENT_ENABLED_STATE_ENABLED 
       默認狀態:COMPONENT_ENABLED_STATE_DEFAULT
  • flag 行爲標籤code

內容很簡單,只有幾行代碼.主要是配置一個全局參數告訴其它應用已經設置並設置組件狀態爲不可用.component

AndroidManifest.xml

再看下 AndroidManifest.xml 文件裏的內容:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.provision">

    <original-package android:name="com.android.provision" />

    <!-- For miscellaneous settings -->
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

    <application>
        <activity android:name="DefaultActivity"
                android:excludeFromRecents="true">
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.SETUP_WIZARD" />
            </intent-filter>
        </activity>
    </application>
</manifest>

AndroidManifest文件中是對 DefaultActivity 的聲明.有兩個關鍵點須要注意:

  1. android:priority 屬性,這個屬性通常會用在 ActivityBroadcastReceiver 中,用來定義 Activity 或者 BroadcastReceiver 啓動的優先級.範圍在 -1000~1000 之間.值越大優先級越高.在 Activity 中使用時只有隱式調用才起做用,顯示調用無效. (這是第二個坑點)

  2. android.intent.category.HOME 這個 category 是用來標記爲桌面程序,例如系統中的Launcher應用.用於在系統啓動以後啓動該應用.

項目中遇到的坑點與解決方法

在上文中提到過遇到的兩個坑點,一個是構造函數 ComponentName(Context pkg,Class<?> cls) 參數使用錯誤致使的問題,一個是 android:priority 屬性使用的問題.先說後面這個問題.

android:priority

上文說過 priority 屬性的值越大優先級越高,就能優先啓動.

在開機嚮導的APP(下文都稱做 SetupWizard)中的配置:

<activity android:name=".activity.MainActivity">
    <intent-filter android:priority="9">
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.HOME" />
         <category android:name="android.intent.category.DEFAULT" />
         <category android:name="android.intent.category.SETUP_WIZARD" />
    </intent-filter>
</activity>

priority 的值設置爲9,而 Launcher APP中沒有聲明 priority 則默認爲0.因此在理論上應該是在系統啓動的時候會優先啓動 SetupWizard APP.可是結果倒是彈出一個選項框以下圖.

 
 

讓咱們二選一啓動.這顯然不是想要的效果.猜測或許是由於 LauncherAPP中沒有設置 priority 屬性的緣由. 故此將 Launcher 應用的優先級修改成1後再次編譯運行.其結果依然是二選一.反覆修改兩個優先級的值依舊無效.經同事提醒將 Launcher 應用的優先級設置爲負數再試試.沒想到就能正常進入到 SetupWizard 應用中; 猜測是否是使用的設備的系統對其優先級有修改,將正數的大小比較給作了處理.而對正負數的大小比較無影響. 所以解決方法是將 Launcher 應用的 priority 屬性設置爲負數就能解決此問題.

ComponentName(Context pkg,Class<?> cls)

在全部流程走完以後會調用以下方法:

public static void finishSetUpWizard(Context context) {
    Settings.Global.putInt(context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
    PackageManager pm = context.getPackageManager();
    ComponentName name = new ComponentName(context, context.getClass());
    pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
      PackageManager.DONT_KILL_APP);
}

可是結束後會再次啓動 SetupWizard 應用,可是會在啓動上次結束的 Activity 時崩潰從新啓動. 例如我在網絡設置 NetworkSettingActivity 中設置網絡成功後結束整個應用,調用 finishSetUpWizard(mContext) 後.並無預期結束當前應用繼而啓動 Launcher 應用.再次啓動 SetupWizard 應用時繼續走流程,發如今啓動 NetworkSettingActivity 時異常崩潰.發現該現象與 setComponentEnabledSetting(componentName,newState,flags) API的做用相似. 深刻了解當 newState 參數設置爲 COMPONENT_ENABLED_STATE_DISABLED 時當前組件 NetworkSettingActivity 會從PM中移除,而沒法再次啓動.就如咱們啓動時會報異常 android.content.ActivityNotFoundException: Unable to find explicit activity.

可是咱們預期的是結束當前應用後繼而啓動 Launcher.如今倒是從新啓動 SetupWizard 應用且不能開啓上次結束時調用了 finishSetUpWizard 方法的 Activity.發現和咱們預期效果不一樣的是禁止喚起的 Activity 不一樣.若是咱們禁止喚起 MainActivitySetupWizard 應用不就不會再次啓動了嗎.所以修改 finishSetUpWizard(Context context) 方法中 ComponentName(Context pkg,Class<?> cls) 的cls參數,將 SetupWizard 應用的入口 MainActivity 傳遞進去.修改後的方法:

public static void finishSetUpWizard(Context context) {
        Settings.Global.putInt(context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
        PackageManager pm = context.getPackageManager();
        ComponentName name = new ComponentName(context, MainActivity.class);
        pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
}

再次編譯啓動,流程設置完畢後正常結束 SetupWizard 並啓動 Launcher 應用.踩坑結束.

相關文章
相關標籤/搜索