Android中ANR的觸發(fā)機(jī)制-Service篇

個(gè)人博客

http://www.milovetingting.cn

Android中ANR的觸發(fā)機(jī)制-Service篇

概述

ANR,即Application Not Responding,應(yīng)用程序不響應(yīng)。在Android系統(tǒng)中咸灿,對于事件的處理,都需要在一定的時(shí)間內(nèi)完成,如果處理超時(shí)的話,就會(huì)觸發(fā)ANR豪嚎,彈出不響應(yīng)的界面,讓用戶選擇等待或是立即結(jié)束應(yīng)用谈火。ANR機(jī)制的簡單流程:在事件發(fā)給應(yīng)用處理前侈询,會(huì)先發(fā)一個(gè)延時(shí)消息到系統(tǒng)的Looper中,如果應(yīng)用在規(guī)定的時(shí)間內(nèi)執(zhí)行完成糯耍,則會(huì)移除掉延時(shí)消息扔字。如果沒有在規(guī)定時(shí)間內(nèi)執(zhí)行完,就會(huì)在處理延時(shí)消息中温技,觸發(fā)ANR革为。

ANR主要場景:

  1. Service

  2. BroadcastReceiver

  3. ContentProvider

  4. Input:包括輸入和觸摸

觸發(fā)機(jī)制分析

下面對Service進(jìn)行源碼分析,源碼為Android9.0舵鳞。

首先從Service的啟動(dòng)來分析震檩。這里只分析startService的模式,bindService模式暫時(shí)不分析蜓堕。

附一張時(shí)序圖

Service的ANR機(jī)制

不管是通過Activity的startService還是非Activity的Context中的startService,最終都是調(diào)用ContextWrapper的startService方法:

//ContextWrapper
@Override
    public ComponentName startService(Intent service) {
        return mBase.startService(service);
    }

mBase對應(yīng)的具體Context類為ContextImpl恳蹲。ContextImpl的startService方法:

//ContextImpl
@Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);
    }

startService方法調(diào)用startServiceCommon方法

//ContextImpl
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
        try {
            validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            ComponentName cn = ActivityManager.getService().startService(
                mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), requireForeground,
                            getOpPackageName(), user.getIdentifier());
            if (cn != null) {
                if (cn.getPackageName().equals("!")) {
                    throw new SecurityException(
                            "Not allowed to start service " + service
                            + " without permission " + cn.getClassName());
                } else if (cn.getPackageName().equals("!!")) {
                    throw new SecurityException(
                            "Unable to start service " + service
                            + ": " + cn.getClassName());
                } else if (cn.getPackageName().equals("?")) {
                    throw new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

在這個(gè)方法里,跨進(jìn)程調(diào)用AMS的startService方法

//AMS
@Override
    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, boolean requireForeground, String callingPackage, int userId)
            throws TransactionTooLargeException {
        enforceNotIsolatedCaller("startService");
        // Refuse possible leaked file descriptors
        if (service != null && service.hasFileDescriptors() == true) {
            throw new IllegalArgumentException("File descriptors passed in Intent");
        }

        if (callingPackage == null) {
            throw new IllegalArgumentException("callingPackage cannot be null");
        }

        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
        synchronized(this) {
            final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            ComponentName res;
            try {
                //調(diào)用ActiveServices的startServiceLocked方法
                res = mServices.startServiceLocked(caller, service,
                        resolvedType, callingPid, callingUid,
                        requireForeground, callingPackage, userId);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return res;
        }
    }

AMS中調(diào)用ActiveServices的startServiceLocked方法:

//ActiveServices
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
            throws TransactionTooLargeException {
        //...
        ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
        return cmp;
    }

這個(gè)方法又調(diào)用了startServiceInnerLocked方法:

//ActiveServices
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
            boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
        //...
        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
        if (error != null) {
            return new ComponentName("!!", error);
        }
        //...
    }

這個(gè)方法調(diào)用bringUpServiceLocked方法:

//ActiveServices
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
        //...
        realStartServiceLocked(r, app, execInFg);
        //...      
    }

這個(gè)方法里調(diào)用realStartServiceLocked方法:

//ActiveServices
private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
        //...
        bumpServiceExecutingLocked(r, execInFg, "create");
        //...
        app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                    app.repProcState);
        //...
    }

這里先調(diào)用了bumpServiceExecutingLocked方法用來設(shè)置超時(shí)消息:

//ActiveServices
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
                + why + " of " + r + " in app " + r.app);
        else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
                + why + " of " + r.shortName);

        // For b/34123235: Services within the system server won't start until SystemServer
        // does Looper.loop(), so we shouldn't try to start/bind to them too early in the boot
        // process. However, since there's a little point of showing the ANR dialog in that case,
        // let's suppress the timeout until PHASE_THIRD_PARTY_APPS_CAN_START.
        //
        // (Note there are multiple services start at PHASE_THIRD_PARTY_APPS_CAN_START too,
        // which technically could also trigger this timeout if there's a system server
        // that takes a long time to handle PHASE_THIRD_PARTY_APPS_CAN_START, but that shouldn't
        // happen.)
        boolean timeoutNeeded = true;
        if ((mAm.mBootPhase < SystemService.PHASE_THIRD_PARTY_APPS_CAN_START)
                && (r.app != null) && (r.app.pid == android.os.Process.myPid())) {

            Slog.w(TAG, "Too early to start/bind service in system_server: Phase=" + mAm.mBootPhase
                    + " " + r.getComponentName());
            timeoutNeeded = false;
        }

        long now = SystemClock.uptimeMillis();
        if (r.executeNesting == 0) {
            r.executeFg = fg;
            ServiceState stracker = r.getTracker();
            if (stracker != null) {
                stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
            }
            if (r.app != null) {
                r.app.executingServices.add(r);
                r.app.execServicesFg |= fg;
                if (timeoutNeeded && r.app.executingServices.size() == 1) {
                    scheduleServiceTimeoutLocked(r.app);
                }
            }
        } else if (r.app != null && fg && !r.app.execServicesFg) {
            r.app.execServicesFg = true;
            if (timeoutNeeded) {
                scheduleServiceTimeoutLocked(r.app);
            }
        }
        r.executeFg |= fg;
        r.executeNesting++;
        r.executingStart = now;
    }

這個(gè)方法中調(diào)用scheduleServiceTimeoutLocked方法:

//ActiveServices
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        mAm.mHandler.sendMessageDelayed(msg,
                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }

調(diào)用ActivityManagerService的MainHandler發(fā)送一個(gè)SERVICE_TIMEOUT消息俩滥,這里具體根據(jù)是否為前臺消息發(fā)送的消息不同嘉蕾。

前臺服務(wù)超時(shí)時(shí)間:20s,后臺服務(wù)超時(shí)時(shí)間:200s

 // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;

    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

設(shè)置超時(shí)消息就先看到這里,接著看啟動(dòng)Service

調(diào)用ProcessRecord中的IApplicationThread類型的thread屬性的scheduleCreateService方法霜旧,即調(diào)用到了ActivityThread的內(nèi)部類ApplicationThread的scheduleCreateService方法:

//ActivityThread$ApplicationThrad
public final void scheduleCreateService(IBinder token,
                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;
            s.compatInfo = compatInfo;

            sendMessage(H.CREATE_SERVICE, s);
        }

這個(gè)方法里調(diào)用了ActivityThread的sendMessage方法:

//ActivityThread
void sendMessage(int what, Object obj) {
        sendMessage(what, obj, 0, 0, false);
    }

最終通過Handler發(fā)送了一個(gè)消息出去

//Handler
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

在mH的handleMessage中回調(diào)處理:

//ActivityThread$H.handleMessage
case CREATE_SERVICE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
                    handleCreateService((CreateServiceData)msg.obj);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
//ActivityThread
 private void handleCreateService(CreateServiceData data) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();

        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to instantiate service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }

        try {
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
            //完成Service的創(chuàng)建错忱,并回調(diào)onCreate方法
            service.onCreate();
            mServices.put(data.token, service);
            try {
                //通知移除延時(shí)消息
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to create service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }
    }

如果在規(guī)定的時(shí)間內(nèi)完成處理,則會(huì)調(diào)用AMS的serviceDoneExecuting:

//AMS
public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
        synchronized(this) {
            if (!(token instanceof ServiceRecord)) {
                Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
                throw new IllegalArgumentException("Invalid service token");
            }
            mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
        }
    }

這個(gè)方法調(diào)用ActiveServices中的serviceDoneExecutingLocked方法:

//ActiveServices
void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
        //...
        serviceDoneExecutingLocked(r, inDestroying, inDestroying);
        //...
    }

這個(gè)方法會(huì)調(diào)用另一個(gè)重載方法:

//ActiveServices
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
            boolean finishing) {
        //...
        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        //...
    }

在這個(gè)方法里移除了前面的延時(shí)消息挂据,就不會(huì)觸發(fā)ANR以清。

如果沒有及時(shí)移除這個(gè)消息,那么將會(huì)在ActivityManagerService的MainHandler中觸發(fā):

MainHandler收到消息的處理:

//ActivityManagerService$MainHandler.handleMessage
@Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                //...
                case SERVICE_TIMEOUT_MSG: {
                    mServices.serviceTimeout((ProcessRecord)msg.obj);
                } break;
                //...
            }
        }

這個(gè)方法調(diào)用ActiveServices中的serviceTimeout方法:

void serviceTimeout(ProcessRecord proc) {
        //...
        if (anrMessage != null) {
            mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
        }
    }

在這里彈出了不響應(yīng)的界面崎逃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掷倔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子个绍,更是在濱河造成了極大的恐慌勒葱,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巴柿,死亡現(xiàn)場離奇詭異凛虽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)广恢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門凯旋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事至非∧剖穑” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵荒椭,是天一觀的道長踏幻。 經(jīng)常有香客問我,道長戳杀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任夭苗,我火速辦了婚禮信卡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘题造。我一直安慰自己傍菇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布界赔。 她就那樣靜靜地躺著丢习,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淮悼。 梳的紋絲不亂的頭發(fā)上咐低,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機(jī)與錄音袜腥,去河邊找鬼见擦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛羹令,可吹牛的內(nèi)容都是我干的鲤屡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼福侈,長吁一口氣:“原來是場噩夢啊……” “哼酒来!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肪凛,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤堰汉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后伟墙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衡奥,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年远荠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矮固。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖档址,靈堂內(nèi)的尸體忽然破棺而出盹兢,到底是詐尸還是另有隱情,我是刑警寧澤守伸,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布绎秒,位于F島的核電站,受9級特大地震影響尼摹,放射性物質(zhì)發(fā)生泄漏见芹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一蠢涝、第九天 我趴在偏房一處隱蔽的房頂上張望玄呛。 院中可真熱鬧,春花似錦和二、人聲如沸徘铝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惕它。三九已至,卻和暖如春废登,著一層夾襖步出監(jiān)牢的瞬間淹魄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工堡距, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留揭北,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓吏颖,卻偏偏與公主長得像搔体,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子半醉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349

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