开机向导简介
在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
应用.踩坑结束.
网友评论