最近在项目上碰到了这样的问题:在某个apk界面长按power键来选择关机或者重启,apk会出现重启现象,并且更加尴尬的是,在另外的方案上面对比后发现没有问题,明明白白地显示这是系统的锅。
好吧,改!仔细研究关机/重启的相关源码后,修改了部分逻辑,问题解决。那就用一篇博客来记录下踩过的这个坑吧。
本文切入点为关机/重启在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调用内核实现关机/重启
敲黑板,重点来了!!!
通过抓log发现,在关闭MountService的时候,进程杀死后重启了,oh shit
为什么会是在这个时候重启?看起来好像跟SD卡有关联。继续百度,一下子百度不到就变换着方式百度。。。。。。
终于功夫不负有心人,拜读了大牛的文章后得到了解答,原来在关闭MountService的时候,如果有进程仍然在对SD卡进行操作(如:记录日志),占据SD卡的进程通常比较顽固,因此问题来了,这个进程在杀死之后可能会立刻重启,重启的次数可能是1、2、3、4次,于是乎就有这个恶性循环:杀死-重启-再杀死-再重启。。。于是乎,你看到的现象就是一闪一闪
关个机居然也这么糟心,浪费时间就算了,还一闪一闪,根本不能忍!
问题确认了,接下来就是how to fix。关机这里,上层有这么多操作了,遇到顽固进程不好使,咋整?简单粗暴点,试试跳过某些操作,直接进rebootOrShutdown调用底层,上层杀不掉,底层直接秒杀?说干就干,打开ShutdownThread.java源码,屏蔽掉什么显示弹框,发广播,关闭这服务那服务的逻辑,编译固件之后验证。
直接贴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:此方法有什么不足之处或者有其他更好的方法,欢迎各位评论区赐教,不胜感谢
有坑!有坑!
采用上述改法之后,第三方采用以下方法进系统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有操作,由于屏蔽了这部分代码,因此没有操作,所以出错。
private static final int MAX_SHUTDOWN_WAIT_TIME = 10*1000;//forlan modified to 10s for reboot and shutdown more quickly
亲测有效。