android persist apk 多次crash會進(jìn)入recovery模式

1.基本介紹

Google在Android 8.0加入該新功能份殿,稱之為rescue party救援程序。

主要監(jiān)控系統(tǒng)核心程序出現(xiàn)循環(huán)崩潰的時候,會啟動該程序德召,根據(jù)不同的救援級別做出一系列操作辑舷,看是否可恢復(fù)設(shè)備喻犁,最嚴(yán)重的時候則是通過進(jìn)入recovery然后提供用戶清空用戶數(shù)據(jù)恢復(fù)出廠設(shè)置解決。

代碼:

frameworks\base\services\core\java\com\android\server\RescueParty.java

1.級別

private static final int LEVEL_NONE = 0;

private static final intLEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;

private static final intLEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;

private static final intLEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;

private static final intLEVEL_FACTORY_RESET = 4;

2.觸發(fā)場景:

(1)system_server 在 5 分鐘內(nèi)重啟 5 次以上調(diào)整一次級別何缓。

(2)永久性系統(tǒng)應(yīng)用在 30 秒內(nèi)崩潰 5 次以上調(diào)整一次級別肢础。

2.分析

Threshold?

類 Threshold :這個類主要實(shí)現(xiàn)對監(jiān)控進(jìn)程的崩潰次數(shù)的計數(shù)邏輯,每監(jiān)控一個進(jìn)程則實(shí)例化一個對應(yīng)的對象碌廓,進(jìn)程標(biāo)識為uid传轰。

主要變量:

private final int uid;監(jiān)控進(jìn)程的uid

private final int triggerCount; 監(jiān)控進(jìn)程崩潰次數(shù)

private final long triggerWindow;監(jiān)控進(jìn)程對應(yīng)的時間邊界

主要方法:

public abstract int getCount();獲取崩潰次數(shù)

public abstract void setCount(int count);設(shè)置更新后的崩潰次數(shù)

public abstract long getStart();獲取該統(tǒng)計周期的起始時間

public abstract void setStart(long start);設(shè)置該統(tǒng)計周期的起始時間

public void reset() {重置崩潰次數(shù)和起始時間

? setCount(0);

? setStart(0);

}

public boolean incrementAndTest() {//通過調(diào)用這個函數(shù)實(shí)現(xiàn)崩潰次數(shù)更新和判斷是否超出該周期內(nèi)邊界時間限制

? finallong now = SystemClock.elapsedRealtime();//獲取當(dāng)前系統(tǒng)時間

? finallong window = now - getStart();//第一次的時候因?yàn)間etstart為0,所以都會大于triggerWindow谷婆,之后則通過window判斷目標(biāo)進(jìn)程是否已經(jīng)超出該周期的邊界時間限制慨蛙。

? if(window > triggerWindow) {//時間超出限制,開啟新統(tǒng)計周期

???????? setCount(1);

???????? setStart(now);

???????? returnfalse;

? }else {

???????? intcount = getCount() + 1;//崩潰統(tǒng)計次數(shù)加1

???????? setCount(count);

???????? EventLogTags.writeRescueNote(uid,count, window);

???????? Slog.w(TAG,"Noticed " + count + " events for UID " + uid + " inlast "

?????????????????????? +(window / 1000) + " sec");

???????? return(count >= triggerCount);//當(dāng)崩潰次數(shù)等于或者大于5次纪挎,返回true

? }

}


前文提到該救援程序主要實(shí)現(xiàn)對system_server和常駐進(jìn)程監(jiān)控期贫,這里分開分析

system_server進(jìn)程監(jiān)控

首先說下類BootThreshold繼承了Threshold

幾個需要說明的點(diǎn)

(1)監(jiān)控uid為android.os.Process.ROOT_UID =0,即zygote 進(jìn)程廷区,因?yàn)閟ystem_server 重啟必然導(dǎo)致zygote重啟

????triggerCount = 5

? ?????? triggerWindow = 300 *DateUtils.SECOND_IN_MILLIS

? 構(gòu)造函數(shù):

? publicBootThreshold() {

???????super(android.os.Process.ROOT_UID, 5, 300 * DateUtils.SECOND_IN_MILLIS);

?}

綜上:統(tǒng)計周期時間邊界為300s即5分鐘唯灵,次數(shù)限制5次

System_server重啟次數(shù)和周期起始時間寫入Settingsprovide

統(tǒng)計次數(shù)對應(yīng)的鍵值???? private static final StringPROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";

統(tǒng)計周期的起始時間對應(yīng)的鍵值private static final String PROP_RESCUE_BOOT_START ="sys.rescue_boot_start";

預(yù)編譯的時候就實(shí)例BootThreshold給對象sBoot

private static final Threshold sBoot = newBootThreshold();

監(jiān)控方法,在system_server每次啟動過程中有如下調(diào)用

SystemServer.startBootstrapServices

?==>RescueParty.noteBoot(mSystemContext);


?public static void noteBoot(Context context) {

? if(isDisabled()) return;

? if(sBoot.incrementAndTest()) {//如果5分鐘內(nèi)崩潰次數(shù)等于5次隙轻,則為true

???????? sBoot.reset();//首先重置統(tǒng)計信息

???????? incrementRescueLevel(sBoot.uid);//調(diào)整system_server的救援等級

???????? executeRescueLevel(context);//執(zhí)行救援操作

? }

}


private static voidincrementRescueLevel(int triggerUid)

???//每調(diào)用一次埠帕,救援等級+1,救援等級被寫入到SettingsProvide的"sys.rescue_level" 鍵值對中保存玖绿,默認(rèn)為LEVEL_NONE敛瓷,最高級別為LEVEL_FACTORY_RESET

? finalint level = MathUtils.constrain(

??????????????? SystemProperties.getInt(PROP_RESCUE_LEVEL,LEVEL_NONE) + 1,

??????????????? LEVEL_NONE,LEVEL_FACTORY_RESET);

? SystemProperties.set(PROP_RESCUE_LEVEL,Integer.toString(level));


? EventLogTags.writeRescueLevel(level,triggerUid);

? //調(diào)用PKMS的接口logCriticalInfo,寫入等級更新的log斑匪,并保存在PKMS的log信息記錄文件中呐籽,目錄/data/system/uiderrors.txt

? PackageManagerService.logCriticalInfo(Log.WARN,"Incremented rescue level to "

??????????????? +levelToString(level) + " triggered by UID " + triggerUid);

}


private static voidexecuteRescueLevel(Context context) {

? finalint level = SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE);//獲取救援等級

? if(level == LEVEL_NONE) return;


? Slog.w(TAG,"Attempting rescue level " + levelToString(level));

? try{

???????? executeRescueLevelInternal(context,level);//根據(jù)不同等級執(zhí)行相關(guān)救援操作

???????? EventLogTags.writeRescueSuccess(level);

???????? PackageManagerService.logCriticalInfo(Log.DEBUG,

?????????????????????? "Finishedrescue level " + levelToString(level));//寫入log到uiderrors.txt

? }catch (Throwable t) {

???????? finalString msg = ExceptionUtils.getCompleteMessage(t);

???????? EventLogTags.writeRescueFailure(level,msg);

???????? PackageManagerService.logCriticalInfo(Log.ERROR,

?????????????????????? "Failedrescue level " + levelToString(level) + ": " + msg);

? }

}


private static voidexecuteRescueLevelInternal(Context context, int level) throws Exception {

? switch(level) {

? ??? 救援等級1-3通過更深入的重置Setting屬性設(shè)置來實(shí)現(xiàn),4等級即最高等級通過進(jìn)入recovery,讓客戶重置data分區(qū)實(shí)現(xiàn)狡蝶。

???????? caseLEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:

??????????????? resetAllSettings(context,Settings.RESET_MODE_UNTRUSTED_DEFAULTS);//主要針對非系統(tǒng)進(jìn)程的屬性設(shè)置進(jìn)行重置

??????????????? break;

???????? caseLEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:

??????????????? resetAllSettings(context,Settings.RESET_MODE_UNTRUSTED_CHANGES);//針對非系統(tǒng)進(jìn)程屬性庶橱,來自系統(tǒng)默認(rèn)的屬性重置,其他刪除

??????????????? break;

???????? caseLEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:

??????????????? resetAllSettings(context,Settings.RESET_MODE_TRUSTED_DEFAULTS);//所有進(jìn)程系統(tǒng)默認(rèn)的屬性重置贪惹,其他刪除

??????????????? break;

???????? caseLEVEL_FACTORY_RESET://進(jìn)入recovery

??????????????? RecoverySystem.rebootPromptAndWipeUserData(context,TAG);//進(jìn)recovery

??????????????? break;

? }

}


private static voidresetAllSettings(Context context, int mode) throws Exception {


? Exceptionres = null;

? finalContentResolver resolver = context.getContentResolver();

? try{//重置系統(tǒng)級Setting 設(shè)置

???????? Settings.Global.resetToDefaultsAsUser(resolver,null, mode, UserHandle.USER_SYSTEM);

? }catch (Throwable t) {

???????? res= new RuntimeException("Failed to reset global settings", t);

? }

? for(int userId : getAllUserIds()) {//多用戶的時候苏章,所有用戶的Setting設(shè)置都要重置

???????? try{

??????????????? Settings.Secure.resetToDefaultsAsUser(resolver,null, mode, userId);

???????? }catch (Throwable t) {

??????????????? res= new RuntimeException("Failed to reset secure settings for " +userId, t);

???????? }

? }

? if(res != null) {

???????? throwres;

? }

}

常駐進(jìn)程崩潰

AppThreshold 繼承Threshold,主要實(shí)現(xiàn)對常駐應(yīng)用進(jìn)程的監(jiān)控

幾個需要說明的點(diǎn)

(1)監(jiān)控uid為傳入崩潰的應(yīng)用uid

????triggerCount = 5

? ?triggerWindow = 30 *DateUtils.SECOND_IN_MILLIS

? ?綜上:統(tǒng)計周期時間邊界為30s奏瞬,次數(shù)限制5次

? publicAppThreshold(int uid) {

???????? super(uid,5, 30 * DateUtils.SECOND_IN_MILLIS);

? }

次數(shù)和周期統(tǒng)計交給對象自己的變量count和start保存

區(qū)別于system_server重啟的監(jiān)控枫绅,應(yīng)用進(jìn)程比較多,建立一個array列表去保存uid 和匹配的AppThreshold對象硼端。

private static SparseArray<Threshold>sApps = new SparseArray<>();


當(dāng)應(yīng)用進(jìn)程出現(xiàn)Crash的時候并淋,都會回調(diào)到AMS,AMS調(diào)用appErrors.crashApplicationInner方法珍昨,這個方法里面有如下邏輯

ProcessRecord r

if (r != null && r.persistent) {//當(dāng)前Crash的進(jìn)程是否是常駐進(jìn)程县耽,是的話進(jìn)入并傳入uid

? RescueParty.notePersistentAppCrash(mContext,r.uid);

}

public static voidnotePersistentAppCrash(Context context, int uid) {

? if(isDisabled()) return;

? //為每一個崩潰過的常駐進(jìn)程實(shí)例化一個AppThreshold,并放在sApps保存

? Thresholdt = sApps.get(uid);

? if(t == null) {

???????? t= new AppThreshold(uid);

???????? sApps.put(uid,t);

? }

? 然后通過uid匹配獲取的AppThreshold進(jìn)行計數(shù)統(tǒng)計等操作曼尊,詳情同上文酬诀,不再贅述。

? if(t.incrementAndTest()) {

???????? t.reset();

???????? incrementRescueLevel(t.uid);

???????? executeRescueLevel(context);

? }

}


禁止場景

(1)PROP_ENABLE_RESCUE屬性值為false骆撇,并且PROP_DISABLE_RESCUE為true

(2)eng版本下

(3)手機(jī)連接usb模式

private static boolean isDisabled() {


? if(SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {

???????? returnfalse;

? }

//是否為eng版本

? if(Build.IS_ENG) {

???????? Slog.v(TAG,"Disabled because of eng build");

???????? returntrue;

? }

//是否有連接usb

? if(Build.IS_USERDEBUG && isUsbActive()) {

???????? Slog.v(TAG,"Disabled because of active USB connection");

???????? returntrue;

? }


? if(SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {

???????? Slog.v(TAG,"Disabled because of manual property");

???????? returntrue;

? }


? returnfalse;

}

其他場景

SettingProvide public的時候也會更新一次救援級別

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

installSystemProviders()->RescueParty.onSettingsProviderPublished(mContext);

???public static void onSettingsProviderPublished(Context context) {

???????executeRescueLevel(context);

??? }


服務(wù)初始化

voidcrashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,

intcallingPid,intcallingUid) {

瞒御。。神郊。

// If a persistent app is stuck in a crash loop, the device isn't very

// usable, so we want to consider sending out a rescue party.

if(r !=null&& r.persistent) {

RescueParty.notePersistentAppCrash(mContext, r.uid);

}

AppErrorResult result =newAppErrorResult();

TaskRecord task;

}


流程圖


處理方式:

代碼路徑如下:

? ? /frameworks/base/services/core/java/com/android/server/RescueParty.java

? ? 關(guān)閉可以直接

? ? ? ? private static boolean isDisabled() {

? ? ? ? ? ? return true;

? ? ? ? ? ? ....

? ? ? ? }

? ? ? ? 進(jìn)入recovery 的命令:

? ? ? private static void executeRescueLevelInternal(Context context, int level) throws Exception {

? ? ? ? ? ? ....

? ? ? ? case LEVEL_FACTORY_RESET:

RecoverySystem.rebootPromptAndWipeUserData(context,TAG);

break;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肴裙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子涌乳,更是在濱河造成了極大的恐慌蜻懦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夕晓,死亡現(xiàn)場離奇詭異宛乃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蒸辆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門征炼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躬贡,你說我怎么就攤上這事谆奥。” “怎么了拂玻?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵酸些,是天一觀的道長宰译。 經(jīng)常有香客問我,道長魄懂,這世上最難降的妖魔是什么沿侈? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮市栗,結(jié)果婚禮上肋坚,老公的妹妹穿的比我還像新娘。我一直安慰自己肃廓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布诲泌。 她就那樣靜靜地躺著盲赊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪敷扫。 梳的紋絲不亂的頭發(fā)上哀蘑,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音葵第,去河邊找鬼绘迁。 笑死,一個胖子當(dāng)著我的面吹牛卒密,可吹牛的內(nèi)容都是我干的缀台。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼哮奇,長吁一口氣:“原來是場噩夢啊……” “哼膛腐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鼎俘,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤哲身,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贸伐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勘天,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年捉邢,在試婚紗的時候發(fā)現(xiàn)自己被綠了脯丝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡歌逢,死狀恐怖巾钉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秘案,我是刑警寧澤砰苍,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布潦匈,位于F島的核電站,受9級特大地震影響赚导,放射性物質(zhì)發(fā)生泄漏茬缩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一吼旧、第九天 我趴在偏房一處隱蔽的房頂上張望凰锡。 院中可真熱鬧,春花似錦圈暗、人聲如沸掂为。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勇哗。三九已至,卻和暖如春寸齐,著一層夾襖步出監(jiān)牢的瞬間欲诺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工渺鹦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扰法,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓毅厚,卻偏偏與公主長得像塞颁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子卧斟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359