最近在项目上碰到了这样的问题:在某个apk界面长按power键来选择关机或者重启,apk会出现重启现象,并且更加尴尬的是,在另外的方案上面对比后发现没有问题,明明白白地显示这是系统的锅。

android长按home android长按power按键无法重启_android长按home


好吧,改!仔细研究关机/重启的相关源码后,修改了部分逻辑,问题解决。那就用一篇博客来记录下踩过的这个坑吧。

本文切入点为关机/重启在framework层的逻辑。我们重点关注两个类:PhoneWindowManager.java ShutdownThread.java

具体代码调用这里不作讲解,有兴趣的请自行查阅源码,大体流程是:
接收到power长按事件—>powerLongPress()—>弹出对话框—>选择关机/重启选项—>执行关机/重启

阅读源码后发现,关机过程的主要实现在ShutdownThread.java里面,重点是以下几个方法:

1.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 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;
        shutdownInner(context, confirm);
    }

2.reboot方法

/**
     * 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 the kernel (e.g. "recovery"), or null.
     * @param confirm true if user confirmation is needed before shutting down.
     */
    public static void reboot(final Context context, String reason, boolean confirm) {
        mReboot = true;
        mRebootSafeMode = false;
        mRebootHasProgressBar = false;
        mReason = reason;
        shutdownInner(context, confirm);
    }

3.rebootOrShutdown方法

/**
     * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
     * or {@link #shutdown(Context, boolean)} instead.
     *
     * @param context Context used to vibrate or null without vibration
     * @param reboot true to reboot or false to shutdown
     * @param reason reason for reboot/shutdown
     */
    public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {
        if (reboot) {
            Log.i(TAG, "Rebooting, reason: " + reason);
            PowerManagerService.lowLevelReboot(reason);
            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
            reason = null;
        } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {
            // vibrate before shutting down
            Vibrator vibrator = new SystemVibrator(context);
            try {
                vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
            } 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(reason);
    }

关机/重启主要做的一些工作:
发送关机广播
关闭AMS
关闭PMS
关闭MountService
PowerManagerService调用内核实现关机/重启

敲黑板,重点来了!!!

android长按home android长按power按键无法重启_sed_02


通过抓log发现,在关闭MountService的时候,进程杀死后重启了,oh shit

android长按home android长按power按键无法重启_重启_03


为什么会是在这个时候重启?看起来好像跟SD卡有关联。继续百度,一下子百度不到就变换着方式百度。。。。。。

android长按home android长按power按键无法重启_java_04


终于功夫不负有心人,拜读了大牛的文章后得到了解答,原来在关闭MountService的时候,如果有进程仍然在对SD卡进行操作(如:记录日志),占据SD卡的进程通常比较顽固,因此问题来了,这个进程在杀死之后可能会立刻重启,重启的次数可能是1、2、3、4次,于是乎就有这个恶性循环:杀死-重启-再杀死-再重启。。。于是乎,你看到的现象就是一闪一闪

android长按home android长按power按键无法重启_android长按home_05

关个机居然也这么糟心,浪费时间就算了,还一闪一闪,根本不能忍!

问题确认了,接下来就是how to fix。关机这里,上层有这么多操作了,遇到顽固进程不好使,咋整?简单粗暴点,试试跳过某些操作,直接进rebootOrShutdown调用底层,上层杀不掉,底层直接秒杀?说干就干,打开ShutdownThread.java源码,屏蔽掉什么显示弹框,发广播,关闭这服务那服务的逻辑,编译固件之后验证。

结果:秒杀关机/重启,时间还缩短了不少

android长按home android长按power按键无法重启_java_06

直接贴patch:

--- a/frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
@@ -251,7 +251,7 @@ public final class ShutdownThread extends Thread {
             }
             sIsStarted = true;
         }
-
+/*forlan modified to reboot and shutdown more quickly start
         // Throw up a system dialog to indicate the device is rebooting / shutting down.
         ProgressDialog pd = new ProgressDialog(context);

@@ -342,7 +342,7 @@ public final class ShutdownThread extends Thread {
                 sInstance.mScreenWakeLock = null;
             }
         }
-
+forlan modified to reboot and shutdown more quickly end*/
         // start the thread that initiates shutdown
         sInstance.mHandler = new Handler() {
         };
@@ -387,7 +387,7 @@ public final class ShutdownThread extends Thread {
         }

         Log.i(TAG, "Sending shutdown broadcast...");
-
+/*forlan modified to reboot and shutdown more quickly start
         // First send the high-level shut down broadcast.
         mActionDone = false;
         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
@@ -499,7 +499,7 @@ public final class ShutdownThread extends Thread {
             // done yet, trigger it now.
             uncrypt();
         }
-
+forlan modified to reboot and shutdown more quickly end*/
         rebootOrShutdown(mContext, mReboot, mReason);
     }

PS:此方法有什么不足之处或者有其他更好的方法,欢迎各位评论区赐教,不胜感谢

**********************华丽丽的分割线**********************

2018.8.25更新:

有坑!有坑!

android长按home android长按power按键无法重启_sed_07


采用上述改法之后,第三方采用以下方法进系统OTA升级会出错:

public static void rebootInstallPackage(final Context context, final File packageFile) {
    Thread thr = new Thread("Reboot") {
        @Override
        public void run() {
            try {
                RecoverySystem.installPackage(context, packageFile);
            } catch (IOException e) {
                Logger.e("forlan debug IOException rebootInstallPackage " + e);
            }
        }
    };
    thr.start();
}

recovery里面报错,找不到/cache/recovery/block.map:

[    2.093052] Supported API: 3
[    2.104192] charge_status 1, charged 1, status -2, capacity -9223372036854775808
[    2.161059] Finding update package...
[    2.237453] I:Update location: @/cache/recovery/block.map
[    2.237512] Opening update package...
[    2.262859] sysutil: Unable to open '/cache/recovery/block.map': No such file or directory
[    2.262915] E:failed to map file
[    2.287740] W:failed to read uncrypt status: No such file or directory
[    2.287988] I:@/cache/recovery/block.map
[    2.288000] 0
[    2.288006] time_total: 0
[    2.288012] retry: 0
[    2.288017]
[    2.288043] Installation aborted.
[    2.387535] I:Saving locale "zh_CN"

分析:

应该是在关闭MountService的时候对cache有操作,由于屏蔽了这部分代码,因此没有操作,所以出错。

最佳改法:

把MAX_SHUTDOWN_WAIT_TIME由20s改成10s

private static final int MAX_SHUTDOWN_WAIT_TIME = 10*1000;//forlan modified to 10s for reboot and shutdown more quickly

亲测有效。