Android 自定義開機嚮導踩坑
開機嚮導簡介
在Android裝置第一次上電或者進行恢復出廠設定後第一次啟動時執行的應用.用於對Android裝置進行語言,網路等相關設定.
Android原始碼中的開機嚮導
本文都是基於Android 8.0 系統原始碼來說明的.
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個註釋的程式碼行中有個關鍵字
Settings.Global.DEVICE_PROVISIONED
是配置全域性設定告訴其他應用裝置已經進行過初始化設定. -
在第2個註釋的程式碼行中的建構函式
ComponentName(Context pkg,Class<?> cls)
需要傳遞2個引數,一個是上下文物件,另一個是class物件. 這裡是第一個坑,下面再講. -
在第3個註釋的程式碼行中
setComponentEnabledSetting(ComponentName componentName,int newState,int flags)
是來設定元件的狀態的API,以下是對引數的說明:
-
ComponentName
元件名 -
newState
元件新狀態有以下三個狀態:不可用狀態:COMPONENT_ENABLED_STATE_DISABLED 可用狀態:COMPONENT_ENABLED_STATE_ENABLED 預設狀態:COMPONENT_ENABLED_STATE_DEFAULT
-
flag
行為標籤
內容很簡單,只有幾行程式碼.主要是配置一個全域性引數告訴其它應用已經設定並設定元件狀態為不可用.
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
的宣告.有兩個關鍵點需要注意:
-
android:priority
屬性,這個屬性一般會用在Activity
和BroadcastReceiver
中,用來定義Activity
或者BroadcastReceiver
啟動的優先順序.範圍在-1000~1000
之間.值越大優先順序越高.在Activity
中使用時只有隱式呼叫才起作用,顯示呼叫無效. (這是第二個坑點) -
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.但是結果卻是彈出一個選項框如下圖.

讓我們二選一啟動.這顯然不是想要的效果.猜想或許是因為 Launcher
APP中沒有設定 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
不同.如果我們禁止喚起 MainActivity
後 SetupWizard
應用不就不會再次啟動了嗎.因此修改 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
應用.踩坑結束.