[Android] BroadcastReceiver 의 am_broadcast_discard_app 이슈



Android App에서 종종 ANR이 발생되어 원인분석을 했다.


증상
Receiver 에서 Broadcast 를 수신받지 못한 후 ANR 발생



adb logcat -v threadtime -b events

06-17 12:07:05.067  3800  3826 I am_broadcast_discard_app: [0,536238391,android.intent.action.PACKAGE_ADDED,7,ResolveInfo {45fc40d com.???.???/.packages.PackageReceiver m=0x208000}]
06-17 12:07:05.127  3800  3826 I am_proc_start: [0,28839,1000,com.!!!.!!!,broadcast,com.!!!.!!!/.receiver.AppInstallReceiver]
06-17 12:07:05.167  3800  3826 I am_anr  : [0,0,com.???.???,8961605,Broadcast of Intent { act=android.intent.action.PACKAGE_ADDED dat=package:com.@@@.@@@ flg=0x4000010 (has extras) }]






/data/anr/traces.txt (ANR 이 발생되면 디바이스에 해당 파일이 생성된다.)

...

"ActivityManager" prio=5 tid=17 TimedWaiting
  | group="main" sCount=1 dsCount=0 obj=0x131c3ba0 self=0x7fa70ba800
  | sysTid=3826 nice=-2 cgrp=default sched=0/0 handle=0x7f9b52f900
  | state=S schedstat=( 0 0 0 ) utm=779 stm=841 core=3 HZ=100
  | stack=0x7f906e8000-0x7f906ea000 stackSize=1036KB
  | held mutexes=
  at java.lang.Object.wait!(Native method)
  - waiting on <0x0d0c5110> (a com.android.server.am.ActivityManagerService$6)
  at java.lang.Object.wait(Object.java:422)
  at com.android.server.am.ActivityManagerService.dumpStackTraces(ActivityManagerService.java:7145)
  - locked <0x0d0c5110> (a com.android.server.am.ActivityManagerService$6)
  at com.android.server.am.ActivityManagerService.dumpStackTraces(ActivityManagerService.java:7122)
  at com.android.server.am.ActivityManagerService.appNotResponding(ActivityManagerService.java:7373)
  at com.android.server.am.BroadcastQueue$AppNotResponding.run(BroadcastQueue.java:217)
  at android.os.Handler.handleCallback(Handler.java:739)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:145)
  at android.os.HandlerThread.run(HandlerThread.java:61)
  at com.android.server.ServiceThread.run(ServiceThread.java:46)

...





https://android.googlesource.com/platform/frameworks/base/+/483f3b06ea84440a082e21b68ec2c2e54046f5a6/services/java/com/android/server/am/BroadcastQueue.java

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) {
            // Delay timeouts until dexopt finishes.
            mService.mDidDexOpt = false;
            long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
            setBroadcastTimeoutLocked(timeoutTime);
            return;
        }
        if (!mService.mProcessesReady) {
            // Only process broadcast timeouts if the system is ready. That way
            // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
            // to do heavy lifting for system up.
            return;
        }
        long timeoutTime = r.receiverTime + mTimeoutPeriod;
        if (timeoutTime > now) {
            // We can observe premature timeouts because we do not cancel and reset the
            // broadcast timeout message after each receiver finishes.  Instead, we set up
            // an initial timeout then kick it down the road a little further as needed
            // when it expires.
            if (DEBUG_BROADCAST) Slog.v(TAG,
                    "Premature timeout ["
                    + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
                    + timeoutTime);
            setBroadcastTimeoutLocked(timeoutTime);
            return;
        }
    }
    Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver
            + ", started " + (now - r.receiverTime) + "ms ago");
    r.receiverTime = now;
    r.anrCount++;
    // Current receiver has passed its expiration date.
    if (r.nextReceiver <= 0) {
        Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");
        return;
    }
    ProcessRecord app = null;
    String anrMessage = null;
    Object curReceiver = r.receivers.get(r.nextReceiver-1);
    Slog.w(TAG, "Receiver during timeout: " + curReceiver);
    logBroadcastReceiverDiscardLocked(r);
    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;
    }
    // Move on to the next receiver.
    finishReceiverLocked(r, r.resultCode, r.resultData,
            r.resultExtras, r.resultAbort, true);
    scheduleBroadcastsLocked();
    if (anrMessage != null) {
        // Post the ANR to the handler since we do not want to process ANRs while
        // potentially holding our lock.
        mHandler.post(new AppNotResponding(app, anrMessage));
    }
}



am_broadcast_discard_app 이라는 로그는 Broadcast를 정상적으로 수신 할 수 없는 경우에 발생이 된다고 한다. (본문 하단의 추가내용 확인)
(http://stackoverflow.com/questions/28675002/broadcast-receiver-being-discarded)



traces.txt 파일을 통해 ANR 원인에 대한 스택을 보면 BroadcastQueue$AppNotResponding Class 에서 ANR 관련 tracesFile 을 생성한다.
최초 BroadcastQueue Class의 broadcastTimeoutLocked Method 에서 AppNotResponding 을 수행시키는데 Broadcast 가 발생했음에도 시간내에 처리되지 않으면 ANR을 발생시키는 것으로 보인다.




위의 이슈로 인해 제조사에 문의를 했다.
제조사에서 온 답변은 다음과 같다.
(메일의 내용을 그대로 담을 수 없어 내용을 조금 수정했다.)

======================================
해당 App에서 App 다운로드시 백그라운드에서 다른 이름의 여러 패키지들이 연속적으로 다운로드 되어지면서, android.intent.action.PACKAGE_ADDED 가 너무 많이 전달됨에 따라 Broadcast처리에 로드가 걸려 안드로이드에서 발생시킨 ANR로 보입니다.

최근 프레임워크에서 관련하여 PACKAGE _ADDED Broadcast가 무분별하게 전달되지 않도록 하는 수정이 있었습니다. 이 수정이 반영된 이후의 바이너리에선 아직 재현된 이슈가 없습니다.
======================================






=============================
2015.07.12 내용 추가

2주 뒤에 위와 같은 이슈가 발생이 되어 android.intent.action.PACKAGE_ADDED 가 짧은 시간에 많이 발생이 되는경우 ANR이 발생되는 증상이 발생 되었다.

onReceive 메소드 내부의 로직을 확인결과 시간이 오래 걸릴 수 있는 로직이 발견되었다.
짧은 시간에 broadcast가 많이 발생 시 문제가 될 수 있기에 Thread 처리를 하여 수정을 했다.

이후 해당 문제로 ANR 발생되는 일이 아직 발생하지 않았다.


댓글