赞
踩
一直以来只知道Android上ANR是因为主线程卡住了,一般5秒就会出现ANR的dialog,一直也没有去深究这个来源。最近在写一个测试程序的时候发现,在主线程上调用了一个阻塞的binder 调用,阻塞了很长时间居然没有ANR,很奇怪,所以终于下决心好好研究下这个问题。
要搞清楚这个问题,所掌握的线索就只有ANR的时候所弹出的dialog,那么就从dialog上的字符串开始,还好这个“not responding”还是比较独特的。很容易就找到了AppNotResponding这个dilaog类,他是BaseErrorDialog类的子类,果然不出所料他是一个WindowManager.LayoutParams.TYPE_SYSTEM_ALERT的类,而且设置了很多的flag,保证它能显示在APP的前面,不去深究这个了,还是看看是谁让它显示的呢?
早ActivityManagerService里找到一段代码:
- case SHOW_NOT_RESPONDING_MSG: {
- synchronized (ActivityManagerService.this) {
- HashMap data = (HashMap) msg.obj;
- ProcessRecord proc = (ProcessRecord)data.get("app");
- if (proc != null && proc.anrDialog != null) {
- Slog.e(TAG, "App already has anr dialog: " + proc);
- return;
- }
-
- Intent intent = new Intent("android.intent.action.ANR");
- if (!mProcessesReady) {
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- }
- broadcastIntentLocked(null, null, intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
-
- if (mShowDialogs) {
- Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
- mContext, proc, (ActivityRecord)data.get("activity"),
- msg.arg1 != 0);
- d.show();
- proc.anrDialog = d;
- } else {
- // Just kill the app if there is no dialog to be shown.
- killAppAtUsersRequest(proc, null);
- }
- }
-
- ensureBootCompleted();
- } break;
可以发现有人给ActivityManagerService发了这个
SHOW_NOT_RESPONDING_MSG
的消息,才导致了这个可恶的dialog给show了出来,是谁,站出来,我保证不打死它。
在同一个文件里发现了
- final void appNotResponding(ProcessRecord app, ActivityRecord activity,
- ActivityRecord parent, boolean aboveSystem, final String annotation) {
- ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
- SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
-
- ......
-
- // Bring up the infamous App Not Responding dialog
- Message msg = Message.obtain();
- HashMap map = new HashMap();
- msg.what = <span style="color: rgb(255, 102, 102);">SHOW_NOT_RESPONDING_MSG</span>;
- msg.obj = map;
- msg.arg1 = aboveSystem ? 1 : 0;
- map.put("app", app);
- if (activity != null) {
- map.put("activity", activity);
- }
-
- mHandler.sendMessage(msg);
- }
- }
再继续往前追溯真凶 ,原来是InputMonitor搞得鬼。而InputMonitor是WindowManager放到InputManager中的回调。它有一个notifyANR的方法才是这个事件的源头。
- // Native callback.
- private long notifyANR(InputApplicationHandle inputApplicationHandle,
- InputWindowHandle inputWindowHandle) {
- return mWindowManagerCallbacks.notifyANR(inputApplicationHandle, inputWindowHandle);
- }
这是InputManagerService里面的代码,Native callback,看到这个注释会崩溃,有木有。
等一等,ANR和InputManagerService 扯上了,什么情况。看来不追到native的代码是不行了。
- void InputDispatcher::doNotifyANRLockedInterruptible(
- CommandEntry* commandEntry) {
- mLock.unlock();
-
- nsecs_t newTimeout = mPolicy-><span style="color:#ff6666;">notifyANR</span>(
- commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle,
- commandEntry->reason);
-
- mLock.lock();
-
- resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
- commandEntry->inputWindowHandle != NULL
- ? commandEntry->inputWindowHandle->getInputChannel() : NULL);
- }
Native的InputDispatcher会报告这个情况。 在InputDispatcher::handleTargetsNotReadyLocked方法中有这么段代码:
- if (<span style="color:#ff6666;">currentTime >= mInputTargetWaitTimeoutTime</span>) {
- onANRLocked(currentTime, applicationHandle, windowHandle,
- ......
- }
而
mInputTargetWaitTimeoutTime = currentTime + timeout;
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
DEFAULT_INPUT_DISPATCHING_TIMEOUT 就是5秒。ok就是这个了。
再进一步查看是谁调用这个doNotifyANRLockedInterruptible, 最终可以发现是在:
- bool InputDispatcherThread::threadLoop() {
- mDispatcher->dispatchOnce();
- return true;
- }
这个loop中,这样就可以理解了,原来是在分发input事件 的时候超时了,才会出现这个一连串的反映,最终导致ANR 的dialog给show了出来。
再度感叹,好复杂。不过借此倒可以学习到InputManager - WindowManger -ActivityManager 三者的一定的概念。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。