最近在項目上碰到了這樣的問題:在某個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:此方法有什麼不足之處或者有其他更好的方法,歡迎各位評論區賜教,不勝感謝
**********************華麗麗的分割線**********************
2018.8.25更新:
有坑!有坑!
採用上述改法之後,第三方採用以下方法進系統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
親測有效。