最近遇到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<Action>(); // 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()就是通往下层的接口了。