ANR觸發原理

ANR(Application Not responding),是指應用程序未響應,Android系統對於一些事件須要在必定的時間範圍內完成,若是超過預約時間能未能獲得有效響應或者響應時間過長,都會形成ANR。java

形成ANR的場景:app

  • Service Timeout:好比前臺服務在20s內未執行完成;
  • BroadcastQueue Timeout:好比前臺廣播在10s內未執行完成
  • ContentProvider Timeout:內容提供者,在publish過超時10s;
  • InputDispatching Timeout: 輸入事件分發超時5s,包括按鍵和觸摸事件。

觸發ANR的過程可分爲三個步驟: 埋炸彈, 拆炸彈, 引爆炸彈ide

 

<1> Service Timeout是位於」ActivityManager」線程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息時觸發。post

對於Service有兩類:this

  • 對於前臺服務,則超時爲SERVICE_TIMEOUT = 20s;
  • 對於後臺服務,則超時爲SERVICE_BACKGROUND_TIMEOUT = 200s

由變量ProcessRecord.execServicesFg來決定是否前臺啓動.spa

埋炸彈階段:在Service進程attach到system_server進程的過程當中會調用realStartServiceLocked()方法  (準確說是scheduleServiceTimeoutLocked方法)  中來埋下炸彈.線程

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //發送delay消息(SERVICE_TIMEOUT_MSG),
    bumpServiceExecutingLocked(r, execInFg, "create");
    try {
        ...
        //最終執行服務的onCreate()方法
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } catch (DeadObjectException e) {
        mAm.appDiedLocked(app);
        throw e;
    } finally {
        ...
    }
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ... 
    scheduleServiceTimeoutLocked(r.app);
}

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    long now = SystemClock.uptimeMillis();
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    
    //當超時後仍沒有remove該SERVICE_TIMEOUT_MSG消息,則執行service Timeout流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

拆炸彈階段:通過Binder等層層調用進入目標進程的主線程ActivityThread.handleCreateService()的過程. 在這個過程會建立目標服務對象,以及回調onCreate()方法, 緊接再次通過屢次調用回到system_server來執行serviceDoneExecuting.code

最終在serviceDoneExecutingLocked中移除服務超時消息SERVICE_TIMEOUT_MSGorm

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

        try {
            //建立ContextImpl對象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //建立Application對象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //調用服務onCreate()方法 
            service.onCreate();
            
            //拆除炸彈引線[見小節2.2.2]
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }
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) {
                //當前服務所在進程中沒有正在執行的service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        ...
    }
    ...

引爆炸彈階段:在system_server進程中有一個Handler線程, 名叫」ActivityManager」.當倒計時結束便會向該Handler線程發送 一條信息SERVICE_TIMEOUT_MSG,server

final class MainHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case SERVICE_TIMEOUT_MSG: {
                ...
                //【見小節2.3.2】
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
            ...
        }
        ...
    }
}
void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;

    synchronized(mAm) {
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        final long now = SystemClock.uptimeMillis();
        final long maxTime =  now -
                (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
        ServiceRecord timeout = null;
        long nextTime = 0;
        for (int i=proc.executingServices.size()-1; i>=0; i--) {
            ServiceRecord sr = proc.executingServices.valueAt(i);
            if (sr.executingStart < maxTime) {
                timeout = sr;
                break;
            }
            if (sr.executingStart > nextTime) {
                nextTime = sr.executingStart;
            }
        }
        if (timeout != null && mAm.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new FastPrintWriter(sw, false, 1024);
            pw.println(timeout);
            timeout.dump(pw, " ");
            pw.close();
            mLastAnrDump = sw.toString();
            mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
            mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
            anrMessage = "executing service " + timeout.shortName;
        }
    }

    if (anrMessage != null) {
        //當存在timeout的service,則執行appNotResponding
        mAm.appNotResponding(proc, null, null, false, anrMessage);
    }
}

其中anrMessage的內容爲」executing service [發送超時serviceRecord信息]」;

 

<2> BroadcastReceiver Timeout是位於」ActivityManager」線程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息時觸發。

對於廣播隊列有兩個: foreground隊列和background隊列:

  • 對於前臺廣播,則超時爲BROADCAST_FG_TIMEOUT = 10s;
  • 對於後臺廣播,則超時爲BROADCAST_BG_TIMEOUT = 60s ;

埋炸彈階段:

經過調用 BroadcastQueue.processNextBroadcast() 來處理廣播.其流程爲先處理並行廣播,再處理當前有序廣播,最後獲取並處理下條有序廣播.

final void processNextBroadcast(boolean fromMsg) {
    synchronized(mService) {
        ...
        //part 2: 處理當前有序廣播
        do {
            r = mOrderedBroadcasts.get(0);
            //獲取全部該廣播全部的接收者
            int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
            if (mService.mProcessesReady && r.dispatchTime > 0) {
                long now = SystemClock.uptimeMillis();
                if ((numReceivers > 0) &&
                        (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                    //當廣播處理時間超時,則強制結束這條廣播
                    broadcastTimeoutLocked(false);
                    ...
                }
            }
            if (r.receivers == null || r.nextReceiver >= numReceivers
                    || r.resultAbort || forceReceive) {
                if (r.resultTo != null) {
                    //處理廣播消息消息
                    performReceiveLocked(r.callerApp, r.resultTo,
                        new Intent(r.intent), r.resultCode,
                        r.resultData, r.resultExtras, false, false, r.userId);
                    r.resultTo = null;
                }
                //拆炸彈
                cancelBroadcastTimeoutLocked();
            }
        } while (r == null);
        ...

        //part 3: 獲取下條有序廣播
        r.receiverTime = SystemClock.uptimeMillis();
        if (!mPendingBroadcastTimeoutMessage) {
            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            //埋炸彈
            setBroadcastTimeoutLocked(timeoutTime);
        }
        ...
    }
}

對於廣播超時處理時機:

  1. 首先在part3的過程當中setBroadcastTimeoutLocked(timeoutTime) 設置超時廣播消息;
  2. 而後在part2根據廣播處理狀況來處理:
    • 當廣播接收者等待時間過長,則調用broadcastTimeoutLocked(false);
    • 當執行完廣播,則調用cancelBroadcastTimeoutLocked;
final void setBroadcastTimeoutLocked(long timeoutTime) {
    if (! mPendingBroadcastTimeoutMessage) {
        Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
        mHandler.sendMessageAtTime(msg, timeoutTime);
        mPendingBroadcastTimeoutMessage = true;
    }
}

設置定時廣播BROADCAST_TIMEOUT_MSG,即當前日後推mTimeoutPeriod時間廣播還沒處理完畢,則進入廣播超時流程。

拆炸彈階段:

在processNextBroadcast()過程, 執行完performReceiveLocked,便會拆除炸彈.

final void cancelBroadcastTimeoutLocked() {
    if (mPendingBroadcastTimeoutMessage) {
        mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
        mPendingBroadcastTimeoutMessage = false;
    }
}

引爆炸彈階段:

BroadcastHandler.handleMessage

private final class BroadcastHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_TIMEOUT_MSG: {
                synchronized (mService) {
                    //【見小節3.3.2】
                    broadcastTimeoutLocked(true);
                }
            } break;
            ...
        }
        ...
    }
}

不會引爆的四種狀況

  1. mOrderedBroadcasts已處理完成,則不會anr;
  2. 正在執行dexopt,則不會anr;
  3. 系統尚未進入ready狀態(mProcessesReady=false),則不會anr;
  4. 若是當前正在執行的receiver沒有超時,則從新設置廣播超時,不會anr;

<3> ContentProvider Timeout是位於」ActivityManager」線程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息時觸發。

ContentProvider 超時爲CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s. 這個跟前面的Service和BroadcastQueue徹底不一樣, 由Provider進程啓動過程相關.

埋炸彈階段:

埋炸彈的過程 實際上是在進程建立的過程,進程建立後會調用attachApplicationLocked()進入system_server進程. 10s以後引爆該炸彈

private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
    ProcessRecord app;
    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
            app = mPidsSelfLocked.get(pid); // 根據pid獲取ProcessRecord
        }
    } 
    ...
    
    //系統處於ready狀態或者該app爲FLAG_PERSISTENT進程則爲true
    boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
    List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

    //app進程存在正在啓動中的provider,則超時10s後發送CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息
    if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
        Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
        msg.obj = app;
        mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
    }
    
    thread.bindApplication(...);
    ...
}

拆炸彈階段:

當provider成功publish以後,便會拆除該炸彈

public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
   ...
   
   synchronized (this) {
       final ProcessRecord r = getRecordForAppLocked(caller);
       
       final int N = providers.size();
       for (int i = 0; i < N; i++) {
           ContentProviderHolder src = providers.get(i);
           ...
           ContentProviderRecord dst = r.pubProviders.get(src.info.name);
           if (dst != null) {
               ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
               
               mProviderMap.putProviderByClass(comp, dst); //將該provider添加到mProviderMap
               String names[] = dst.info.authority.split(";");
               for (int j = 0; j < names.length; j++) {
                   mProviderMap.putProviderByName(names[j], dst);
               }

               int launchingCount = mLaunchingProviders.size();
               int j;
               boolean wasInLaunchingProviders = false;
               for (j = 0; j < launchingCount; j++) {
                   if (mLaunchingProviders.get(j) == dst) {
                       //將該provider移除mLaunchingProviders隊列
                       mLaunchingProviders.remove(j);
                       wasInLaunchingProviders = true;
                       j--;
                       launchingCount--;
                   }
               }
               //成功pubish則移除該消息
               if (wasInLaunchingProviders) {
                   mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
               }
               synchronized (dst) {
                   dst.provider = src.provider;
                   dst.proc = r;
                   //喚醒客戶端的wait等待方法
                   dst.notifyAll();
               }
               ...
           }
       }
   }    
}

引爆炸彈階段:

在system_server進程中有一個Handler線程, 名叫」ActivityManager」.當倒計時結束便會向該Handler線程發送 一條信息CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG,

final class MainHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {
                ...
                ProcessRecord app = (ProcessRecord)msg.obj;
                synchronized (ActivityManagerService.this) {
                
                    processContentProviderPublishTimedOutLocked(app);
                }
            } break;
            ...
        }
        ...
    }
}

 

總結:

當出現ANR時,都是會調用到AMS.appNotResponding()方法

Timeout時長

  • 對於前臺服務,則超時爲SERVICE_TIMEOUT = 20s;
  • 對於後臺服務,則超時爲SERVICE_BACKGROUND_TIMEOUT = 200s
  • 對於前臺廣播,則超時爲BROADCAST_FG_TIMEOUT = 10s;
  • 對於後臺廣播,則超時爲BROADCAST_BG_TIMEOUT = 60s;
  • ContentProvider超時爲CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;

超時檢測

Service超時檢測機制:

  • 超過timeout時長沒有執行完相應操做來觸發移除延時消息,則會觸發anr;

BroadcastReceiver超時檢測機制:

  • 有序廣播的總執行時間超過 2* receiver個數 * timeout時長,則會觸發anr;
  • 有序廣播的某一個receiver執行過程超過 timeout時長,則會觸發anr;

另外:

  • 對於Service, Broadcast, Input發生ANR以後,最終都會調用AMS.appNotResponding;
  • 對於provider,在其進程啓動時publish過程可能會出現ANR, 則會直接殺進程以及清理相應信息,而不會彈出ANR的對話框. appNotRespondingViaProvider()過程會走appNotResponding(), 
相關文章
相關標籤/搜索