关机有几种方式:按键、重启或者其他模式
在关机过程中,主要做了三件事:
1.发送关机广播
2.关闭一些主要服务进程
3.通过PowerManagerService调用底层进行关机
有的模块可能需要监听手机关机事件,所以在关机时发送关机广播,通知相关模块处理。
接下来主要讲解按键响应关机整个流程:
PhoneWindowManager.java | framework\base\services\core\java\com\android\server\policy
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
// Hold a wake lock until the power key is released.
if (!mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.acquire();
}
......
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;
//........
}
}
}
当按下电源按键的时候会有不同的响应,可能什么都不做,也可能直接关机,没有给用户任何提醒. 或者弹出dialog让用户自己选择. 下面将主要讲解弹出dialog关机的情况。
GlobalActions.java | framework\base\services\core\java\com\android\server\policy
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();
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();//shutDownRing();//显示全局Dailog
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;
}
}
延伸:
可以在调用showGlobalActionsInternal()方法显示全局Dailog之前做一些定制化处理,比如随机播放关机铃声,如shutDownRing()方法:
private void shutDownRing(){
//Merged by lefty.lan @20180803 for shutdown_ring, start
Random random = new Random();
int num = random.nextInt(4)%(4-1+1) + 1;
MediaPlayer mPlayer = new MediaPlayer();
try {
if(mPlayer != null){
switch (num) {
case 1:
mPlayer.setDataSource("/system/media/01.wav");
break;
case 2:
mPlayer.setDataSource("/system/media/02.wav");
break;
case 3:
mPlayer.setDataSource("/system/media/03.wav");
break;
case 4:
mPlayer.setDataSource("/system/media/04.wav");
break;
}
mPlayer.prepare();
mPlayer.start();
try{
Thread.sleep(2000);
} catch (Exception e) {
//e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
showGlobalActionsInternal();
if(mPlayer != null){
mPlayer.stop();
}
//end
}
言归正传
void showGlobalActionsInternal() {
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);//关闭系统Dailog
if (mGlobalActions == null) {
mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);//创建Dailog
}
final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();//keyguard是否在显示
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());//显示Dailog
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出来,否则就新创建出来一个显示。
/**
* Show the global actions dialog (creating if necessary)
* @param keyguardShowing True if keyguard is showing
*/
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
if (mDialog != null) {
mDialog.dismiss();
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();//创建Dailog
prepareDialog();//准备Dailog
// 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();//显示Dailog
mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
//add by lefty_lan,2018-7-3 13:48:55
//mWindowManagerFuncs.shutdown(true);
// try
// {
// Thread.sleep(2000);
// } catch (Exception e) {
// //e.printStackTrace();
// }
//mWindowManagerFuncs.shutdown(false);
}
}
通过在(frameworks\base\core\res\res\values)config.xml中的actionslist记录的长按power键可以选择执行的动作,遍历actions列表进行匹配动作为其创建对应的Action对象.之后就创建dialog并返回。
- power
- bugreport
- users
/**
* Create the global actions dialog.
* @return A new 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());
}
// Add by Avar.wu @20161021 to add reboot action, start
else if(GLOBAL_ACTION_KEY_REBOOT.equals(actionKey)) {
mItems.add(new RebootAction());
// end
// Add by Avar.wu @20170303 to add region-screenshot action
} else if (GLOBAL_ACTION_KEY_REGION_SCREENSHOT.equals(actionKey)) {
mItems.add(new ScreenShotAction());
// end
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
// Add here so we don't add more than one.
addedKeys.add(actionKey);//最后将actionKey放入列表, 不重复处理
}
mAdapter = new MyAdapter();
AlertParams params = new AlertParams(mContext);
params.mAdapter = mAdapter;
params.mOnClickListener = this;
params.mForceInverseBackground = true;
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;//返回Dailog
}
当进行关机操作时就会调用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 | framework\base\services\core\java\com\android\server\power
/**
* Request a clean shutdown, waiting for subsystems to clean up their
* state etc. Must be called from a Looper thread in which its UI
* is shown.
*
* @param context Context used to display the shutdown progress dialog.
* @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.
* @param confirm true if user confirmation is needed before shutting down.
*/
public static void shutdown(final Context context, String reason, boolean confirm) {
mReboot = false;
mRebootSafeMode = false;
mReason = reason;
Log.d(TAG, "!!! Request to shutdown !!!");
if (mSpew) {
StackTraceElement[] stack = new Throwable().getStackTrace();
for (StackTraceElement element : stack)
{
Log.d(TAG, " |----" + element.toString());
}
}
if (SystemProperties.getBoolean("ro.monkey", false)) {
Log.d(TAG, "Cannot request to shutdown when Monkey is running, returning.");
return;
}
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);
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();
}
bConfirmForAnimation = confirm;
Log.d(TAG, "PowerOff dialog doesn't exist. Create it first");
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();
//add by lefty_lan,2018-7-3 13:49:22
//beginShutdownSequence(context);
} else {
beginShutdownSequence(context);//如果不需要确认就直接关机
}
}
摘录自:https://blog.csdn.net/fu_kevin0606/article/details/54586009
在关机的时候如果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中,然后经过内核进行关机。