安卓ANR問(wèn)題1_ANR問(wèn)題類(lèi)型及產(chǎn)生原理

ANR問(wèn)題類(lèi)型及產(chǎn)生原理

ANR(Application Not Responding):即應(yīng)用無(wú)響應(yīng). 在日常使用安卓手機(jī)的過(guò)程中, 對(duì)最anr最直接的印象就是手機(jī)彈框顯示應(yīng)用未響應(yīng). 選擇繼續(xù)等待或者關(guān)閉.
如果應(yīng)用程序的主線程在規(guī)定的時(shí)間內(nèi), 沒(méi)有完成特定操作和事件, 就會(huì)發(fā)生ANR.

四種ANR類(lèi)型

  1. KeyDispatchTimeout : input事件在5S內(nèi)沒(méi)有處理完成發(fā)生ANR
  2. ServiceTimeout : bind,create,start,unbind等操作,前臺(tái)Service在20s內(nèi),后臺(tái)
    Service在200s內(nèi)沒(méi)有處理完成發(fā)生ANR
  3. BroadcastTimeout : BroadcastReceiver onReceiver處理事務(wù)時(shí)前臺(tái)廣播在10S內(nèi),后臺(tái)廣播在60s內(nèi) (應(yīng)用程序應(yīng)該避免在BroadcastReceiver里做耗時(shí)的操作或計(jì)算。如果響應(yīng)Intent廣播需要執(zhí)行一個(gè)耗時(shí)的動(dòng)作的話勉躺,應(yīng)用程序應(yīng)該啟動(dòng)一個(gè) Service).
    沒(méi)有處理完成發(fā)生ANR
  4. ProcessContentProviderPublishTimedOutLocked : ContentProvider publish在10s內(nèi)沒(méi)有處理完成發(fā)
    生ANR

其中第四種ANR發(fā)生的概率最小.

ANR產(chǎn)生的常見(jiàn)原因

  1. 主線程耗時(shí)操作,如復(fù)雜的layout,龐大的for循環(huán),IO等. (實(shí)際APP開(kāi)發(fā)時(shí)開(kāi)發(fā)者會(huì)避開(kāi)這種, 沒(méi)有見(jiàn)到過(guò)這種問(wèn)題產(chǎn)生ANR);
  2. 主線程被子線程同步鎖block. (當(dāng)子線程先拿著鎖, 主線程等待這把鎖的時(shí)候, 子線程太耗時(shí). 導(dǎo)致主線程一直被阻塞, 從而ANR)
  3. 主線程被Binder對(duì)端阻塞
  4. Binder被占滿導(dǎo)致主線程無(wú)法和SystemServer通信
  5. 得不到系統(tǒng)資源(CPU/RAM/IO) (耗時(shí)的動(dòng)畫(huà)需要大量的計(jì)算工作癌瘾,可能導(dǎo)致CPU負(fù)載過(guò)重.)

ANR觸發(fā)機(jī)制

ANR有四種類(lèi)型, 所以可以從這四種類(lèi)型去了解ANR觸發(fā)機(jī)制.

1 Service發(fā)生ANR的機(jī)制

Service Timeout是AMS中MainHandler收到SERVICE_TIMEOUT_MSG消息時(shí)觸發(fā)。

由ServiceTimeout原因發(fā)生的ANR有兩種, 前臺(tái)服務(wù)未響應(yīng)(20s) 和 后臺(tái)服務(wù)未響應(yīng)(200s).

1.1 startService()觸發(fā)ANR流程:

  1. 在用戶進(jìn)程A中, 當(dāng)發(fā)起startService的時(shí)候, 通過(guò)binder通信, 通過(guò)IActivityManager#startService調(diào)用AMS中startService()方法. (這一步從用戶進(jìn)程跨越到system_server進(jìn)程, 調(diào)用AMS的方法)
    備注:IActivityManager對(duì)象保存在進(jìn)程A的單例Singleton<IActivityManager>中饵溅,進(jìn)程啟動(dòng)時(shí)查詢ServiceManager#getService獲得妨退,具有緩存作用
  1. 在AMS中, 當(dāng)realStartServiceLocked()啟動(dòng)服務(wù)的時(shí)候, 其內(nèi)部會(huì)操作AMS的MainHandler調(diào)用sendMessageAtTime()方法發(fā)送SERVICE_TIMEOUT_MSG消息埋下炸彈, 并通過(guò)參數(shù)指定消息發(fā)送的時(shí)間. (當(dāng)AMS埋好炸彈之后, 通過(guò)binder把目標(biāo)服務(wù)創(chuàng)建所需要的信息傳遞給目標(biāo)服務(wù)進(jìn)程B, 進(jìn)行服務(wù)的創(chuàng)建)

    在這里插入圖片描述

  2. 在服務(wù)進(jìn)程B中, 服務(wù)進(jìn)程將AMS傳遞的過(guò)來(lái)的服務(wù)創(chuàng)建信息進(jìn)行打包, 然后ApplicationThread內(nèi)部類(lèi)使用消息傳遞機(jī)制讓ActivityThread外部類(lèi)異步執(zhí)行service.onCreate回調(diào). 當(dāng)service創(chuàng)建完畢之后, 服務(wù)進(jìn)程B會(huì)再次binder通信, 告訴AMS調(diào)用serviceDoneExecuting(), 該方法最后會(huì)執(zhí)行Handler.removeMessages()把之前埋下的炸彈拆除.
    所以, 回調(diào)完Service的onCreate()方法之后便會(huì)移除啟動(dòng)服務(wù)超消息SERVICE_TIMEOUT_MSG.
    Service啟動(dòng)過(guò)程出現(xiàn)ANR,”executing service [發(fā)送超時(shí)serviceRecord信息]”蜕企, 這往往是service的onCreate()回調(diào)方法執(zhí)行時(shí)間過(guò)長(zhǎng)咬荷。

  3. 當(dāng)炸彈沒(méi)有及時(shí)被拆除. 當(dāng)SERVICE_TIMEOUT_MSG爆炸消息被AMS的MainHandler收到的時(shí)候, 就會(huì)發(fā)生ANR.

    在這里插入圖片描述

注: 1: 當(dāng)啟動(dòng)進(jìn)程A發(fā)起startService的時(shí)候, AMS如果發(fā)現(xiàn)目標(biāo)服務(wù)的進(jìn)程還沒(méi)有啟動(dòng), 會(huì)啟動(dòng)一個(gè)進(jìn)程. 但是啟動(dòng)這個(gè)進(jìn)程的時(shí)間不算在ANR的爆炸時(shí)間內(nèi). 因?yàn)橹挥挟?dāng)AMS真正調(diào)用realStartServiceLocked()之后, 才會(huì)埋下炸彈.

注2: 在ActivityManagerService實(shí)例化的時(shí)候, AMS會(huì)新開(kāi)的一條HandlerThread線程,在這個(gè)線程里會(huì)準(zhǔn)備好一個(gè)Looper. 然后使用有參構(gòu)造指定之前線程準(zhǔn)備好的Looper,實(shí)例化一個(gè)Handler.
該Handler就是AMS中的mHandler.
所有埋炸彈的Message都會(huì)在這個(gè)Looper中的, MessageQueue中進(jìn)行入隊(duì). 最終回調(diào)MainHandler.handleMessage()方法.

注3: AMS和ActivityService
在ActivityManagerService實(shí)例化的時(shí)候, 會(huì)實(shí)例化一個(gè)ActiveService成員變量.
在實(shí)例化ActiveService的時(shí)候, AMS會(huì)把this傳給ActiveService, 然后ActiveService也有一個(gè)AMS的成員變量.
AMS.mServices和AS.mAm.

注4: ApplicationThread是ActivityThread的普通內(nèi)部類(lèi), 當(dāng)system_server進(jìn)程通過(guò)binder通信調(diào)用ApplicationThread中的scheduleCreateService()方法時(shí), 在ApplicationThread該方法內(nèi)部往外部ActivityThread發(fā)送消息, 當(dāng)ActivityThread進(jìn)行handle調(diào)用的時(shí)候, 創(chuàng)建服務(wù).

1.2 產(chǎn)生ANR的源碼中與"埋炸彈", "拆炸彈"有關(guān)的代碼

1) 埋炸彈

[ActiveSErvices.java]
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //發(fā)送delay消息(SERVICE_TIMEOUT_MSG)
    bumpServiceExecutingLocked(r, execInFg, "create");
   ...
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ... 
    scheduleServiceTimeoutLocked(r.app);
}

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    ...
    //當(dāng)超時(shí)后仍沒(méi)有remove該SERVICE_TIMEOUT_MSG消息,則執(zhí)行service Timeout流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

2) 拆炸彈

在目標(biāo)進(jìn)程的服務(wù)創(chuàng)建之后, 會(huì)remove掉AMS, MessageQueue之中的超時(shí)消息, 當(dāng)這個(gè)消息被remove掉之后, 自然不會(huì)再被MainHandler處理, 也就不會(huì)發(fā)生ANR.

[ActivityThread.java]
 private void handleCreateService(CreateServiceData data) {
        ...
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        ...

        try {
            //創(chuàng)建ContextImpl對(duì)象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //創(chuàng)建Application對(duì)象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //調(diào)用服務(wù)onCreate()方法 
            service.onCreate();
            
            //拆除炸彈引線
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }
[ActivityThread.java]
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {
    ...
    if (r.executeNesting <= 0) {
        if (r.app != null) {
            r.app.execServicesFg = false;
            r.app.executingServices.remove(r);
            if (r.app.executingServices.size() == 0) {
                //當(dāng)前服務(wù)所在進(jìn)程中沒(méi)有正在執(zhí)行的service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        ...
    }
    ...
}

2 BroadcastReceiver發(fā)生ANR的機(jī)制

BroadcastReceiver Timeout是AMS中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息時(shí)觸發(fā).
其中, 前臺(tái)廣播超時(shí)mTimeoutPeriod為10s, 后臺(tái)廣播超時(shí)mTimeoutPeriod為 60s.

廣播發(fā)送的時(shí)候有兩種方式, 一種是 "默認(rèn)廣播" (并行廣播), 第二種是 "有序廣播" (串行廣播).
只有串行廣播才需要考慮超時(shí)轻掩,因?yàn)榻邮照呤谴刑幚淼男移梗耙粋€(gè)receiver處理慢,會(huì)影響后一個(gè)receiver唇牧;并行廣播 通過(guò)一個(gè)循環(huán)一次性向所有的receiver分發(fā)廣播事件罕扎,所以不存在彼此影響的問(wèn)題,則沒(méi)有廣播超時(shí)丐重;

串行廣播超時(shí)情況1:某個(gè)廣播總處理時(shí)間 > 2* receiver總個(gè)數(shù) * mTimeoutPeriod, 其中mTimeoutPeriod
串行廣播超時(shí)情況2:某個(gè)receiver的執(zhí)行時(shí)間超過(guò)mTimeoutPeriod

Broadcast的ANR流程如下圖所示:


BBABA.png
  1. 圖中1,2,3 步從用戶進(jìn)程通過(guò)binder通信調(diào)用AMS的broadcastIntentLocked()方法.
    在該方法中, 首先進(jìn)行了一系列的廣播驗(yàn)證.
    然后先并行處理動(dòng)態(tài)注冊(cè)的廣播, 然后合并動(dòng)態(tài)注冊(cè)廣播和靜態(tài)注冊(cè)的廣播receivers進(jìn)行串行處理. (原因: 讓靜態(tài)注冊(cè)的廣播串行化腔召,能防止出現(xiàn)瞬間啟動(dòng)大量進(jìn)程的噴井效應(yīng)。)
    由于我們ANR只會(huì)在處理串行廣播的時(shí)候發(fā)生, 接下來(lái)我們只關(guān)注串行廣播處理的流程.

  2. 圖中第4,5步, 在第4步中AMS按照并行廣播和串行廣播兩種, 分別要發(fā)送的廣播入相應(yīng)的隊(duì)列, 然后再通過(guò)scheduleBroadcastLocked()方法 內(nèi)部使用BroadcastHandler實(shí)例 sendMessage (BroadcastHandler是BroadcastQueue類(lèi)的一個(gè)普通內(nèi)部類(lèi)).
    備注:這里的BroadcastHandler實(shí)例, 是在AMS啟動(dòng)的時(shí)候, AMS構(gòu)造方法中進(jìn)行實(shí)例化的, 在new這個(gè)handler的時(shí)候, 把AMS構(gòu)造器之前的準(zhǔn)備好Looper給了該Handler.

  3. 圖中第6, 7步, 當(dāng)系統(tǒng)回調(diào)handlerMessage()方法的時(shí)候, 其內(nèi)部進(jìn)行廣播的處理. 當(dāng)然我們關(guān)注的是處理串行廣播的流程.

  4. 第9步, 當(dāng)前廣播超時(shí), 強(qiáng)制結(jié)束廣播.

  5. 10,11 通過(guò)binder通信告訴ActivityThread, 讓他去sendMessage(Receiver), ActivityThread處理這個(gè)消息的時(shí)候回調(diào)BroadcastReceiver的onReceiver()方法, 完成廣播接受處理.

  6. BroadcastReceiver完成onReceiver()回調(diào). 而B(niǎo)roadcastQueue向ActivityThread發(fā)起B(yǎng)inder通信之后, 在BroadcastQueue的processNextBroadcast()方法中執(zhí)行cancelBroadcastTimeoutLocked(), handler會(huì)發(fā)送removeMessages的消息, 把之前埋下的炸彈拆除.

綜上所訴: 當(dāng)分析ANR的問(wèn)題時(shí), 如果log顯示是BroadcastReceiver的ANR, 可以首先懷疑BroadCastReceiver.onRecieve()的問(wèn)題; 如果是Service的ANR, 可以首先懷疑是Service.onCreate()方法耗時(shí)的問(wèn)題.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扮惦,一起剝皮案震驚了整個(gè)濱河市臀蛛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖浊仆,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件客峭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡氧卧,警方通過(guò)查閱死者的電腦和手機(jī)桃笙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沙绝,“玉大人搏明,你說(shuō)我怎么就攤上這事∩撩剩” “怎么了肝谭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拢切。 經(jīng)常有香客問(wèn)我如捅,道長(zhǎng),這世上最難降的妖魔是什么样傍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任横缔,我火速辦了婚禮,結(jié)果婚禮上衫哥,老公的妹妹穿的比我還像新娘茎刚。我一直安慰自己,他們只是感情好撤逢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布膛锭。 她就那樣靜靜地躺著,像睡著了一般蚊荣。 火紅的嫁衣襯著肌膚如雪初狰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天互例,我揣著相機(jī)與錄音奢入,去河邊找鬼。 笑死敲霍,一個(gè)胖子當(dāng)著我的面吹牛俊马,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肩杈,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼柴我,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了扩然?” 一聲冷哼從身側(cè)響起艘儒,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后界睁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體觉增,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年翻斟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逾礁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡访惜,死狀恐怖嘹履,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情债热,我是刑警寧澤砾嫉,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站窒篱,受9級(jí)特大地震影響焕刮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜墙杯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一配并、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧高镐,春花似錦荐绝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)召夹。三九已至岩喷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間监憎,已是汗流浹背纱意。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲸阔,地道東北人偷霉。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像褐筛,于是被迫代替她去往敵國(guó)和親类少。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容