Android ANR 원리 분석

19131 단어 AndroidANR
카 톤 원리
메 인 스 레 드 에 시간 이 걸 리 면 렉 이 걸 리 고 렉 이 밸브 값 을 초과 하여 ANR 을 촉발 할 수 있 습 니 다.프로 세 스 가 시 작 될 때 Zygote 는 Activity Thread 의 main 방법 을 반사 적 으로 호출 하여 loop 순환 을 시작 합 니 다.ActivityThread(api29)

    public static void main(String[] args) {
        Looper.prepareMainLooper();
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Looper 의 loop 방법:

//           。     
public static void loop() {
        for (;;) {
            // 1、   
            Message msg = queue.next(); // might block
            ...
            // This must be in a local variable, in case a UI event sets the logger
            // 2、       
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            ...
            // 3、      
            msg.target.dispatchMessage(msg);
            ...
            // 4、       
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
        }
}
loop 에 for 순환 이 존재 합 니 다.메 인 스 레 드 는 장시간 운행 할 수 있 습 니 다.메 인 스 레 드 에서 작업 을 수행 할 때 Handler post 하나의 작업 을 통 해 메시지 대기 열 로 이동 할 수 있 습 니 다.loop 순환 으로 msg 를 가 져 와 msg 의 target(Handler)에 맡 길 수 있 습 니 다.
두 곳 이 걸 릴 수 있 습 니 다.
  • 주석 1 queue.next()
  • 주석 3 dispatchMessage 소모 시간
  • Message Queue.next 시간 소모 코드(api 29)
    
        @UnsupportedAppUsage
        Message next() {
            for (;;) {
                // 1、nextPollTimeoutMillis  0   
                nativePollOnce(ptr, nextPollTimeoutMillis);
                // 2、                   ,
                if (msg != null && msg.target == null) {
                        // 3、        ,               ,             
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                 }
                 // 4、      ,      
                 if (msg != null) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // 5、          ,       1,nativePollOnce -1,     
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
            }
        }
    
  • Message Queue 는 링크 데이터 구조 로 Message Queue 머리(첫 번 째 메시지)가 동기 화 장벽 메시지 인지 판단 합 니 다(동기 화 메시지 에 장벽 을 추가 하여 동기 화 메시지 가 처리 되 지 않 고 비동기 메시지 만 처리 합 니 다).
  • 동기 화 장벽 메 시 지 를 만나면 Message Queue 에서 동기 화 메 시 지 를 건 너 뛰 고 안에 있 는 비동기 메시지 만 처리 합 니 다.비동기 메시지 가 없 으 면 주석 5 까지,nextPollTimeoutMillis 는-1 이 며,다음 에 주석 1 의 nativePollOnce 를 순환 호출 하면 막 힙 니 다.
  • looper 가 정상적으로 정 보 를 얻 을 수 있다 면 비동기/동기 화 메시지 에 관 계 없 이 처리 절차 와 마찬가지 로 주석 4 에서 지연 여 부 를 판단 합 니 다.만약 에 nextPoll TimeoutMillis 가 할당 되 었 다 면 다음 에 주석 1 의 nativePoll Once 를 호출 하면 한동안 막 힐 것 입 니 다.delay 메시지 가 아니라면 msg 로 돌아 가 handler 에 처리 합 니 다.
  • next 방법 은 Message Queue 에서 메 시 지 를 계속 가 져 오고 메시지 가 있 으 면 처리 하 며 메시지 가 없 으 면 nativePoll Once 를 호출 하여 차단 합 니 다.바 텀 은 Linux 의 epoll 체제 이 고 Linux IO 다 중 재 활용 입 니 다.
    Linux IO 다 중 재 활용 방안 은 select,poll,epoll 이 있 습 니 다.그 중에서 epoll 성능 이 가장 좋 고 병발 량 을 지원 합 니 다.
  • select:운영 체제 가 제공 하 는 시스템 호출 함수 입 니 다.파일 설명자 의 배열 을 운영 체제 에 보 낼 수 있 습 니 다.운영 체 제 를 옮 겨 다 니 며 어떤 설명자 가 읽 고 쓸 수 있 는 지 확인 하고 처리 할 수 있 는 지 알려 주 십시오.
  • poll:select 와 주요 차이 점 은 select 가 1024 개의 파일 설명자 만 감청 할 수 있 는 제한 을 없 앴 습 니 다.
  • epoll:select 의 세 가지 최적화 점 을 개선 할 수 있 습 니 다.
  • 
    1、              ,          ,           。
    2、                    ,    IO    。
    3、      IO           ,               。
    
    동기 화 장벽 메시지
    Android App 은 동기 화 메시지 장벽,Message Queue(api 29)코드 를 직접 호출 할 수 없습니다.
    
        @TestApi
        public int postSyncBarrier() {
            return postSyncBarrier(SystemClock.uptimeMillis());
        }
    
        private int postSyncBarrier(long when) {
            ...
        }
    
    시스템 의 높 은 우선 순 위 는 동기 화 장벽 메시지 에 사 용 됩 니 다.예 를 들 어 View 가 그 릴 때 ViewRootImpl 의 scheduleTraversals 방법 은 동기 화 장벽 메 시 지 를 삽입 하고 그 려 진 후에 동기 화 장벽 메 시 지 를 제거 합 니 다.ViewRootImpl api29
    
        @UnsupportedAppUsage
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
        
        void unscheduleTraversals() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            }
        }
    
    View 의 그리 기 과정 이 메 인 스 레 드 의 다른 작업 에 영향 을 받 지 않도록 View 는 그리 기 전에 Message Queue 에 동기 화 장벽 메 시 지 를 삽입 한 다음 에 Vsync 신호 감청 을 등록 합 니 다.Choreographer$FrameDisplayEventReceiver 감청 은 vsync 신호 리 셋 을 받 습 니 다.
    
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
                implements Runnable {
                @Override
                public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
                    Message msg = Message.obtain(mHandler, this);
                    // 1、      
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
                  }
                  
                          @Override
                public void run() {
                    // 2、doFrame    
                    doFrame(mTimestampNanos, mFrame);
                  }
                }
    
    Vsync 신호 리 셋 을 받 았 습 니 다.주석 1 은 메 인 스 레 드 Message Queue post 에 비동기 메 시 지 를 보 내 주석 2 의 doFrame 이 우선 실 행 될 수 있 도록 합 니 다.
    doFrame 이 야 말로 View 가 진정 으로 그리 기 시작 한 곳 입 니 다.ViewRootIml 의 doTraversal,permTraversals 를 호출 하고 permTraversals 에 서 는 View 의 onMeasure,onLayout,onDraw 를 호출 합 니 다.
    app 에서 동기 화 장벽 메 시 지 를 보 낼 수 없 지만 비동기 메 시 지 를 사용 하 는 것 은 허용 합 니 다.
    비동기 메시지 SDK 에 서 는 App 이 Message Queue,Message 클래스 에 비동기 메 시 지 를 게시 할 수 없 도록 제한 합 니 다.
    
        @UnsupportedAppUsage
        /*package*/ int flags;
    
    비동기 메 시 지 를 신중하게 사용 하고 잘못 사용 하면 메 인 스 레 드 가사 가 발생 할 수 있 습 니 다.
    Handler#dispatchMessage
    
        /**
         * Handle system messages here.
         */
        public void dispatchMessage(@NonNull Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
  • Handler#post(Runnable r)
  • 구조 방법 전 CallBack
  • Handler handlerMessage 재 작성 방법
  • 응용 카드,일반적으로 Handler 처리 메시지 가 너무 오래 걸 려 서 발생 합 니 다(방법 자체,알고리즘 효율,cpu 선점,메모리 부족,IPC 시간 초과 등)
    정지 감시
    카드 감시 방안 1 Looper\#loop
    
    //           。     
    public static void loop() {
            for (;;) {
                // 1、   
                Message msg = queue.next(); // might block
                ...
                // This must be in a local variable, in case a UI event sets the logger
                // 2、       
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
                ...
                // 3、      
                msg.target.dispatchMessage(msg);
                ...
                // 4、       
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
            }
    }
    
    설명 2 와 4 의 logging.println 은 api 가 인 터 페 이 스 를 제공 합 니 다.Handler 를 감청 하 는 데 시간 이 걸 립 니 다.Looper.getMainLooper().setMessageLogging(printer)을 통 해 메 시 지 를 받 는 전후 시간 입 니 다.카드 가 들 리 면 dispatchMessage 호출 이 끝 났 고 스 택 에는 카드 코드 가 포함 되 어 있 지 않 습 니 다.
    정기 적 으로 메 인 스 택 을 가 져 옵 니 다.시간 은 key 이 고 스 택 정 보 는 value 입 니 다.map 에 저장 하면 카드 가 발생 합 니 다.카드 시간 내 스 택 을 꺼 내 면 됩 니 다.오프라인 으로 사용 하기 좋 습 니 다.
  • logging.println 에 문자열 연결 이 존재 하고 자주 호출 되 며 대량의 대상 을 만 들 고 메모리 디 더 링 을 합 니 다.
  • 백 스테이지 에서 메 인 스 택 을 자주 가 져 오고 성능 에 영향 을 주 며 메 인 스 택 을 가 져 와 메 인 스 레 드 의 운행 을 중단 합 니 다.
  • 카드 모니터링 방안 2
    온라인 카드 모니터링 에는 바이트 코드 파일 삽입 기술 이 필요 하 다.
    Gradle Plugin+ASM 을 통 해 컴 파일 기간 은 모든 방법 시작 과 끝 위치 에 각각 한 줄 의 코드 를 삽입 하여 시간 을 집계 합 니 다.예 를 들 어 위 챗 매트릭스 가 사용 하 는 카드 톤 모니터링 방안.질문 주의:
  • 피 하 는 방법 수 폭 증:독립 ID 를 매개 변수 로 분배
  • 여과 간단 함수:블랙 리스트 를 추가 하여 불필요 한 함수 통 계 를 낮 춥 니 다.
  • 위 챗 Matrix 는 대량의 최적화 를 하고 가방 의 부 피 는 1%~2%증가 하 며 프레임 율 은 2 프레임 이내 로 떨 어 지고 그 레이스 케 일 은 사용 합 니 다.
    ANR 원리
  • Service Timeout:프론트 서비스 20s 내 미 실행,백 스테이지 서 비 스 는 10s
  • BroadcastQueue Timeout:프론트 방송 10s 내 실행 완료,백 스테이지 60s
  • ContentProvider Timeout:publish 시간 초과 10s
  • Input Dispatching Timeout:입력 이벤트 가 5s 이상 배포 되 며 버튼 과 터치 이 벤트 를 포함 합 니 다.
  • ActivityManagerService api29
    
        // How long we allow a receiver to run before giving up on it.
        static final int BROADCAST_FG_TIMEOUT = 10*1000;
        static final int BROADCAST_BG_TIMEOUT = 60*1000;
    
    ANR 트리거 프로 세 스
    폭탄 을 묻다
    배경 sevice 호출:Context.startService-->AMS.startService-->ActiveService.startService-->ActiveService.realStartServiceLocked
    
        private final void realStartServiceLocked(ServiceRecord r,
                ProcessRecord app, boolean execInFg) throws RemoteException {
                    // 1、  delay  (SERVICE_TIMEOUT_MSG)
                    bumpServiceExecutingLocked(r, execInFg, "create");
                    try {
                        //  2、  AMS    
                        app.thread.scheduleCreateService(r, r.serviceInfo,
                        mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                        app.getReportedProcState());
                    }
                }
    
    설명 1 내부 호출 scheduleServiceTimeoutLocked
    
        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;
            //   delay  ,     20s,     200s
            mAm.mHandler.sendMessageDelayed(msg,
                    proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
        }
    
    설명 2 는 AMS 가 서 비 스 를 시작 하기 전에 설명 1 은 handler 지연 메 시 지 를 보 내 고 20s 내(프론트 서비스)가 처리 되 지 않 으 면 ActiveServices\#serviceTimeout 이 호출 됩 니 다.
    폭탄 을 해체 하 다
    서 비 스 를 시작 하려 면 먼저 AMS 관 리 를 거 친 다음 에 AMS 가 서 비 스 를 실행 하 는 생명 주 기 를 알려 주 고 Activity Thread 의 handlerCreateService 방법 이 호출 되 었 습 니 다.
    
        @UnsupportedAppUsage
        private void handleCreateService(CreateServiceData data) {
            try {
                Application app = packageInfo.makeApplication(false, mInstrumentation);
                service.attach(context, this, data.info.name, data.token, app,
                        ActivityManager.getService());
                // 1、service onCreate  
                service.onCreate();
                mServices.put(data.token, service);
                try {
                    // 2、   
                    ActivityManager.getService().serviceDoneExecuting(
                            data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }
    
    주석 1,Service 의 onCreate 방법 은 주석 2 를 호출 하고 AMS 의 serviceDoneExecuting 방법 을 호출 합 니 다.최종 적 으로 ActiveServices.serviceDoneExecutingLocked 를 호출 합 니 다.
    
        private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
                boolean finishing) {
                    //  delay   
                    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
           
           }
    
    onCreate 호출 후 delay 메 시 지 를 제거 하고 폭탄 을 철거 합 니 다.
    폭탄 을 터 뜨리 고 Service 의 onCreate 가 10s 를 초과 한다 고 가정 하면 폭탄 이 터 집 니 다.즉,ActiveServices\#serviceTimeout 방법 이 호출 됩 니 다.api29
    
    void serviceTimeout(ProcessRecord proc) {
            if (anrMessage != null) {
                proc.appNotResponding(null, null, null, null, false, anrMessage);
            }
    }
    
    모든 ANR,최종 적 으로 ProcessRecord 를 호출 하 는 apNotResponsing 방법 을 가 져 옵 니 다.api29
    
    void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
                String parentShortComponentName, WindowProcessController parentProcess,
                boolean aboveSystem, String annotation) {
            // 1、  event log
            // Log the ANR to the event log.
            EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
                        annotation);
            // 2、     log、anr、cpu ,  StringBuilder 。
            // Log the ANR to the main log.
            StringBuilder info = new StringBuilder();
            info.setLength(0);
            info.append("ANR in ").append(processName);
            if (activityShortComponentName != null) {
                info.append(" (").append(activityShortComponentName).append(")");
            }
            info.append("
    "); info.append("PID: ").append(pid).append("
    "); if (annotation != null) { info.append("Reason: ").append(annotation).append("
    "); } if (parentShortComponentName != null && parentShortComponentName.equals(activityShortComponentName)) { info.append("Parent: ").append(parentShortComponentName).append("
    "); } ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); // 3、dump , java native , // For background ANRs, don't pass the ProcessCpuTracker to // avoid spending 1/2 second collecting stats to rank lastPids. File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, (isSilentAnr()) ? null : processCpuTracker, (isSilentAnr()) ? null : lastPids, nativePids); String cpuInfo = null; // 4、 ANR Slog.e(TAG, info.toString()); if (tracesFile == null) { // 5、 tracesFile, SIGNAL_QUIT // There is no trace file, so dump (only) the alleged culprit's threads to the log Process.sendSignal(pid, Process.SIGNAL_QUIT); } // 6、 drapbox mService.addErrorToDropBox("anr", this, processName, activityShortComponentName, parentShortComponentName, parentPr, annotation, cpuInfo, tracesFile, null); synchronized (mService) { // 7、 ANR, if (isSilentAnr() && !isDebugging()) { kill("bg anr", true); return; } // 8、 // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLocked(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR", info.toString()); // 9、 ANR dialog, handleShowAnrUi Message msg = Message.obtain(); msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem); mService.mUiHandler.sendMessage(msg); } }
  • 이벤트 로그 쓰기
  • main log 쓰기
  • tracesFile 생 성
  • ANR logcat 출력(콘 솔 에서 볼 수 있 음)
  • tracesFile 을 가 져 오지 않 으 면 SIGNAL 을 보 냅 니 다.QUIT 신호,수집 스 레 드 스 택 정보 프로 세 스 를 촉발 하여 traceFile
  • 을 기록 합 니 다.
  • drapbox 로 출력
  • 백 스테이지 ANR,프로 세 스 직접 죽 이기
  • 오류 보고
  • ANR dialog 를 꺼 내 AppErrors\#handle ShowAnrUi 방법 을 호출 합 니 다.
  • 
    ANR    ,   --》      
      Service,onCreate         Handler  10s   ,Service onCreate     ,         。
      Service onCreate      10s,           ,  ANR,  cpu、    , ANR dialog
    
    시스템 의 data/anr/trace.txt 파일 을 캡 처 하지만,고 버 전 시스템 은 루트 권한 이 있어 야 이 디 렉 터 리 를 읽 을 수 있 습 니 다.
    ANRWatchDog github.com/SalomonBrys…
    ANR 소스 라 이브 러 리 자동 검색
    이상 은 안 드 로 이 드 ANR 원리 분석의 상세 한 내용 입 니 다.더 많은 안 드 로 이 드 ANR 원리 에 관 한 자 료 는 우리 의 다른 관련 글 을 주목 하 세 요!

    좋은 웹페이지 즐겨찾기