在Android设备第一次上电或者进行恢复出厂设置后第一次启动时运行的应用.用于对Android设备进行语言,网络等相关设置.java
本文都是基于Android 8.0 系统源码来讲明的.android
在系统目录 packages\apps
之下有个 Provision
项目就是开机向导.可是里面只有一个简单的 DefaultActivity
.来看下源码有什么内容.bash
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对象.这里是第一个坑,下面再讲.app
在第3个注释的代码行中 setComponentEnabledSetting(ComponentName componentName,int newState,int flags)
是来设置组件的状态的API,如下是对参数的说明:ide
ComponentName
组件名函数
newState
组件新状态有如下三个状态:this
不可用状态:COMPONENT_ENABLED_STATE_DISABLED
可用状态:COMPONENT_ENABLED_STATE_ENABLED
默认状态:COMPONENT_ENABLED_STATE_DEFAULT
复制代码
flag
行为标签spa
内容很简单,只有几行代码.主要是配置一个全局参数告诉其它应用已经设置并设置组件状态为不可用.code
再看下 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
属性设置为负数就能解决此问题.
当非特权app任何priority > 0的设置都不起做用。不管设置任何正数值会被恢复0。由于开机启动APP被放置在 vendor/app
目录之下,但并不真正属于系统应用,其priority属性没法生效。所以根本解决方法是将其放置在 system/priv-app/Provision
之下,覆盖系统只带的 Provision.apk
开机向导app便可。并不须要将 Launcher.apk
的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
应用.踩坑结束.