最近遇到fastboot关机电流偏高的问题,虽然最后确认是硬件的问题,但还是顺便分析了一下android开关机的流程。总结一下,加深印象,也方便日后查阅。
Android智能手机和平板一般都有Power key,长按Power key弹出关机对话框,选择power off就会让系统关闭。关机动作从按键触发中断,linux kernel层给android framework层返回按键事件进入framework层,再从 framework层到kernel层执行关机任务。本文分析过程将分成两篇,(1)Framework层 (2)JNI和Kernel层,代码基于自己的android4.3源码。
一. 长按power key对应的handler代码:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
private final Runnable mPowerLongPress = new Runnable() {
@Override
public void run() {
// The context isn't read
if (mLongPressOnPowerBehavior < 0) {
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
}
int resolvedBehavior = mLongPressOnPowerBehavior;
if (FactoryTest.isLongPressOnPowerOffEnabled()) {
resolvedBehavior = LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
}
switch (resolvedBehavior) {
case LONG_PRESS_POWER_NOTHING:
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
performAuditoryFeedbackForAccessibilityIfNeed();
}
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
showGlobalActionsDialog();
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(resolvedBehavior == LONG_PRESS_POWER_SHUT_OFF);
break;
}
}
};
run()函数中通过从mLongPressOnPowerBehavior向resolvedBehavior传入值,从而基于resolvedBehavior决定关机动作。下面是对代码的分析:
1. mLongPressOnPowerBehavior初始值为-1,所以会通过getInteger()从config_longPressOnPowerBehavior配置选项中得到mLongPressOnPowerBehavior。
int mLongPressOnPowerBehavior = -1;
2. 检查是否是FactoryTest模式enable了长按powerkey进行关机的选项,是则LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM。
3. switch(resolvedBehavior)来决定关机动作:
void sendCloseSystemWindows(String reason) {
sendCloseSystemWindows(mContext, reason);
}
static void sendCloseSystemWindows(Context context, String reason) {
if (ActivityManagerNative.isSystemReady()) {
try {
ActivityManagerNative.getDefault().closeSystemDialogs(reason);
} catch (RemoteException e) {
}
}
}
最后,showGlobalActionsDialog()会显示请求的对话框; public void shutdown(boolean confirm);
二、显示power off对话框:showGlobalActionsDialog()
showGlobalActionsDialog()的定义:
void showGlobalActionsDialog() {
if (mGlobalActions == null) {
mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
}
final boolean keyguardShowing = keyguardIsShowingTq();
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
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.
mKeyguardMediator.userActivity();
}
}
下面是对代码的分析:
1. 这里首先new GlobalActions,注意一下GlobalActions()的后一个参数是mWindowManagerFuncs;
2. 然后会调用mGlobalActions.showDialog()来显示对话框;
3. 最后根据keyguardShowing的值,也就是是否在锁屏状态,决定是否需要userActivity。一般不会有这种情况。
显示对话框的具体调用是mGlobalActions.showDialog(),它在frameworks/base/policy/src/com/android/internal/policy/impl/GlobalActions.java中。
/**
* 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();
}
}
再进入mGlobalActions.handleShow()中,handleShow()中调用了createDialog(),showDialog()、handleShow()和createDialog()都是GlobalActions类的私有函数。
private void handleShow() {
awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
attrs.setTitle("GlobalActions");
mDialog.getWindow().setAttributes(attrs);
mDialog.show();
mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
}
createDialog()是最后要显示的对话框的内容,这个函数虽然很长,但是耐心分析很快就能知道它都做了什么。
/**
* Create the global actions dialog.
* @return A new dialog.
*/
private GlobalActionsDialog createDialog() {
// Simple toggle style if there's no vibrator, otherwise use a tri-state
if (!mHasVibrator) {
mSilentModeAction = new SilentModeToggleAction();
} else {
mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
}
mAirplaneModeOn = new ToggleAction(
R.drawable.ic_lock_airplane_mode,
R.drawable.ic_lock_airplane_mode_off,
R.string.global_actions_toggle_airplane_mode,
R.string.global_actions_airplane_mode_on_status,
R.string.global_actions_airplane_mode_off_status) {
void onToggle(boolean on) {
if (mHasTelephony && Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
mIsWaitingForEcmExit = true;
// Launch ECM exit dialog
Intent ecmDialogIntent =
new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(ecmDialogIntent);
} else {
changeAirplaneModeSystemSetting(on);
}
}
@Override
protected void changeStateFromPress(boolean buttonOn) {
if (!mHasTelephony) return;
// In ECM mode airplane state cannot be changed
if (!(Boolean.parseBoolean(
SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
mState = buttonOn ? State.TurningOn : State.TurningOff;
mAirplaneState = mState;
}
}
public boolean showDuringKeyguard() {
return true;
}
public boolean showBeforeProvisioning() {
return false;
}
};
onAirplaneModeChanged();
mItems = new ArrayList();
// first: power off
mItems.add(
new SinglePressAction(
com.android.internal.R.drawable.ic_lock_power_off,
R.string.global_action_power_off) {
public void onPress() {
// shutdown by making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown(true);
}
public boolean onLongPress() {
mWindowManagerFuncs.rebootSafeMode(true);
return true;
}
public boolean showDuringKeyguard() {
return true;
}
public boolean showBeforeProvisioning() {
return true;
}
});
// next: airplane mode
mItems.add(mAirplaneModeOn);
// next: bug report, if enabled
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0) {
mItems.add(
new SinglePressAction(com.android.internal.R.drawable.stat_sys_adb,
R.string.global_action_bug_report) {
public void onPress() {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(com.android.internal.R.string.bugreport_title);
builder.setMessage(com.android.internal.R.string.bugreport_message);
builder.setNegativeButton(com.android.internal.R.string.cancel, null);
builder.setPositiveButton(com.android.internal.R.string.report,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Add a little delay before executing, to give the
// dialog a chance to go away before it takes a
// screenshot.
mHandler.postDelayed(new Runnable() {
@Override public void run() {
try {
ActivityManagerNative.getDefault()
.requestBugReport();
} catch (RemoteException e) {
}
}
}, 500);
}
});
AlertDialog dialog = builder.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.show();
}
public boolean onLongPress() {
return false;
}
public boolean showDuringKeyguard() {
return true;
}
public boolean showBeforeProvisioning() {
return false;
}
});
}
// last: silent mode
if (mShowSilentToggle) {
mItems.add(mSilentModeAction);
}
// one more thing: optionally add a list of users to switch to
if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
addUsersToMenu(mItems);
}
mAdapter = new MyAdapter();
AlertParams params = new AlertParams(mContext);
params.mAdapter = mAdapter;
params.mOnClickListener = this;
params.mForceInverseBackground = true;
GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
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) {
return mAdapter.getItem(position).onLongPress();
}
});
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.setOnDismissListener(this);
return dialog;
}
因为本文主要分析关机流程,所以对后面几个选项就不分析了。回到handleShow()中,然后调用mDialog类中的函数做显示窗口的动作。
三、执行ShutdownThread.shutdown(),启动关机线程执行关机动作。
前面在GlobalActions.createDialog()中,当进入power off,然后onPress()的时候,mWindowManagerFuncs.shutdown(ture)将被调用。
public void onPress() {
// shutdown by making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown(true);
}
看一下mWindowManagerFuncs的定义,可以看到它是一个WindowManagerFuncs接口类。
private final WindowManagerFuncs mWindowManagerFuncs;
WindowManagerFuncs类是在/frameworks/base/core/java/android/view/WindowManagerPolicy.java中定义,它被包含在WindowManagerPolicy接口类中。
在WindowManagerFuncs类中,shutdown(boolean confirm)只给出了原型,并没有具体实现。搜索一下很容易发现真正被调用执行的是在/frameworks/base/services/java/com/android/server/wm/WindowManagerService.java中定义的shutdown(),即WindowManagerService.shutdown()。
// Called by window manager policy. Not exposed externally.
@Override
public void shutdown(boolean confirm) {
ShutdownThread.shutdown(mContext, confirm);
}
// Called by window manager policy. Not exposed externally.
@Override
public void rebootSafeMode(boolean confirm) {
ShutdownThread.rebootSafeMode(mContext, confirm);
}
shutdown()和rebootSafeMode()都在这里定义并会被window manager policy调用。这里已经指明真正执行关机线程的是ShutdownThread.shutdown(mContext, confirm)。
下面来看一下具体的关机流程:
ShutdownThread类在frameworks/base/services/java/com/android/server/power/ShutdownThread.java中定义,下面是shutdown()的代码。
/**
* 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 confirm true if user confirmation is needed before shutting down.
*/
public static void shutdown(final Context context, boolean confirm) {
mReboot = false;
mRebootSafeMode = false;
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();
}
sConfirmDialog = new AlertDialog.Builder(context)
.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);
}
}
1. 注意两个参数:context 和 confirm,context用来用来显示关机进程的对话,confirm确保是true如果关机之前需要确认。这一点很容易理解。
2. 在shutdown()将会调用shutdownInner(),longPressBehavior和resourceId是用来记录一些标志。
一般情况下confirm为true,即表示确认shutdown,然后系统会显示确认shutdown的对话框,同时执行beginShutdownSequence(),启动shutdown线程。如果不需要confirm就可以shutdown,则直接执行beginShutdownSequence(),启动shutdown线程。
3. 下面看一下beginShutdownSequence()的具体流程。
private static void beginShutdownSequence(Context context) {
synchronized (sIsStartedGuard) {
if (sIsStarted) {
Log.d(TAG, "Shutdown sequence already running, returning.");
return;
}
sIsStarted = true;
}
// throw up an indeterminate system dialog to indicate radio is
// shutting down.
ProgressDialog pd = new ProgressDialog(context);
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
pd.setIndeterminate(true);
pd.setCancelable(false);
pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
pd.show();
sInstance.mContext = context;
sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
// make sure we never fall asleep again
sInstance.mCpuWakeLock = null;
try {
sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
sInstance.mCpuWakeLock.setReferenceCounted(false);
sInstance.mCpuWakeLock.acquire();
} catch (SecurityException e) {
Log.w(TAG, "No permission to acquire wake lock", e);
sInstance.mCpuWakeLock = null;
}
// also make sure the screen stays on for better user experience
sInstance.mScreenWakeLock = null;
if (sInstance.mPowerManager.isScreenOn()) {
try {
sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
sInstance.mScreenWakeLock.setReferenceCounted(false);
sInstance.mScreenWakeLock.acquire();
} catch (SecurityException e) {
Log.w(TAG, "No permission to acquire wake lock", e);
sInstance.mScreenWakeLock = null;
}
}
// start the thread that initiates shutdown
sInstance.mHandler = new Handler() {
};
sInstance.start();
}
// static instance of this thread
private static final ShutdownThread sInstance = new ShutdownThread();
四、framework层最后的一步:ShutdownThread.run()
当进程执行到sInstance.start()后,就进入了framework层最后的一步:ShutdownThread.run()。
/**
* Makes sure we handle the shutdown gracefully.
* Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
*/
public void run() {
BroadcastReceiver br = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
// We don't allow apps to cancel this, so ignore the result.
actionDone();
}
};
/*
* Write a system property in case the system_server reboots before we
* get to the actual hardware restart. If that happens, we'll retry at
* the beginning of the SystemServer startup.
*/
{
String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
}
/*
* If we are rebooting into safe mode, write a system property
* indicating so.
*/
if (mRebootSafeMode) {
SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
}
Log.i(TAG, "Sending shutdown broadcast...");
// First send the high-level shut down broadcast.
mActionDone = false;
mContext.sendOrderedBroadcastAsUser(new Intent(Intent.ACTION_SHUTDOWN),
UserHandle.ALL, null, br, mHandler, 0, null, null);
final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
synchronized (mActionDoneSync) {
while (!mActionDone) {
long delay = endTime - SystemClock.elapsedRealtime();
if (delay <= 0) {
Log.w(TAG, "Shutdown broadcast timed out");
break;
}
try {
mActionDoneSync.wait(delay);
} catch (InterruptedException e) {
}
}
}
Log.i(TAG, "Shutting down activity manager...");
final IActivityManager am =
ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
if (am != null) {
try {
am.shutdown(MAX_BROADCAST_TIME);
} catch (RemoteException e) {
}
}
// Shutdown radios.
shutdownRadios(MAX_RADIO_WAIT_TIME);
// Shutdown MountService to ensure media is in a safe state
IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
public void onShutDownComplete(int statusCode) throws RemoteException {
Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
actionDone();
}
};
Log.i(TAG, "Shutting down MountService");
// Set initial variables and time out time.
mActionDone = false;
final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
synchronized (mActionDoneSync) {
try {
final IMountService mount = IMountService.Stub.asInterface(
ServiceManager.checkService("mount"));
if (mount != null) {
mount.shutdown(observer);
} else {
Log.w(TAG, "MountService unavailable for shutdown");
}
} catch (Exception e) {
Log.e(TAG, "Exception during MountService shutdown", e);
}
while (!mActionDone) {
long delay = endShutTime - SystemClock.elapsedRealtime();
if (delay <= 0) {
Log.w(TAG, "Shutdown wait timed out");
break;
}
try {
mActionDoneSync.wait(delay);
} catch (InterruptedException e) {
}
}
}
rebootOrShutdown(mReboot, mRebootReason);
}
run()函数虽然长,但是还是比较好理解的。
1. 如果分配的时间已经过去,不管radio和bluetooth状态如何都关闭。
2. 如果是rebootSafeMode,会设置一些系统属性。
3. 接下来是shutdown的流程,首先向APP层发送shutdown的广播。注意synchronized。
4. shutdown radios,调用shutdownRadios(),它也是ShutdownThread的成员函数。
5. shutdown mountservice,这样可以保证media的安全。
6. 设置初始化参数和时间。
7. rebootOrShutdown(),最后执行的shutdown流程的函数。
/**
* Do not call this directly. Use {@link #reboot(Context, String, boolean)}
* or {@link #shutdown(Context, boolean)} instead.
*
* @param reboot true to reboot or false to shutdown
* @param reason reason for reboot
*/
public static void rebootOrShutdown(boolean reboot, String reason) {
if (reboot) {
Log.i(TAG, "Rebooting, reason: " + reason);
try {
PowerManagerService.lowLevelReboot(reason);
} catch (Exception e) {
Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
}
} else if (SHUTDOWN_VIBRATE_MS > 0) {
// vibrate before shutting down
Vibrator vibrator = new SystemVibrator();
try {
vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
} catch (Exception e) {
// Failure to vibrate shouldn't interrupt shutdown. Just log it.
Log.w(TAG, "Failed to vibrate during shutdown.", e);
}
// vibrator is asynchronous so we need to wait to avoid shutting down too soon.
try {
Thread.sleep(SHUTDOWN_VIBRATE_MS);
} catch (InterruptedException unused) {
}
}
// Shutdown power
Log.i(TAG, "Performing low-level shutdown...");
PowerManagerService.lowLevelShutdown();
}
}
1. 注释表明:这是由shutdown(final Context context, boolean confirm)调用的。参数reboot如果是true,则reboot,false则shutdown;参数reason就是reboot reason。
2. 如果reboot是true,则会执行PowerManagerService.lowLevelReboot(reason)的过程;否则继续shutdown的过程。文章中分析shutdown的流程,所以reboot暂时不看。
3. 接下来系统会确保shutdown之前的机器震动,从注释很容易看明白。
4. Shutdown power开始进入底层的shutdown,即进入JNI层和kernel层。
PowerManagerService.lowLevelShutdown()是在frameworks/base/services/java/com/android/server/power/PowerManagerService.java中定义的。它是一个通往下层JNI的函数。
/**
* Low-level function turn the device off immediately, without trying
* to be clean. Most people should use {@link ShutdownThread} for a clean shutdown.
*/
public static void lowLevelShutdown() {
nativeShutdown();
}
nativeShutdown()就是通往下层的接口了。