在长按power键时系统会弹出对话框,让用户选择关机, 重启或者其他模式. 在本文中重点讲解系统关机流程. 让大家了解在系统关机过程都做了哪些事情,而导致关机慢又有那些主要的原因.在Android7.0 PowerManagerService亮灭屏(一)一文中有对按power键传输讲解, 长按power键是在java层的PhoneWindowManager.java中进行处理的.
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
//..........
// When interactive, we're already awake.
// Wait for a long press or for the button to be released to decide what to do.
if (hasLongPressOnPowerBehavior()) { //是否存在长按事件
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
msg.setAsynchronous(true); //如果是长按power键就发送MSG_POWER_LONG_PRESS
mHandler.sendMessageDelayed(msg, //发送delay消息, delay 500ms给用户一些时间长按power键显示关机dialog, 同时delay时间也可以通过config.xml中配置
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
}
//........
}
延迟消息最终会在PolicyHandler的handleMessage中处理长按事件.
private class PolicyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
//......
case MSG_POWER_LONG_PRESS:
powerLongPress(); //处理长按事件
break;
//........
}
}
}
当长按power键时可能会有不同的behavior,可能什么都不做, 也可能直接关机,没有给用户任何提醒. 或者弹出dialog让用户自己选择. 下面将主要讲解弹出dialog关机的情况.
static final int LONG_PRESS_POWER_NOTHING = 0; //长按power键什么都不做
static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; //为全局动作, 显示关机dialog
static final int LONG_PRESS_POWER_SHUT_OFF = 2; //只有关机一个选项
static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3; //直接关机不用确认
private void powerLongPress() {
final int behavior = getResolvedLongPressOnPowerBehavior(); //获取长按power键要做的事情
switch (behavior) {
case LONG_PRESS_POWER_NOTHING: //返回,什么都不做
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
performAuditoryFeedbackForAccessibilityIfNeed();
}
showGlobalActionsInternal(); //显示全局的dialog
break;
case LONG_PRESS_POWER_SHUT_OFF:
case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: //直接关机
mPowerKeyHandled = true;
performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
}
}
在显示关机dialog时需要将其他的系统dialogs都关闭了, 优先处理关机dialog, 之后就创建mGlobalActions显示dialog.\
void showGlobalActionsInternal() {
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); //关闭系统dialogs
if (mGlobalActions == null) {
mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs); //创建GlobalActions
}
final boolean keyguardShowing = isKeyguardShowingAndNotOccluded(); //keyguard是否在显示
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); //显示dialog
if (keyguardShowing) {
// since it took two seconds of long press to bring this up,
// poke the wake lock so they have some time to see the dialog.
mPowerManager.userActivity(SystemClock.uptimeMillis(), false); //通知power发生了一次用户时间, 让用户可以看到dialog
}
}
在GlobalActions中如果已经存在mDialog就将原来的dismiss掉,之后发送消息重新show出来,否则就新创建出来一个显示.
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
mKeyguardShowing = keyguardShowing; //keyguard正在显示
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog != null) {
mDialog.dismiss(); //dismiss dialog
mDialog = null;
// Show delayed, so that the dismiss of the previous dialog completes
mHandler.sendEmptyMessage(MESSAGE_SHOW);
} else {
handleShow(); //如果不存在mDialog, 就调用handleShow处理
}
}
private void handleShow() {
awakenIfNecessary();
mDialog = createDialog(); //创建mDialog对象
prepareDialog(); //准备dialog
// If we only have 1 item and it's a simple press action, just do this action.
if (mAdapter.getCount() == 1
&& mAdapter.getItem(0) instanceof SinglePressAction
&& !(mAdapter.getItem(0) instanceof LongPressAction)) {
((SinglePressAction) mAdapter.getItem(0)).onPress(); //调用onPress函数
} else {
WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
attrs.setTitle("GlobalActions");
mDialog.getWindow().setAttributes(attrs);
mDialog.show(); //显示dialog
mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
}
}
通过在config.xml中的actions list记录的长按power键可以选择执行的动作, 遍历actions列表进行匹配动作为其创建对应的Action对象.之后就创建dialog并返回.
private GlobalActionsDialog createDialog() {
mItems = new ArrayList();
String[] defaultActions = mContext.getResources().getStringArray(
com.android.internal.R.array.config_globalActionsList); //获取config.xml中的action list记录在defaultActions数组中
ArraySet addedKeys = new ArraySet();
for (int i = 0; i < defaultActions.length; i++) { //遍历defaultActions列表
String actionKey = defaultActions[i];
if (addedKeys.contains(actionKey)) { //如果action已经在addedKeys列表中就不再添加了
// If we already have added this, don't add it again.
continue;
}
if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { //对action list中actio进行匹配,如果能匹配上就创建对应对象放入mItems列表中
mItems.add(new PowerAction()); //长按power键动作
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
mItems.add(mAirplaneModeOn);
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
mItems.add(new BugReportAction());
}
} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
if (mShowSilentToggle) {
mItems.add(mSilentModeAction);
}
} else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
addUsersToMenu(mItems);
}
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
mItems.add(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
mItems.add(getLockdownAction());
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
mItems.add(getVoiceAssistAction());
} else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
mItems.add(getAssistAction());
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
// Add here so we don't add more than one.
addedKeys.add(actionKey); //最后将actionKey放入列表, 不重复处理
}
GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params); //创建dialog
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. 当点击dialog外面,就会将dialog取消掉
dialog.getListView().setItemsCanFocus(true);
dialog.getListView().setLongClickable(true);
dialog.getListView().setOnItemLongClickListener(
new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position,
long id) {
final Action action = mAdapter.getItem(position);
if (action instanceof LongPressAction) {
return ((LongPressAction) action).onLongPress();
}
return false;
}
});
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.setOnDismissListener(this);
return dialog; //将新建的dialog返回
}
当进行关机操作时就会调用PowerAction的onPress函数, 进行调用WindowManagerFuncs接口中的shutdown函数, shutdown函数的具体实现在WindowManagerService中.
private final class PowerAction extends SinglePressAction implements LongPressAction {
private PowerAction() {
super(com.android.internal.R.drawable.ic_lock_power_off,
R.string.global_action_power_off);
}
@Override
public boolean onLongPress() {
UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.rebootSafeMode(true);
return true;
}
return false;
}
//.............
@Override
public void onPress() {
// shutdown by making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown(false /* confirm */); //关机
}
}
由于WindowManagerService实现了接口WindowManagerFuncs, 所以就会调用到WMS中的shutdown函数. 进而调用ShutdownThread中去实现关机功能.
// Called by window manager policy. Not exposed externally.
@Override
public void shutdown(boolean confirm) {
ShutdownThread.shutdown(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, confirm);
}
关机过程的主要实现在ShutdownThread.java中
public static void shutdown(final Context context, String reason, boolean confirm) {
mReboot = false;
mRebootSafeMode = false; //不是重启
mReason = reason; //记录关机的原因
shutdownInner(context, confirm);
}
static void shutdownInner(final Context context, boolean confirm) {
// ensure that only one thread is trying to power down.
// any additional calls are just returned
synchronized (sIsStartedGuard) { //保证只有一个关机线程
if (sIsStarted) {
Log.d(TAG, "Request to shutdown already running, returning.");
return;
}
}
final int longPressBehavior = context.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior); //获取长按资源id
final int resourceId = mRebootSafeMode
? com.android.internal.R.string.reboot_safemode_confirm
: (longPressBehavior == 2 //关机确认信息
? com.android.internal.R.string.shutdown_confirm_question
: com.android.internal.R.string.shutdown_confirm);
Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
if (confirm) {
final CloseDialogReceiver closer = new CloseDialogReceiver(context);
if (sConfirmDialog != null) {
sConfirmDialog.dismiss();
}
sConfirmDialog = new AlertDialog.Builder(context)//需要再次确认关机, 创建dialog
.setTitle(mRebootSafeMode
? com.android.internal.R.string.reboot_safemode_title
: com.android.internal.R.string.power_off)
.setMessage(resourceId)
.setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
beginShutdownSequence(context); //确认关机
}
})
.setNegativeButton(com.android.internal.R.string.no, null) //不关机了
.create();
closer.dialog = sConfirmDialog;
sConfirmDialog.setOnDismissListener(closer);
sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
sConfirmDialog.show();
} else {
beginShutdownSequence(context); //如果不需要确认就直接关机
}
}
关闭主要模块
在关机的时候如果sdcard处于MOUNTED状态下,就需要发送消息H_UNMOUNT_PM_UPDATE将sdcard进行unmount操作。
如果sdcard为emulated模拟内卡,就不需要进行unmount操作,所以模拟内卡比物理内卡的手机关机速度要快很多。
在进行unmount操作时,需要先调用PackageManagerService进行更新一下包状态,删除安装在sd卡上的软件。完成之后回调到MountService中。
之后需要向底层发送命令查询占用sd卡的进程(如数据库,slog所在进程),然后调用AMS将占用sd卡的进程杀死。由于占用sd卡的进程比较顽强,可能杀死后还会起来,所以杀完后再进行查询占用sd卡的进程。如果所查进程数仍然不为NULL,就会再次杀,最多尝试杀四次,如果还杀不死就调用底层vold进行强制杀死。故,如果占用sd卡的进程较多,这部分就会较耗时。
上层的准备工作处理完毕后,就需要向底层发送命令来进行卸载sd卡。
底层在卸载sd卡的过程中,要将sd卡的状态改变发广播到上层,通知MountSevice。
sd卡状态由Mounted变为Unmounting时,为了给上层framework一定的反应时间,底层在发送广播后睡了1s。
之后开始umount操作,如果在上层没有将占用sd卡的进程杀死,就会在底层强制杀进程。系统原生给了10次机会,每次umount失败一次系统都会睡1s之后在尝试umount,如果在最后一次仍然没有成功就强制杀进程。所以重新umount的次数越多耗时就会越长。
系统关机
当Shutdown MountService完成之后,回调至ShutdownThread中,将线程唤醒,继续往下执行。
在关机之前获得振动器震动手机,由于震动手机与调用power关机异步进行,所以为了避免关机太快来不及震动,上层会在调用用振动器后睡500ms。
最后调到power中,然后经过内核进行关机。