深入解析Android关机

移动开发 Android
最后关机一定是在Linux kernel完成,说到底层其实就是Linux。所有的调用是为了停掉运行Android的虚拟机Dalvik和Dalvik与Kernel交互用的Server,至于关机按钮怎么用,就是改改系统调用的事情。

Android 关机顺序

  • 当我们长按电源按钮时,手机里究竟发了什么?
  • 什么又是关机顺序?
  • Android的关机顺序与Linux桌面系统有何不同?
  • 如何更改关机菜单?

当我们说起Android的关机顺序时,许多诸如此类的问题便会涌进我们的脑袋。 不过,在继续阅读之前,建议您首先能对开机顺序有一个了解开机顺序文章

Android是基于Linux内核的开源操作系统。尽管x86(x86 是一系列计算机微处理器指令集及其架构的统称,这种架构基于Intel 8086 CPU)是大多数Linux系统所采用的处理器架构,然而绝大多数Android系统却运行于ARM架构之上(ARM,又称Advanced RISC Machine,其前身为Acorn RISC Machine),除了来自Intel的Xolo设备。这种移动设备使用了Atom 1. 6Ghz x86处理器。 但不论哪种架构,Android的关机顺序都区别于Linux的桌面系统,如Ubuntu、Fedora等。 本文主要介绍Android的关机顺序, 如果想更多地了解Linux桌面系统的关机顺序,请参考Linux的启动与关闭流程

下图详细阐释了Android的关机顺序。

第一步: 按住电源按钮半秒钟(500ms)。

第二步: 之后,PhoneWindowManager.java 将捕获长按电源按钮这一事件并调用“interceptKeyBeforeQueueing”方法。

下面是处理长按电源键事件的代码片段

  1. /** {@inheritDoc} */ 
  2. @Override 
  3. public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { 
  4. .... 
  5. .... 
  6. .... 
  7. case KeyEvent.KEYCODE_POWER: { 
  8.      result &= ~ACTION_PASS_TO_USER; 
  9.        if (down) { 
  10.          if (isScreenOn && !mPowerKeyTriggered 
  11.                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { 
  12.                    mPowerKeyTriggered = true
  13.                    mPowerKeyTime = event.getDownTime(); 
  14.                    interceptScreenshotChord(); 
  15.             } 
  16.                ITelephony telephonyService = getTelephonyService(); 
  17.                 boolean hungUp = false
  18.                if (telephonyService != null) { 
  19.                    try { 
  20.                        if (telephonyService.isRinging()) { 
  21.                            // 如果在来电响铃时按下电源键,则系统将关闭来电提示 
  22.                             telephonyService.silenceRinger(); 
  23.                        } else if ((mIncallPowerBehavior 
  24.                                 & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 
  25.                                && telephonyService.isOffhook()) { 
  26.                             // 如果处在通话中且电源键挂断选项已启用,则按下电源键会结束当前通话 
  27.                             hungUp = telephonyService.endCall(); 
  28.                        } 
  29.                    } catch (RemoteException ex) { 
  30.                         Log.w(TAG, "ITelephony threw RemoteException", ex); 
  31.                    } 
  32.                } 
  33.                interceptPowerKeyDown(!isScreenOn || hungUp 
  34.                        || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered); 
  35.            } else { 
  36.                mPowerKeyTriggered = false
  37.                cancelPendingScreenshotChordAction(); 
  38.                if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) { 
  39.                    result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP; 
  40.                } 
  41.                mPendingPowerKeyUpCanceled = false
  42.            } 
  43.           break
  44.       } 
  45. .... 
  46. .... 
  47. .... 

上面的代码包含了对多种情形下对长按电源键时间的处理,例如静默来电响铃、屏幕截图以及关闭电源等。 系统将根据电源键被按住的时间长短以及相关按键的使用情况来决定如何恰当地处理当前的用户操作。 当电源键被按下且没有截屏操作触发时interceptPowerKeyDown 将被调用,这时其他的按键响应(其他按键响应指 interceptKeyBeforeQueueing 中其他cases)将不会被触发。

下面的代码展示了 interceptPowerKeyDown 函数内容, 函数将注册一个回调函数,在500毫秒超时事件(ViewConfiguration#getGlobalActionKeyTimeout())触发时启动 mPowerLongPress 线程。

  1. private void interceptPowerKeyDown(boolean handled) { 
  2.   mPowerKeyHandled = handled; 
  3.   if (!handled) { 
  4.        mHandler.postDelayed(mPowerLongPress, ViewConfiguration.getGlobalActionKeyTimeout()); 
  5.   } 

mPowerLongPress 线程的实现如下:

  1. private final Runnable mPowerLongPress = new Runnable() { 
  2.         @Override 
  3.         public void run() { 
  4.             // The context isn't read 
  5.             if (mLongPressOnPowerBehavior < 0) { 
  6.                 mLongPressOnPowerBehavior = mContext.getResources().getInteger( 
  7.                         com.android.internal.R.integer.config_longPressOnPowerBehavior); 
  8.             } 
  9.             int resolvedBehavior = mLongPressOnPowerBehavior; 
  10.             if (FactoryTest.isLongPressOnPowerOffEnabled()) { 
  11.                 resolvedBehavior = LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; 
  12.             } 
  13.   
  14.             switch (resolvedBehavior) { 
  15.             case LONG_PRESS_POWER_NOTHING: 
  16.                 break
  17.             case LONG_PRESS_POWER_GLOBAL_ACTIONS: 
  18.                 mPowerKeyHandled = true
  19.                 if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) { 
  20.                     performAuditoryFeedbackForAccessibilityIfNeed(); 
  21.                 } 
  22.                 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); 
  23.                 showGlobalActionsDialog(); 
  24.                 break
  25.             case LONG_PRESS_POWER_SHUT_OFF: 
  26.             case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: 
  27.                 mPowerKeyHandled = true
  28.                 performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); 
  29.                 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); 
  30.                 mWindowManagerFuncs.shutdown(resolvedBehavior == LONG_PRESS_POWER_SHUT_OFF); 
  31.                 break
  32.             } 
  33.         } 
  34.     }; 

第三步: 由上面代码的Switch分支可知,当程序进去Long_Press_Power_Global_Options时控制将移交给 GlobalActions 类, 该模块则负责显示关机选项的对话框,这些选项在各Android发行版(各OEM厂商定制的Android系统, 不同的手机型号和不同版本的Android系统)中不尽相同,通常包括关闭电源、飞行模式和屏幕截图。也可能包括其他一些选项按键。GlobalActions 类实现了一个showdialog方法,该方法将根据当前系统支持的菜单内容来创建这个对话框。

  1. void showGlobalActionsDialog() { 
  2.     if (mGlobalActions == null) { 
  3.         mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs); 
  4.     } 
  5.     final boolean keyguardShowing = keyguardIsShowingTq(); 
  6.     mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); 
  7.     if (keyguardShowing) { 
  8.          // 由于激活关机对话框需要长按电源键两秒以上,所以当对话框显示之后,屏幕的唤醒状态将被锁定,以方便用户浏览对话框中内容 
  9.         mKeyguardMediator.userActivity(); 
  10.     } 

第四步: 若用户选择“关闭电源“,则对系统的控制将交回给 PhoneWindowManager, 然后由PhoneWindowManager 启动关闭流程。

第五步: 整个关机过程起始于ShutdownThread模块中的shutdowninner方法。该方法首先创建一个确认对话框给用户, 用户可以选择确认关机或是取消关机操作。 如果用户选择确认,则系统将真正进入关机流程。

第六步: 如上所述,当用户点击确认按钮后beginShutdownSequence方法将被调用以启动关机顺序。

  1. private static void beginShutdownSequence(Context context) { 
  2.         synchronized (sIsStartedGuard) { 
  3.             if (sIsStarted) { 
  4.                 Log.d(TAG, "Shutdown sequence already running, returning."); 
  5.                 return
  6.             } 
  7.             sIsStarted = true
  8.         } 
  9.   
  10.         // 显示正在关闭电源的对话框 
  11.         ProgressDialog pd = new ProgressDialog(context); 
  12.         pd.setTitle(context.getText(com.android.internal.R.string.power_off)); 
  13.         pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress)); 
  14.         pd.setIndeterminate(true); 
  15.         pd.setCancelable(false); 
  16.  
  17. pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 
  18.         pd.show(); 
  19.         sInstance.mContext = context; 
  20.         sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 
  21.         // 阻止CPU进入休眠状态 
  22.         sInstance.mCpuWakeLock = null
  23.         try { 
  24.             sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock( 
  25.                     PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu"); 
  26.             sInstance.mCpuWakeLock.setReferenceCounted(false); 
  27.             sInstance.mCpuWakeLock.acquire(); 
  28.         } catch (SecurityException e) { 
  29.             Log.w(TAG, "No permission to acquire wake lock", e); 
  30.             sInstance.mCpuWakeLock = null
  31.         } 
  32.         // 电源关闭前一直保持屏幕唤醒状态,以便提升用户体验 
  33.         sInstance.mScreenWakeLock = null
  34.         if (sInstance.mPowerManager.isScreenOn()) { 
  35.             try { 
  36.                 sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock( 
  37.                         PowerManager.FULL_WAKE_LOCK, TAG + "-screen"); 
  38.                sInstance.mScreenWakeLock.setReferenceCounted(false); 
  39.                 sInstance.mScreenWakeLock.acquire(); 
  40.             } catch (SecurityException e) { 
  41.                 Log.w(TAG, "No permission to acquire wake lock", e); 
  42.                 sInstance.mScreenWakeLock = null
  43.             } 
  44.         } 
  45.         // 启动负责关机顺序的线程 
  46.         sInstance.mHandler = new Handler() { 
  47.         }; 
  48.         sInstance.start(); 
  49.     } 

运行函数,启动实际的关机流程

  1. public void run() { 
  2.         BroadcastReceiver br = new BroadcastReceiver() { 
  3.             @Override public void onReceive(Context context, Intent intent) { 
  4.                 // We don't allow apps to cancel this, so ignore the result. 
  5.                 actionDone(); 
  6.             } 
  7.         }; 
  8.   
  9.         /* 
  10.          *  写入一个系统参数,以防Android系统中的System Server 
  11.          * (一个运行于Dalvik虚拟机与真实系统内核间的server,负责虚拟机与内核的通信)在真实硬件重启前完成重启。 
  12.          * 当上述情况发生时, 则在System Server完成启动后重试之前的重启操作。 
  13.          */ 
  14.         { 
  15.             String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : ""); 
  16.             SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); 
  17.         } 
  18.   
  19.         /* 
  20.          * 写入一个系统参数以便重启后进入安全模式 
  21.          */ 
  22.         if (mRebootSafeMode) { 
  23.             SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); 
  24.         } 
  25.   
  26.         Log.i(TAG, "Sending shutdown broadcast..."); 
  27.   
  28.         // 关闭移动通信 
  29.         mActionDone = false
  30.         Intent intent = new Intent(Intent.ACTION_SHUTDOWN); 
  31.         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 
  32.         mContext.sendOrderedBroadcastAsUser(intent, 
  33.                 UserHandle.ALL, null, br, mHandler, 0nullnull); 
  34.   
  35.         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME; 
  36.         synchronized (mActionDoneSync) { 
  37.             while (!mActionDone) { 
  38.                 long delay = endTime - SystemClock.elapsedRealtime(); 
  39.                 if (delay <= 0) { 
  40.                     Log.w(TAG, "Shutdown broadcast timed out"); 
  41.                     break
  42.                 } 
  43.                 try { 
  44.                     mActionDoneSync.wait(delay); 
  45.                 } catch (InterruptedException e) { 
  46.                 } 
  47.             } 
  48.         } 
  49.   
  50.         Log.i(TAG, "Shutting down activity manager..."); 
  51.   
  52.         final IActivityManager am = 
  53.             ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); 
  54.         if (am != null) { 
  55.             try { 
  56.                 am.shutdown(MAX_BROADCAST_TIME); 
  57.             } catch (RemoteException e) { 
  58.             } 
  59.         } 
  60.   
  61.         // 关闭移动通信 
  62.         shutdownRadios(MAX_RADIO_WAIT_TIME); 
  63.   
  64.         // 安全移除外部存储卡 
  65.         IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { 
  66.             public void onShutDownComplete(int statusCode) throws RemoteException { 
  67.                 Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown"); 
  68.                 actionDone(); 
  69.             } 
  70.         }; 
  71.   
  72.         Log.i(TAG, "Shutting down MountService"); 
  73.   
  74.         // 初始化变量,并设置关机超时时限 
  75.         mActionDone = false
  76.         final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME; 
  77.         synchronized (mActionDoneSync) { 
  78.             try { 
  79.                 final IMountService mount = IMountService.Stub.asInterface( 
  80.                         ServiceManager.checkService("mount")); 
  81.                 if (mount != null) { 
  82.                     mount.shutdown(observer); 
  83.                 } else { 
  84.                     Log.w(TAG, "MountService unavailable for shutdown"); 
  85.                 } 
  86.             } catch (Exception e) { 
  87.                 Log.e(TAG, "Exception during MountService shutdown", e); 
  88.             } 
  89.             while (!mActionDone) { 
  90.                 long delay = endShutTime - SystemClock.elapsedRealtime(); 
  91.                 if (delay <= 0) { 
  92.                     Log.w(TAG, "Shutdown wait timed out"); 
  93.                     break
  94.                 } 
  95.                 try { 
  96.                     mActionDoneSync.wait(delay); 
  97.                 } catch (InterruptedException e) { 
  98.                 } 
  99.             } 
  100.         } 
  101.   
  102.         rebootOrShutdown(mReboot, mRebootReason); 
  103.     } 

第七步: 当rebootOrShutdown方法被调用时,系统控制权首先转至底层函数 nativeShutdown(在com_android_server_power_PowerManagerService。cpp中定义) 并最终调用android_reboot函数(定义于android_reboot.c中)来完成整个关机顺序

  1. static void nativeShutdown(JNIEnv *env, jclass clazz) { 
  2.     android_reboot(ANDROID_RB_POWEROFF, 00); 

原文链接: javacodegeeks 翻译: ImportNew.com - 靳禹
译文链接: http://www.importnew.com/6356.html

责任编辑:闫佳明 来源: importnew
相关推荐

2014-06-23 10:31:09

Android启动过程

2013-03-26 13:55:45

Android Bro

2014-12-10 10:37:45

Android自定义布局

2016-05-18 17:15:17

互动出版网

2016-10-31 19:41:29

Java垃圾回收

2010-10-09 11:20:13

2010-09-17 15:44:21

网络协议

2009-09-02 14:00:34

C#文件处理

2013-04-01 10:12:39

2011-12-01 14:56:30

Java字节码

2023-12-12 07:16:34

HTML标签开发

2011-08-03 09:18:39

RIP路由协议RIP

2009-03-16 15:47:16

Java线程多线程

2022-02-16 14:29:21

Callable接口IDE

2011-04-07 10:51:57

路由

2011-04-07 10:35:37

路由

2011-04-07 10:35:11

路由

2020-10-07 14:20:41

Tomcat深入解析

2012-11-16 10:14:54

Windows 8

2011-04-07 10:34:12

路由
点赞
收藏

51CTO技术栈公众号