深入解析Android关机

Android 关机顺序

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

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

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

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

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

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

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

/** {@inheritDoc} */
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
....
....
....
case KeyEvent.KEYCODE_POWER: {
     result &= ~ACTION_PASS_TO_USER;
       if (down) {
         if (isScreenOn && !mPowerKeyTriggered
               && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                   mPowerKeyTriggered = true;
                   mPowerKeyTime = event.getDownTime();
                   interceptScreenshotChord();
            }
               ITelephony telephonyService = getTelephonyService();
                boolean hungUp = false;
               if (telephonyService != null) {
                   try {
                       if (telephonyService.isRinging()) {
                           // 如果在来电响铃时按下电源键,则系统将关闭来电提示
                            telephonyService.silenceRinger();
                       } else if ((mIncallPowerBehavior
                                & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                               && telephonyService.isOffhook()) {
                            // 如果处在通话中且电源键挂断选项已启用,则按下电源键会结束当前通话
                            hungUp = telephonyService.endCall();
                       }
                   } catch (RemoteException ex) {
                        Log.w(TAG, "ITelephony threw RemoteException", ex);
                   }
               }
               interceptPowerKeyDown(!isScreenOn || hungUp
                       || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered);
           } else {
               mPowerKeyTriggered = false;
               cancelPendingScreenshotChordAction();
               if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) {
                   result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP;
               }
               mPendingPowerKeyUpCanceled = false;
           }
          break;
      }
....
....
....
}

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

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

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

mPowerLongPress 线程的实现如下:

 

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;
            }
        }
    };

 

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

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

 

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

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

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

private static void beginShutdownSequence(Context context) {
        synchronized (sIsStartedGuard) {
            if (sIsStarted) {
                Log.d(TAG, "Shutdown sequence already running, returning.");
                return;
            }
            sIsStarted = true;
        }

        // 显示正在关闭电源的对话框
        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);

        // 阻止CPU进入休眠状态
        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;
        }

        // 电源关闭前一直保持屏幕唤醒状态,以便提升用户体验
        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;
            }
        }

        // 启动负责关机顺序的线程
        sInstance.mHandler = new Handler() {
        };
        sInstance.start();
    }

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

 

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();
            }
        };

        /*
         *  写入一个系统参数,以防Android系统中的System Server
         * (一个运行于Dalvik虚拟机与真实系统内核间的server,负责虚拟机与内核的通信)在真实硬件重启前完成重启。 
         * 当上述情况发生时, 则在System Server完成启动后重试之前的重启操作。
         */
        {
            String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
        }

        /*
         * 写入一个系统参数以便重启后进入安全模式
         */
        if (mRebootSafeMode) {
            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
        }

        Log.i(TAG, "Sending shutdown broadcast...");

        // 关闭移动通信
        mActionDone = false;
        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        mContext.sendOrderedBroadcastAsUser(intent,
                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) {
            }
        }

        // 关闭移动通信
        shutdownRadios(MAX_RADIO_WAIT_TIME);

        // 安全移除外部存储卡
        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");

        // 初始化变量,并设置关机超时时限
        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);
    }

 

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

static void nativeShutdown(JNIEnv *env, jclass clazz) {
    android_reboot(ANDROID_RB_POWEROFF, 0, 0);
}
原文链接: javacodegeeks 翻译: ImportNew.com - 靳禹
译文链接: http://www.importnew.com/6356.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 靳禹

(新浪微博:@imcom)A coder and a basketball player. Interested in programming, web security and mobile security

查看靳禹的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

2 条评论

  1. funnuy 说道:

    真正关机是在android_reboot…说不定最后关机还是Linux内核的那套,前面只是定制了在什么时候和怎样调用android_reboot。就像Linux桌面一样,关机按钮可以定制成休眠或执行其他程序一个道理。

    Thumb up 1 Thumb down 0

    • 靳禹 说道:

      没错,最后关机一定是在Linux kernel完成.说到底层其实就是Linux.前面的所有调用是为了停掉运行Android的虚拟机Dalvik和Dalvik与Kernel交互用的Server.至于关机按钮怎么用,就是改改系统调用的事情.要是有人能劫持了电源键的事件,就搞了.

      Thumb up 0 Thumb down 0

跳到底部
返回顶部