Google开机向导简介
以下涉及Google开机向导的代码,均来自于反编译自版本227.4976668的SetupWizard.apk。
Launcher
开机向导本质是个Launcher,查看其manifest XML文件可以发现,其MAIN activity声明了category.HOME
,且优先级为 5:
...
启动
当AMS准备好之后(systemReady),会调用startHomeActivityLocked()
方法启动优先级最高的Launcher应用。当Google开机向导优先级最高时,就会启动com.google.android.setupwizard.SetupWizardActivity
。
进入Google开机向导后,首先判断系统是否需要进入开机向导,通过以下三个参数的值:
- Settings$Global.DEVICE_PROVISIONED
- Settings$Secure.USER_SETUP_COMPLETE
- System property: ro.setupwizard.mode (默认配置为OPTIONAL)
其中DEVICE_PROVISIONED
用来标识是否走完开机向导。
USER_SETUP_COMPLETE
与ro.setupwizard.mode
是为多用户系统服务的,所知不多,暂不深入分析。
当DEVICE_PROVISIONED
为0,表示需要正常进入开机向导流程。
Wizard script
Google开机向导定义了一个wizard script的XML格式,用来描述开机向导的流程,格式如下:
...
如上,Google开机向导的每一步均以WizardAction
标签表示,id
作为该步骤的唯一标识,内容可以是intent uri,也可以是另一个script uri;下一步的走向则由result
标签决定,根据resultCode
的不同,会走到不同的流程。
Google开机向导启动后,需要解析该wizard script,WizardAction
标签的内容解析为WizardAction
类对象,
所解析出来的这些WizardAction
对象存在WizardScript
对象的一个列表中:
public class WizardAction implements Parcelable {
private final WizardBranchArray mBranches; // 对应"result"标签,继承"SparseArray","resultCode"为key
private final String mId; // 对应`id`标签
private final int mIndex; // Action列表中的位置
private final String mScriptUri; // 所属script uri
private final String mUri; // Intent uri
...
}
First intent
获取到WizardAction
列表之后,需要取出第一个可用的action,新建一个WizardStack
对象,将第一个action放到栈顶,并构建相应的intent,跳转到该页面,如此便到了用户可见的第一个页面。
com.google.android.wizardmanager.WizardManager
:
private static Intent buildIntent(Context context, Intent originalIntent, WizardStack stack, Callback callback) {
WizardAction action = stack.peek(); // 获取栈顶的WizardAction
Intent wizardIntent = action.getIntent(); // 由wizard script中的intent uri转化而来
if (wizardIntent == null) {
return null;
}
wizardIntent.putExtras(originalIntent);
// 启动下一个界面时必须传入当前的WizardStack对象引用与当前的WizardAction对象引用
Bundle args = new Bundle();
WizardBundleHelper.putParcelableAsByteArray(args, "stack", stack);
WizardBundleHelper.putParcelableAsByteArray(args, "action", action);
wizardIntent.putExtra("wizardBundle", args);
return wizardIntent;
}
Next intent
进入wizard script定义的第一个页面之后,怎么跳转到下一个页面呢?
Google 开机向导通过构建一个action为com.android.wizard.NEXT
的nextIntent,并跳转到nextIntent。Android原生提供了帮助类WizardManagerHelper
来构建next intent。Next intent中必不可少的参数是wizardBundle
,如上代码所示,wizardBundle
对应的参数包含了WizardStack
对象引用与当前的WizardAction
对象引用,如果缺少该参数,Google开机向导将不会执行下一步操作,直接返回,导致流程无法进行下去。而resultCode
则用来标识下一步的action,从wizard script示例中可以看到,每个resultCode
对应一个id
,通过传入resultCode
可以指定下一步的action,没有resultCode
则执行默认的action。
com.android.setupwizardlib.util.WizardManagerHelper
:
public static Intent getNextIntent(Intent originalIntent, int resultCode, Intent data) {
Intent intent = new Intent("com.android.wizard.NEXT");
intent.putExtra("wizardBundle", originalIntent.getBundleExtra("wizardBundle"));
...
intent.putExtra("com.android.setupwizard.ResultCode", resultCode);
...
return intent;
}
退出Google开机向导
Google开机向导启动时候做了很多初始化操作,例如调用StatusBarManager.disable()
来禁用状态栏,具体可查看com.google.android.setupwizard.lifecycle
包下面的一些实现。
相应的,在结束时应当将这些初始化操作复原,这些复原操作都是在退出时进行的。在Google开机向导的wizard script中可以看到最后的action是com.android.setupwizard.EXIT
,查看对应的activity com.google.android.setupwizard.SetupWizardExitActivity
:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
FrpHelper.get(this).removeFrpNotification();
boolean isDeferredSetup = WizardManagerHelper.isDeferredSetupWizard(getIntent());
ExitHelper.get(this).finishSetup(isDeferredSetup);
ExitHelper.get(this).launchExitIntent(this, isDeferredSetup);
ExitHelper.get(this).finishAllAppTasks(this);
}
可以看到,Google开机向导实现了一个工具类ExitHelper
处理退出的一些操作。