在Android設備第一次上電或者進行恢復出廠設置後第一次啓動時運行的應用.用於對Android設備進行語言,網絡等相關設置.java
本文都是基於Android 8.0 系統源碼來講明的.android
在系統目錄 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
是配置全局設置告訴其餘應用設備已經進行過初始化設置.app
在第2個註釋的代碼行中的構造函數 ComponentName(Context pkg,Class<?> cls)
須要傳遞2個參數,一個是上下文對象,另外一個是class對象.這裏是第一個坑,下面再講.ide
在第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
文件裏的內容:
<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
屬性使用的問題.先說後面這個問題.
上文說過 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
屬性設置爲負數就能解決此問題.
在全部流程走完以後會調用以下方法:
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
應用.踩坑結束.