Android ANR分析

[toc]

ANR分析

ANR觸發(fā)場景

在android系統(tǒng)中经柴,特定的操作需要在一定時間內(nèi)完成,超過限定的時間就會觸發(fā)ANR。

組件 時長
Service 前臺服務在20s內(nèi),后臺服務200s內(nèi) 未執(zhí)行完成
Content Provider 內(nèi)容提供者,在publish過超時10s;
Broadcast 前臺廣播在10s,后臺廣播60s內(nèi)未執(zhí)行完成
Input Dispatching 輸入事件分發(fā)超時5s,包括按鍵和觸摸事件。
Service

service timeout 是位于 ActivityManager 線程中的 AMS.MainHandler收到SERVICE_TIMEOUT_MSG觸發(fā)的剑逃,觸發(fā)時長上表中可以查看,區(qū)分前后臺是通過變量ProcessRecord.
execServicesFg

注冊ANR

在Service啟動流程中官辽,當service attach到system_server進程的過程中會調(diào)用realStartServiceLock(),方法進行ANR的注冊

  • ActivityService.java realStartServiceLocked
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    //發(fā)送delay消息(SERVICE_TIMEOUT_MSG)
    bumpServiceExecutingLocked(r, execInFg, "create");
    try {
        ...
        //最終執(zhí)行服務的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消息蛹磺,則執(zhí)行service Timeout流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

由此可見,是通過發(fā)送delay的消息進行ANR的注冊同仆,當超過時長(前臺20s,后臺200s就會觸發(fā)ANR)

解除注冊

在目標進程的線程中會進行撤銷發(fā)送上文中提到的SERVICE_TIMEOUT_MSG

  • 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對象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //創(chuàng)建Application對象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //調(diào)用服務onCreate()方法 
            service.onCreate();
            
            //解除SERVICE_TIMEOUT_MSG
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }

該過程會常見服務對象萤捆,并調(diào)用服務的onCreate方法,然后會通過多次調(diào)用回到system_server來執(zhí)行serviceDoneExecuting

  • AS.serviceDoneExecutingLocked
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) {
                //當前服務所在進程中沒有正在執(zhí)行的service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
        ...
    }
    ...
}

當service啟動完成,則移除延時消息SERVICE_TIMEOUT_MSG

觸發(fā)ANR

如果沒有在規(guī)定的時間內(nèi)解除延時消息俗或,那么則就會觸發(fā)ANR市怎。
在system_server進程中有一個Handler線程,叫做“ActivityManager”.當?shù)褂嫊r結(jié)束便會向該Handler線程發(fā)送一條SERVICE_TIMEOUT_MSG.

  • ActivityManagerService.java ::MainHandler
final class MainHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case SERVICE_TIMEOUT_MSG: {
                ...
                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辛慰,則執(zhí)行appNotResponding
        mAm.appNotResponding(proc, null, null, false, anrMessage);
    }
}
BroadcastReceiver

當ActivityManager線程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息時就會觸發(fā)ANR

處理廣播中的anr消息

在廣播的啟動流程中区匠,通過調(diào)用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))) {
                    //當廣播處理時間超時帅腌,則強制結(jié)束這條廣播
                    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;
                }
                //取消廣播超時ANR
                cancelBroadcastTimeoutLocked();
            }
        } while (r == null);
        ...

        //part 3: 獲取下條有序廣播
        r.receiverTime = SystemClock.uptimeMillis();
        if (!mPendingBroadcastTimeoutMessage) {
            long timeoutTime = r.receiverTime + mTimeoutPeriod;
            //設置廣播超時anr
            setBroadcastTimeoutLocked(timeoutTime);
        }
        ...
    }
}

對于廣播超時處理時機

  • 首先在part3過程中setBroadcastTimeoutLocked(timeoutTime) 設置超時廣播消息驰弄;

  • 然后在part2根據(jù)廣播處理情況來處理:

    • 當廣播接收者等待時間過長,則調(diào)用 broadcastTimeoutLocked(false);

    • 當執(zhí)行完廣播,則調(diào)用cancelBroadcastTimeoutLocked;

  • setBroadcastTimeoutLocked

final void setBroadcastTimeoutLocked(long timeoutTime) {
    if (! mPendingBroadcastTimeoutMessage) {
        Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
        mHandler.sendMessageAtTime(msg, timeoutTime);
        mPendingBroadcastTimeoutMessage = true;
    }
}

取消設置廣播的超時anr和service類似速客,但是通過靜態(tài)注冊的廣播超時受SharePreference(SP)影響

相關(guān)代碼如下,只有xml靜態(tài)注冊的光爆超時檢測過程會考慮是否有SP尚未完成戚篙,動態(tài)廣播并不受其影響

public final void finish() {
    if (mType == TYPE_COMPONENT) {
        final IActivityManager mgr = ActivityManager.getService();
        if (QueuedWork.hasPendingWork()) {
            //當SP有未同步到磁盤的工作,則需等待其完成溺职,才告知系統(tǒng)已完成該廣播
            QueuedWork.queue(new Runnable() {
                public void run() {
                    sendFinished(mgr);
                }
            }, false);
        } else {
            sendFinished(mgr);
        }
    } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
        final IActivityManager mgr = ActivityManager.getService();
        sendFinished(mgr);
    }
}

final void cancelBroadcastTimeoutLocked() {
    if (mPendingBroadcastTimeoutMessage) {
        mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
        mPendingBroadcastTimeoutMessage = false;
    }
}
觸發(fā)anr
  • BroadcastQueue.java ::BroadcastHandler
private final class BroadcastHandler extends Handler {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_TIMEOUT_MSG: {
                synchronized (mService) {
               
                    broadcastTimeoutLocked(true);
                }
            } break;
            ...
        }
        ...
    }
}

//fromMsg = true
final void broadcastTimeoutLocked(boolean fromMsg) {
    if (fromMsg) {
        mPendingBroadcastTimeoutMessage = false;
    }

    if (mOrderedBroadcasts.size() == 0) {
        return;
    }

    long now = SystemClock.uptimeMillis();
    BroadcastRecord r = mOrderedBroadcasts.get(0);
    if (fromMsg) {
        if (mService.mDidDexOpt) {
            mService.mDidDexOpt = false;
            long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
            setBroadcastTimeoutLocked(timeoutTime);
            return;
        }
        
        if (!mService.mProcessesReady) {
            return; //當系統(tǒng)還沒有準備就緒時岔擂,廣播處理流程中不存在廣播超時
        }

        long timeoutTime = r.receiverTime + mTimeoutPeriod;
        if (timeoutTime > now) {
            //如果當前正在執(zhí)行的receiver沒有超時,則重新設置廣播超時
            setBroadcastTimeoutLocked(timeoutTime);
            return;
        }
    }

    BroadcastRecord br = mOrderedBroadcasts.get(0);
    if (br.state == BroadcastRecord.WAITING_SERVICES) {
        //廣播已經(jīng)處理完成浪耘,但需要等待已啟動service執(zhí)行完成乱灵。當?shù)却銐驎r間,則處理下一條廣播点待。
        br.curComponent = null;
        br.state = BroadcastRecord.IDLE;
        processNextBroadcast(false);
        return;
    }

    r.receiverTime = now;
    //當前BroadcastRecord的anr次數(shù)執(zhí)行加1操作
    r.anrCount++;

    if (r.nextReceiver <= 0) {
        return;
    }
    ...
    
    Object curReceiver = r.receivers.get(r.nextReceiver-1);
    //查詢App進程
    if (curReceiver instanceof BroadcastFilter) {
        BroadcastFilter bf = (BroadcastFilter)curReceiver;
        if (bf.receiverList.pid != 0
                && bf.receiverList.pid != ActivityManagerService.MY_PID) {
            synchronized (mService.mPidsSelfLocked) {
                app = mService.mPidsSelfLocked.get(
                        bf.receiverList.pid);
            }
        }
    } else {
        app = r.curApp;
    }

    if (app != null) {
        anrMessage = "Broadcast of " + r.intent.toString();
    }

    if (mPendingBroadcast == r) {
        mPendingBroadcast = null;
    }

    //繼續(xù)移動到下一個廣播接收者
    finishReceiverLocked(r, r.resultCode, r.resultData,
            r.resultExtras, r.resultAbort, false);
    scheduleBroadcastsLocked();

    if (anrMessage != null) {
        // BroadcastQueue.java AppNotResponding
        mHandler.post(new AppNotResponding(app, anrMessage));
    }
}



private final class AppNotResponding implements Runnable {
    ...
    public void run() {
        // 進入ANR處理流程
        mService.appNotResponding(mApp, null, null, false, mAnnotation);
    }
}
  • mOrderedBroadcasts已處理完成阔蛉,則不會anr;
  • 在執(zhí)行dexopt弃舒,則不會anr;
  • 系統(tǒng)還沒有進入ready狀態(tài)(mProcessesReady=false)癞埠,則不會anr;
  • 如果當前正在執(zhí)行的receiver沒有超時,則重新設置廣播超時聋呢,不會anr;
ContnentProvider

ContentProvider Timeout是位于”ActivityManager”線程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息時觸發(fā)苗踪。

設置ANR超時

ContentProvider 超時為CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s. 這個跟前面的Service和BroadcastQueue完全不同, 由Provider進程啟動過程相關(guān).當進程創(chuàng)建后悔調(diào)用attachApplicationLocked()進入system_server進程.

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

    //app進程存在正在啟動中的provider,則超時10s后發(fā)送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之后,移除延時的timeout消息

  • AMS.publishContentProviders
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();
               }
               ...
           }
       }
   }    
}
觸發(fā)超時

在system_server中有一個handler線程叫做“ActivityManager”。當?shù)褂嫊r結(jié)束便會向該handler線程發(fā)送一條信息 CONTENT_PROVIDER_PUBLISH_TIMNEOUT_MSG

  • ActivityManagerService.java -MainHandler.handleMessage
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;
            ...
        }
        ...
    }
}

private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {
    
    cleanupAppInLaunchingProvidersLocked(app, true); 
    
    removeProcessLocked(app, false, true, "timeout publishing content providers");
}

boolean cleanupAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {
    boolean restart = false;
    for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) {
        ContentProviderRecord cpr = mLaunchingProviders.get(i);
        if (cpr.launchingApp == app) {
            if (!alwaysBad && !app.bad && cpr.hasConnectionOrHandle()) {
                restart = true;
            } else {
                //移除死亡的provider
                removeDyingProviderLocked(app, cpr, true);
            }
        }
    }
    return restart;
}
  • 對于stable類型的provider(即conn.stableCount > 0),則會殺掉所有跟該provider建立stable連接的非persistent進程.
  • 對于unstable類的provider(即conn.unstableCount > 0),并不會導致client進程被級聯(lián)所殺.

總結(jié)

超時檢測

Service超時檢測機制:

  • 超過一定時間沒有執(zhí)行完相應操作來觸發(fā)移除延時消息削锰,則會觸發(fā)anr;
    BroadcastReceiver超時檢測機制:

  • 有序廣播的總執(zhí)行時間超過 2* receiver個數(shù) * timeout時長通铲,則會觸發(fā)anr;

  • 有序廣播的某一個receiver執(zhí)行過程超過 timeout時長,則會觸發(fā)anr;
    另外:

  • 對于Service, Broadcast, Input發(fā)生ANR之后,最終都會調(diào)用AMS.appNotResponding;

  • 對于provider,在其進程啟動時publish過程可能會出現(xiàn)ANR, 則會直接殺進程以及清理相應信息,而不會彈出ANR的對話框

  • 對于input anr 可通過adb shell dumpsys input來查看手機當前的input狀態(tài), 輸出內(nèi)容分別為EventHub.dump(), InputReader.dump(),InputDispatcher.dump()這3類,另外如果發(fā)生過input ANR,那么也會輸出上一個ANR的狀態(tài).

當ANR出現(xiàn)時器贩,無論是四大組件還是進程等颅夺,都是調(diào)用到AMS.appNotResponding()方法,provider除外

  • AMS.appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {
    ...
    updateCpuStatsNow(); //第一次 更新cpu統(tǒng)計信息
    synchronized (this) {
      //PowerManager.reboot() 會阻塞很長時間蛹稍,因此忽略關(guān)機時的ANR
      if (mShuttingDown) {
          return;
      } else if (app.notResponding) {
          return;
      } else if (app.crashing) {
          return;
      }
      //記錄ANR到EventLog
      EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
              app.processName, app.info.flags, annotation);
              
      // 將當前進程添加到firstPids
      firstPids.add(app.pid);
      int parentPid = app.pid;
      
      //將system_server進程添加到firstPids
      if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
      
      for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
          ProcessRecord r = mLruProcesses.get(i);
          if (r != null && r.thread != null) {
              int pid = r.pid;
              if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
                  if (r.persistent) {
                      firstPids.add(pid); //將persistent進程添加到firstPids
                  } else {
                      lastPids.put(pid, Boolean.TRUE); //其他進程添加到lastPids
                  }
              }
          }
      }
    }
    
    // 記錄ANR輸出到main log
    StringBuilder info = new StringBuilder();
    info.setLength(0);
    info.append("ANR in ").append(app.processName);
    if (activity != null && activity.shortComponentName != null) {
        info.append(" (").append(activity.shortComponentName).append(")");
    }
    info.append("\n");
    info.append("PID: ").append(app.pid).append("\n");
    if (annotation != null) {
        info.append("Reason: ").append(annotation).append("\n");
    }
    if (parent != null && parent != activity) {
        info.append("Parent: ").append(parent.shortComponentName).append("\n");
    }
    
    //創(chuàng)建CPU tracker對象
    final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
    //輸出traces信息
    File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, 
            lastPids, NATIVE_STACKS_OF_INTEREST);
            
    updateCpuStatsNow(); //第二次更新cpu統(tǒng)計信息
    //記錄當前各個進程的CPU使用情況
    synchronized (mProcessCpuTracker) {
        cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);
    }
    //記錄當前CPU負載情況
    info.append(processCpuTracker.printCurrentLoad());
    info.append(cpuInfo);
    //記錄從anr時間開始的Cpu使用情況
    info.append(processCpuTracker.printCurrentState(anrTime));
    //輸出當前ANR的reason吧黄,以及CPU使用率、負載信息
    Slog.e(TAG, info.toString()); 
    
    //將traces文件 和 CPU使用率信息保存到dropbox唆姐,即data/system/dropbox目錄
    addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
            cpuInfo, tracesFile, null);

    synchronized (this) {
        ...
        //后臺ANR的情況, 則直接殺掉
        if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
            app.kill("bg anr", true);
            return;
        }

        //設置app的ANR狀態(tài)拗慨,病查詢錯誤報告receiver
        makeAppNotRespondingLocked(app,
                activity != null ? activity.shortComponentName : null,
                annotation != null ? "ANR " + annotation : "ANR",
                info.toString());

        //重命名trace文件
        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
        if (tracesPath != null && tracesPath.length() != 0) {
            //traceRenameFile = "/data/anr/traces.txt"
            File traceRenameFile = new File(tracesPath);
            String newTracesPath;
            int lpos = tracesPath.lastIndexOf (".");
            if (-1 != lpos)
                // 新的traces文件= /data/anr/traces_進程名_當前日期.txt
                newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processName + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos);
            else
                newTracesPath = tracesPath + "_" + app.processName;

            traceRenameFile.renameTo(new File(newTracesPath));
        }
                
        //彈出ANR對話框
        Message msg = Message.obtain();
        HashMap<String, Object> map = new HashMap<String, Object>();
        msg.what = SHOW_NOT_RESPONDING_MSG;
        msg.obj = map;
        msg.arg1 = aboveSystem ? 1 : 0;
        map.put("app", app);
        if (activity != null) {
            map.put("activity", activity);
        }
        
        //向ui線程發(fā)送,內(nèi)容為SHOW_NOT_RESPONDING_MSG的消息
        mUiHandler.sendMessage(msg);
    }
    
}

ANR發(fā)生后日志和log信息

當ANR時,會按順序依次執(zhí)行:

  • 輸出ANR Reason信息到EventLog. 也就是說ANR觸發(fā)的時間點最接近的就是EventLog中輸出的am_anr信息;
  • 收集并輸出重要進程列表中的各個線程的traces信息赵抢,該方法較耗時;
  • 輸出當前各個進程的CPU使用情況以及CPU負載情況;
  • 將traces文件和 CPU使用情況信息保存到dropbox剧蹂,即data/system/dropbox目錄
  • 根據(jù)進程類型,來決定直接后臺殺掉,還是彈框告知用戶.

ANR輸出重要進程的traces信息,這些重要進程包括:

  • firstPids隊列:第一個是ANR進程烦却,第二個是system_server宠叼,剩余是所有persistent進程;

  • Native隊列:是指/system/bin/目錄的mediaserver,sdcard 以及surfaceflinger進程其爵;

  • lastPids隊列: 是指mLruProcesses中的不屬于firstPids的所有進程车吹。

  • AMS.dumpStackTraces

public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
    //默認為 data/anr/traces.txt
    String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
    if (tracesPath == null || tracesPath.length() == 0) {
        return null;
    }

    File tracesFile = new File(tracesPath);
    try {
        //當clearTraces,則刪除已存在的traces文件
        if (clearTraces && tracesFile.exists()) tracesFile.delete();
        //創(chuàng)建traces文件
        tracesFile.createNewFile();
        FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1);
    } catch (IOException e) {
        return null;
    }
    //輸出trace內(nèi)容
    dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
    return tracesFile;
}
//這里會保證data/anr/traces.txt文件內(nèi)容是全新的方式醋闭,而非追加窄驹。










private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
    FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
        @Override
        public synchronized void onEvent(int event, String path) { notify(); }
    };

    try {
        observer.startWatching();

        //首先,獲取最重要進程的stacks
        if (firstPids != null) {
            try {
                int num = firstPids.size();
                for (int i = 0; i < num; i++) {
                    synchronized (observer) {
                        //向目標進程發(fā)送signal來輸出traces
                        Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
                        observer.wait(200);  //等待直到寫關(guān)閉证逻,或者200ms超時
                    }
                }
            } catch (InterruptedException e) {
                Slog.wtf(TAG, e);
            }
        }

        //下一步乐埠,獲取native進程的stacks
        if (nativeProcs != null) {
            int[] pids = Process.getPidsForCommands(nativeProcs);
            if (pids != null) {
                for (int pid : pids) {
                    //輸出native進程的trace【見小節(jié)4】
                    Debug.dumpNativeBacktraceToFile(pid, tracesPath);
                }
            }
        }

        if (processCpuTracker != null) {
            processCpuTracker.init();
            System.gc();
            processCpuTracker.update();
            synchronized (processCpuTracker) {
                processCpuTracker.wait(500); //等待500ms
            }
            //測量CPU使用情況
            processCpuTracker.update();

            //從lastPids中選取CPU使用率 top 5的進程,輸出這些進程的stacks
            final int N = processCpuTracker.countWorkingStats();
            int numProcs = 0;
            for (int i=0; i<N && numProcs<5; i++) {
                ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
                if (lastPids.indexOfKey(stats.pid) >= 0) {
                    numProcs++;
                    synchronized (observer) {
                        Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
                        observer.wait(200); 
                    }
                }
            }
        }
    } finally {
        observer.stopWatching();
    }
}

dumpStackTraces方法主要輸出

  • 收集firstPids進程的stacks囚企;
    第一個是發(fā)生ANR進程丈咐;
    第二個是system_server;
    mLruProcesses中所有的persistent進程龙宏;
  • 收集Native進程的stacks棵逊;(dumpNativeBacktraceToFile)
    依次是mediaserver,sdcard,surfaceflinger進程;
  • 收集lastPids進程的stacks;银酗;
    依次輸出CPU使用率top 5的進程辆影;

觸發(fā)ANR時系統(tǒng)會輸出關(guān)鍵信息,依次打印各個進程信息和cpu的使用情況黍特,將會比較耗時

  1. 最接近ANR發(fā)生時間的是am_anr信息蛙讥,輸出到EventLog,所以查看ANR的起點應該看EventLog信息
  2. 獲取重要進程trace信息,保存到/data/anr/traces.txt灭衷;(會先刪除老的文件次慢、有的會重新命名trave文件)
  • Java進程的traces;
  • Native進程的traces;
  1. ANR reason以及cpu使用情況信息輸出到main log
  2. 再講cpu使用情況和進程trace文件信息保存到/data/system/dropbox;路徑下

ANR簡單案例分析

首先在頁面中制造一個簡單的ANR

anr_code.jpg

當我們點擊這個按鈕的時候翔曲,就會觸發(fā)ANR迫像,然后在logcat中我們可以看到如下日志


anr_log.png
anr_logcat.png

在traces文件中(位于data/anr)可以看到主線程的線程狀態(tài)為SLEEPING


main_thread_anr.png

這只是一個非常簡單的案例,實際問題的分析會相對復雜的多瞳遍,但是通過分析log日志和trace日志闻妓,搜索一些關(guān)鍵字,來定位日志位置比如要排查的app進程包名傅蹂,主線程名字“main”纷闺,和cpu狀態(tài)等等可以獲取一些有效的信息

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末算凿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子犁功,更是在濱河造成了極大的恐慌氓轰,老刑警劉巖惶洲,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累澡,死亡現(xiàn)場離奇詭異,居然都是意外死亡绊诲,警方通過查閱死者的電腦和手機限嫌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門靴庆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怒医,你說我怎么就攤上這事炉抒。” “怎么了稚叹?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵焰薄,是天一觀的道長。 經(jīng)常有香客問我扒袖,道長塞茅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任季率,我火速辦了婚禮野瘦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘飒泻。我一直安慰自己鞭光,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布蠢络。 她就那樣靜靜地躺著衰猛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刹孔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天娜睛,我揣著相機與錄音髓霞,去河邊找鬼。 笑死畦戒,一個胖子當著我的面吹牛方库,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播障斋,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼纵潦,長吁一口氣:“原來是場噩夢啊……” “哼徐鹤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邀层,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤返敬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寥院,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劲赠,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年秸谢,在試婚紗的時候發(fā)現(xiàn)自己被綠了凛澎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡估蹄,死狀恐怖塑煎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臭蚁,我是刑警寧澤轧叽,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站刊棕,受9級特大地震影響炭晒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜甥角,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一网严、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗤无,春花似錦震束、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嚎卫,卻和暖如春嘉栓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拓诸。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工侵佃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奠支。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓馋辈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親倍谜。 傳聞我的和親對象是個殘疾皇子迈螟,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

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