ANR

ANR原理分析

什么是ANR

ANR(Application Not Responding)就是應(yīng)用在規(guī)定的時(shí)間內(nèi)沒有響應(yīng)用戶輸入或者系統(tǒng)服務(wù)熙暴。

ANR發(fā)生場景

這里以廣播超時(shí)和輸入事件為例講述ANR發(fā)生場景,其余組件如Activity慌盯,Service,ContentProvider實(shí)現(xiàn)原理非常類似.

1. BroadcastReceiver 超時(shí)

在分析Broadcast ANR之前我們先簡單了解下Broadcast掂器。
Broadcast一般分為兩類:

- Normal broadcasts (sent with Context.sendBroadcast) are completely asynchronous. All receivers of the broadcast are run in an undefined order,often at the same time. This is more efficient, but means that receivers cannot use the result or abort APIs included here.

- Ordered broadcasts (sent with Context.sendOrderedBroadcast) are delivered to one receiver at a time. As each receiver executes in turn, it can propagate a result to the next receiver, or it can completely abort the broadcast so that it won't be passed to other receivers. The order receivers run in can be controlled with the android:priority attribute of the matching intent-filter; receivers with the same priority will be run in an arbitrary order.

BroadcastReceiver有兩種注冊方式:

You can either dynamically register an instance of this class with Context.registerReceiver() or statically publish an implementation through the receiver tag in your AndroidManifest.xml.

下面分析Broadcast ANR的流程亚皂,如圖所示:


![input.png](https://upload-images.jianshu.io/upload_images/11087999-e755aebafe88da10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果AMS將Broadcast發(fā)送給廣播接收機(jī)后,在規(guī)定的時(shí)間內(nèi)沒有收到廣播接收機(jī)
發(fā)送的finishReceiver的消息国瓮,就會(huì)觸發(fā)BroadcastTimeout ANR灭必。下面從broadcastIntentLocked開始分析。

   final int broadcastIntentLocked(ProcessRecord callerApp,
            String callerPackage, Intent intent, String resolvedType,
            IIntentReceiver resultTo, int resultCode, String resultData,
            Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
            boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
        ...
        // Figure out who all will receive this broadcast.
        List receivers = null;
        List<BroadcastFilter> registeredReceivers = null;
        // Need to resolve the intent to interested receivers...
        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                 == 0) {
            // 收集靜態(tài)注冊的廣播接收機(jī)
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
        }
        if (intent.getComponent() == null) {
            if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
                // Query one target user at a time, excluding shell-restricted users
                for (int i = 0; i < users.length; i++) {
                    if (mUserController.hasUserRestriction(
                            UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
                        continue;
                    }
                    List<BroadcastFilter> registeredReceiversForUser =
                            mReceiverResolver.queryIntent(intent,
                                    resolvedType, false, users[i]);
                    if (registeredReceivers == null) {
                        registeredReceivers = registeredReceiversForUser;
                    } else if (registeredReceiversForUser != null) {
                        registeredReceivers.addAll(registeredReceiversForUser);
                    }
                }
            } else {
                // 查找動(dòng)態(tài)注冊的廣播接收機(jī)
                registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false, userId);
            }
        }
        ...
        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
        if (!ordered && NR > 0) {
            // 發(fā)送普通廣播到動(dòng)態(tài)注冊的廣播接收機(jī)
            // If we are not serializing this broadcast, then send the
            // registered receivers separately so they don't wait for the
            // components to be launched.
            // 根據(jù)廣播類型決定發(fā)送廣播的隊(duì)列乃摹,前臺廣播由前臺廣播對表處理禁漓;
            // 后臺廣播由后臺廣播隊(duì)列處理
            final BroadcastQueue queue = broadcastQueueForIntent(intent);
            // 創(chuàng)建廣播記錄
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, resolvedType, requiredPermissions,
                    appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData,
                    resultExtras, ordered, sticky, false, userId);
            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
            final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
            if (!replaced) {
                // 將(前臺\后臺)普通廣播放入(前臺\后臺)并行廣播列表中
                queue.enqueueParallelBroadcastLocked(r);
                // 處理(前臺\后臺)并行廣播列表中的廣播
                queue.scheduleBroadcastsLocked();
            }
            registeredReceivers = null;
            NR = 0;
        }

        // Merge into one list.
        // 動(dòng)態(tài)注冊的廣播接收機(jī)、靜態(tài)注冊的廣播接收機(jī)按優(yōu)先級排序(高->低)孵睬,
        // 存放到receivers中
        int ir = 0;
        if (receivers != null) {
            ...
            int NT = receivers != null ? receivers.size() : 0;
            int it = 0;
            ResolveInfo curt = null;
            BroadcastFilter curr = null;
            while (it < NT && ir < NR) {
                if (curt == null) {
                    curt = (ResolveInfo)receivers.get(it);
                }
                if (curr == null) {
                    curr = registeredReceivers.get(ir);
                }
                if (curr.getPriority() >= curt.priority) {
                    // Insert this broadcast record into the final list.
                    receivers.add(it, curr);
                    ir++;
                    curr = null;
                    it++;
                    NT++;
                } else {
                        // Skip to the next ResolveInfo in the final list.
                    it++;
                    curt = null;
                }
            }
        }
        while (ir < NR) {
            if (receivers == null) {
                receivers = new ArrayList();
            }
            receivers.add(registeredReceivers.get(ir));
            ir++;
        }

        if ((receivers != null && receivers.size() > 0)
                || resultTo != null) {
            // 根據(jù)廣播類型決定發(fā)送廣播的隊(duì)列播歼,前臺廣播由前臺廣播對表處理;
            // 后臺廣播由后臺廣播隊(duì)列處理
            BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, resolvedType,
                    requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
                    resultData, resultExtras, ordered, sticky, false, userId);

            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r
                    + ": prev had " + queue.mOrderedBroadcasts.size());
            if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
                    "Enqueueing broadcast " + r.intent.getAction());

            boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
            if (!replaced) {
                // 將(前臺\后臺)普通廣播放入(前臺\后臺)有序廣播列表中
                    queue.enqueueOrderedBroadcastLocked(r);
                // 處理(前臺\后臺)有序廣播列表中的廣播
                queue.scheduleBroadcastsLocked();
            }
        }
        ...
    }

broadcastIntentLocked在進(jìn)行一系列的檢查以及特殊情況的處理后掰读,按廣播的類型以及相應(yīng)的廣播接收機(jī)的類型進(jìn)行分發(fā)秘狞。

下面分析分發(fā)函數(shù)scheduleBroadcastsLocked

android/frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java

    public void scheduleBroadcastsLocked() {
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
                + mQueueName + "]: current="
                + mBroadcastsScheduled);

        if (mBroadcastsScheduled) {
            return;
        }
        // 發(fā)送處理廣播的Message
        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
        mBroadcastsScheduled = true;
    }

scheduleBroadcastsLocked只是簡單的發(fā)送BROADCAST_INTENT_MSG消息叭莫,該消息的處理函數(shù)調(diào)用processNextBroadcast進(jìn)行分發(fā)。

    final void processNextBroadcast(boolean fromMsg) {
        synchronized(mService) {
            BroadcastRecord r;

            ...
            mService.updateCpuStats();
            ...
            // First, deliver any non-serialized broadcasts right away.
            while (mParallelBroadcasts.size() > 0) {
                r = mParallelBroadcasts.remove(0);
                r.dispatchTime = SystemClock.uptimeMillis();
                r.dispatchClockTime = System.currentTimeMillis();
                final int N = r.receivers.size();
                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
                        + mQueueName + "] " + r);
                for (int i=0; i<N; i++) {
                    Object target = r.receivers.get(i);
                    if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                            "Delivering non-ordered on [" + mQueueName + "] to registered "
                            + target + ": " + r);
                    //將廣播同時(shí)發(fā)送給Parallel列表中的廣播接收機(jī)
                    deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
                }
                addBroadcastToHistoryLocked(r);
                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
                        + mQueueName + "] " + r);
            }
            ...
            do {
                if (mOrderedBroadcasts.size() == 0) {
                    // No more broadcasts pending, so all done!
                    mService.scheduleAppGcsLocked();
                    if (looped) {
                        // If we had finished the last ordered broadcast, then
                        // make sure all processes have correct oom and sched
                        // adjustments.
                        mService.updateOomAdjLocked();
                    }
                    return;
                }
                // 將廣播按照優(yōu)先級一個(gè)一個(gè)的分發(fā)給Ordered列表中的廣播接收機(jī)
                r = mOrderedBroadcasts.get(0);
                    boolean forceReceive = false;
                ...
                int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
                if (mService.mProcessesReady && r.dispatchTime > 0) {
                    // 1) 廣播發(fā)送始于SystemReady之前烁试,結(jié)束于SystemReady之后的超時(shí)檢測
                    // 由于SystemReady之前的廣播發(fā)送可能很慢雇初,而且不檢測,所以超時(shí)時(shí)間為
                    // 2 * mTimeoutPeriod * numReceivers
                    // 2) 廣播發(fā)送過程中有dex2oat發(fā)生减响。
                    long now = SystemClock.uptimeMillis();
                    if ((numReceivers > 0) &&
                            (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                        ...
                        broadcastTimeoutLocked(false); // forcibly finish this broadcast
                        forceReceive = true;
                        r.state = BroadcastRecord.IDLE;
                    }
                }
                ...
                if (r.receivers == null || r.nextReceiver >= numReceivers
                        || r.resultAbort || forceReceive) {
                    // No more receivers for this broadcast!  Send the final
                    // result if requested...
                    if (r.resultTo != null) {
                        // 廣播發(fā)送完成靖诗,如果發(fā)送方需要結(jié)果,將結(jié)果反饋給發(fā)送方支示。
                        try {
                            if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
                                    "Finishing broadcast [" + mQueueName + "] "
                                    + r.intent.getAction() + " app=" + r.callerApp);
                            performReceiveLocked(r.callerApp, r.resultTo,
                                new Intent(r.intent), r.resultCode,
                                r.resultData, r.resultExtras, false, false, r.userId);
                            // Set this to null so that the reference
                                // (local and remote) isn't kept in the mBroadcastHistory.
                            r.resultTo = null;
                        } catch (RemoteException e) {
                            r.resultTo = null;
                            Slog.w(TAG, "Failure ["
                                    + mQueueName + "] sending broadcast result of "
                                    + r.intent, e);

                        }
                    }

                    if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
                    // 一個(gè)廣播的所有接收機(jī)發(fā)送完成呻畸,取消超時(shí)消息設(shè)置。
                    cancelBroadcastTimeoutLocked();

                    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
                            "Finished with ordered broadcast " + r);

                    // ... and on to the next...
                    addBroadcastToHistoryLocked(r);
                    if (r.intent.getComponent() == null && r.intent.getPackage() == null
                            && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
                        // This was an implicit broadcast... let's record it for posterity.
                        mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
                                r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
                    }
                    // 從Ordered隊(duì)列中移除發(fā)送完成的廣播
                    mOrderedBroadcasts.remove(0);
                    r = null;
                    looped = true;
                    continue;
                }
            } while (r == null);

            // Get the next receiver...
            // 獲取廣播的下一個(gè)接收者(可能有多個(gè))發(fā)送
            int recIdx = r.nextReceiver++;
    
            // Keep track of when this receiver started, and make sure there
            // is a timeout message pending to kill it if need be.
            r.receiverTime = SystemClock.uptimeMillis();
            if (recIdx == 0) {
                // 廣播多個(gè)接收者中的第一個(gè)悼院,記錄分發(fā)時(shí)間
                r.dispatchTime = r.receiverTime;
                r.dispatchClockTime = System.currentTimeMillis();
                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
                        + mQueueName + "] " + r);
            }
            if (! mPendingBroadcastTimeoutMessage) {
                long timeoutTime = r.receiverTime + mTimeoutPeriod;
                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                        "Submitting BROADCAST_TIMEOUT_MSG ["
                        + mQueueName + "] for " + r + " at " + timeoutTime);
                // 如果沒有設(shè)定廣播發(fā)送超時(shí)時(shí)間伤为,在這里設(shè)定
                setBroadcastTimeoutLocked(timeoutTime);
            }
            ...
            final Object nextReceiver = r.receivers.get(recIdx);

            if (nextReceiver instanceof BroadcastFilter) {
                // Simple case: this is a registered receiver who gets
                // a direct call.
                BroadcastFilter filter = (BroadcastFilter)nextReceiver;
                if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                        "Delivering ordered ["
                        + mQueueName + "] to registered "
                        + filter + ": " + r);
                // 如果是動(dòng)態(tài)注冊的廣播接收機(jī),直接發(fā)送
                deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
                // 我的理解r.ordered == true ???
                if (r.receiver == null || !r.ordered) {
                    // The receiver has already finished, so schedule to
                    // process the next one.
                    if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
                            + mQueueName + "]: ordered="
                            + r.ordered + " receiver=" + r.receiver);
                    r.state = BroadcastRecord.IDLE;
                    scheduleBroadcastsLocked();
                } else {
                        if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
                        scheduleTempWhitelistLocked(filter.owningUid,
                                brOptions.getTemporaryAppWhitelistDuration(), r);
                    }
                }
                return;
            }
            ...
            // Is this receiver's application already running?
            if (app != null && app.thread != null) {
                // 廣播接收機(jī)Host進(jìn)程已經(jīng)運(yùn)行据途,發(fā)送廣播
                try {
                    app.addPackage(info.activityInfo.packageName,
                            info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
                    // 最終通過Binder IPC運(yùn)行廣播接收機(jī)        
                    processCurBroadcastLocked(r, app);
                    return;
                }
            }
            ...
            // 創(chuàng)建廣播接收機(jī)Host進(jìn)程
            if ((r.curApp=mService.startProcessLocked(targetProcess,
                    info.activityInfo.applicationInfo, true,
                    r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
                    "broadcast", r.curComponent,
                    (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
                            == null) {
                // Ah, this recipient is unavailable.  Finish it if necessary,
                // and mark the broadcast record as ready for the next.
                Slog.w(TAG, "Unable to launch app "
                        + info.activityInfo.applicationInfo.packageName + "/"
                        + info.activityInfo.applicationInfo.uid + " for broadcast "
                        + r.intent + ": process is bad");
                logBroadcastReceiverDiscardLocked(r);
                finishReceiverLocked(r, r.resultCode, r.resultData,
                        r.resultExtras, r.resultAbort, false);
                scheduleBroadcastsLocked();
                r.state = BroadcastRecord.IDLE;
                return;
            }

            mPendingBroadcast = r;
            }
    }

超時(shí)消息處理绞愚,超時(shí)時(shí)間到直接調(diào)用broadcastTimeoutLocked處理

161    private final class BroadcastHandler extends Handler {
162        public BroadcastHandler(Looper looper) {
163            super(looper, null, true);
164        }
165
166        @Override
167        public void handleMessage(Message msg) {
168            switch (msg.what) {
169                case BROADCAST_INTENT_MSG: {
170                    if (DEBUG_BROADCAST) Slog.v(
171                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG");
172                    processNextBroadcast(true);
173                } break;
174                case BROADCAST_TIMEOUT_MSG: {
175                    synchronized (mService) {
176                        broadcastTimeoutLocked(true);
177                    }
178                } break;
179            }
180        }
181    }
    final void broadcastTimeoutLocked(boolean fromMsg) {
        // fromMsg標(biāo)記超時(shí)觸發(fā)者,true表示超時(shí)消息觸發(fā)
        // false表示直接調(diào)用超時(shí)處理
        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;
            // 如果發(fā)送給當(dāng)前廣播接收機(jī)(可能多個(gè))沒有超時(shí)颖医,則重新設(shè)定超時(shí)消息位衩;從這里
            // 看出超時(shí)其實(shí)是針對單個(gè)廣播接收機(jī),如果多個(gè)廣播接收機(jī)收發(fā)累計(jì)時(shí)間
            // 超時(shí)熔萧,并不會(huì)觸發(fā)ANR糖驴。
            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_BROADCAST,
                        "Premature timeout ["
                        + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
                        + timeoutTime);
                setBroadcastTimeoutLocked(timeoutTime);
                return;
            }
        }
        ...
        // 觸發(fā)廣播超時(shí)ANR
        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));
        }
    }

broadcastTimeoutLocked根據(jù)參數(shù)fromMsg進(jìn)一步判定是否確實(shí)廣播超時(shí)ANR,這里需要注意并不是沒發(fā)送一條廣播就發(fā)送BROADCAST_TIMEOUT_MSG消息佛致,而在每個(gè)receiver接收處理后才cancelBroadcastTimeoutLocked, 而是在每次timeout之后判斷當(dāng)前廣播處理時(shí)間是否超時(shí)贮缕,沒有超時(shí)會(huì)重新發(fā)送BROADCAST_TIMEOUT_MSG,用于延長timeout時(shí)間俺榆,這樣可以避免比如100條有序廣播就要發(fā)送100個(gè)BROADCAST_TIMEOUT_MSG消息以及100次cancelBroadcastTimeoutLocked

Broadcast2.png

2. 輸入事件超時(shí)

/frameworks/native/services/inputflinger/InputDispatcher.cpp

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    int32_t injectionResult;
    std::string reason;

... ...

    // Check whether the window is ready for more input.
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            mFocusedWindowHandle, entry, "focused");
    if (!reason.empty()) {
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.c_str());
        goto Unresponsive;
    }

    // Success!  Output targets.
    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
    addWindowTargetLocked(mFocusedWindowHandle,
            InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
            inputTargets);

    // Done.
Failed:
Unresponsive:
    nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
    updateDispatchStatisticsLocked(currentTime, entry,
            injectionResult, timeSpentWaitingForApplication);
#if DEBUG_FOCUS
    ALOGD("findFocusedWindow finished: injectionResult=%d, "
            "timeSpentWaitingForApplication=%0.1fms",
            injectionResult, timeSpentWaitingForApplication / 1000000.0);
#endif
    return injectionResult;
}

checkWindowReadyForMoreInputLocked方法判斷窗口是否準(zhǔn)備號接受事件

std::string InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // If the window is paused then keep waiting.
    if (windowHandle->getInfo()->paused) {
        return StringPrintf("Waiting because the %s window is paused.", targetType);
    }
    //獲取InputChannel在mConnectionsByFd中的索引,從而得到對應(yīng)的Connection對象
    // If the window's connection is not registered then keep waiting.
    ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
    if (connectionIndex < 0) {
        return StringPrintf("Waiting because the %s window's input channel is not "
                "registered with the input dispatcher.  The window may be in the process "
                "of being removed.", targetType);
    }

    // If the connection is dead then keep waiting.
    sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
    if (connection->status != Connection::STATUS_NORMAL) {
        return StringPrintf("Waiting because the %s window's input connection is %s."
                "The window may be in the process of being removed.", targetType,
                connection->getStatusLabel());
    }
     //inputPublisher被block,比如窗口反饋慢,導(dǎo)致InputChannel被寫滿,inputPublisher就會(huì)被block
    // If the connection is backed up then keep waiting.
    if (connection->inputPublisherBlocked) {
        return StringPrintf("Waiting because the %s window's input channel is full.  "
                "Outbound queue length: %d.  Wait queue length: %d.",
                targetType, connection->outboundQueue.count(), connection->waitQueue.count());
    }
    //Key事件要求outboundQueue和waitQueue全部為空才繼續(xù)分發(fā)
    // Ensure that the dispatch queues aren't too far backed up for this event.
    if (eventEntry->type == EventEntry::TYPE_KEY) {
        // If the event is a key event, then we must wait for all previous events to
        // complete before delivering it because previous events may have the
        // side-effect of transferring focus to a different window and we want to
        // ensure that the following keys are sent to the new window.
        //
        // Suppose the user touches a button in a window then immediately presses "A".
        // If the button causes a pop-up window to appear then we want to ensure that
        // the "A" key is delivered to the new pop-up window.  This is because users
        // often anticipate pending UI changes when typing on a keyboard.
        // To obtain this behavior, we must serialize key events with respect to all
        // prior input events.
        if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
            return StringPrintf("Waiting to send key event because the %s window has not "
                    "finished processing all of the input events that were previously "
                    "delivered to it.  Outbound queue length: %d.  Wait queue length: %d.",
                    targetType, connection->outboundQueue.count(), connection->waitQueue.count());
        }
    } else {
       //Touch事件只要求在0.5s內(nèi)窗口接到反饋即可,這個(gè)用waitQueue頭事件的處理事件作為判斷
        // Touch events can always be sent to a window immediately because the user intended
        // to touch whatever was visible at the time.  Even if focus changes or a new
        // window appears moments later, the touch event was meant to be delivered to
        // whatever window happened to be on screen at the time.
        //
        // Generic motion events, such as trackball or joystick events are a little trickier.
        // Like key events, generic motion events are delivered to the focused window.
        // Unlike key events, generic motion events don't tend to transfer focus to other
        // windows and it is not important for them to be serialized.  So we prefer to deliver
        // generic motion events as soon as possible to improve efficiency and reduce lag
        // through batching.
        //
        // The one case where we pause input event delivery is when the wait queue is piling
        // up with lots of events because the application is not responding.
        // This condition ensures that ANRs are detected reliably.
        if (!connection->waitQueue.isEmpty()
                && currentTime >= connection->waitQueue.head->deliveryTime
                        + STREAM_AHEAD_EVENT_TIMEOUT) {
            return StringPrintf("Waiting to send non-key event because the %s window has not "
                    "finished processing certain input events that were delivered to it over "
                    "%0.1fms ago.  Wait queue length: %d.  Wait queue head age: %0.1fms.",
                    targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
                    connection->waitQueue.count(),
                    (currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
        }
    }
    return "";
}

checkWindowReadyForMoreInputLocked返回reason不為空,則繼續(xù)調(diào)用handleTargetsNotReadyLocked處理

int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime, const char* reason) {
        //系統(tǒng)沒起來
    if (applicationHandle == NULL && windowHandle == NULL) {
        ... ...
        }
    } else {
    //ANR開始時(shí)間點(diǎn)記錄, 因?yàn)閙InputTargetWaitCause初始值為INPUT_TARGET_WAIT_CAUSE_NONE
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
#if DEBUG_FOCUS
            ALOGD("Waiting for application to become ready for input: %s.  Reason: %s",
                    getApplicationWindowLabelLocked(applicationHandle, windowHandle).c_str(),
                    reason);
#endif
            nsecs_t timeout;
            if (windowHandle != NULL) {
                timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else if (applicationHandle != NULL) {
                timeout = applicationHandle->getDispatchingTimeout(
                        DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else {
                timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
            }

            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            mInputTargetWaitStartTime = currentTime;
            mInputTargetWaitTimeoutTime = currentTime + timeout;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();

            if (windowHandle != NULL) {
                mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
            }
            if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
                mInputTargetWaitApplicationHandle = applicationHandle;
            }
        }
    }
    
    if (mInputTargetWaitTimeoutExpired) {
        return INPUT_EVENT_INJECTION_TIMED_OUT;
    }
    //再次窗口沒準(zhǔn)備好則會(huì)判斷是否超過ANR時(shí)間,如果超過引發(fā)ANR
    if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);

        // Force poll loop to wake up immediately on next iteration once we get the
        // ANR response back from the policy.
        *nextWakeupTime = LONG_LONG_MIN;
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

所以引發(fā)ANR的整體流程可以概況為:

inputdispacher派發(fā)線程在找到分發(fā)窗口后,首先判斷窗口是否準(zhǔn)備好了,如果準(zhǔn)備好了(Key事件兩個(gè)隊(duì)列都為空,touch事件0.5秒內(nèi)有反饋),那么直接寫入InputChannel 分發(fā)給窗口, 分發(fā)線程進(jìn)入休眠等待反饋,準(zhǔn)備進(jìn)行下次循環(huán),如果窗口沒有準(zhǔn)備好,那么記錄ANR開始時(shí)間,在下一次分發(fā)時(shí)如果窗口還沒有準(zhǔn)備好,那么判斷時(shí)間,超時(shí)則拋出ANR

input.png

ANR Trace打印

1. AMS中如何處理ANR感昼?

無論是哪種超時(shí)最終都會(huì)調(diào)用方法AppErrors.a(chǎn)ppNotResponding去處理ANR后續(xù)工作:
android/frameworks/base/services/core/java/com/android/server/am/AppErrors.java

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);
      ... ...
             //1. 將persistent進(jìn)程和treatLikeActivity(輸入法)還有當(dāng)前發(fā)生ANR 的進(jìn)程放入firstPids中,其余正在運(yùn)行的進(jìn)程放入lastPids中
              for (int i = mService.mLruProcesses.size() - 1; i >= 0; i--) {
                  ProcessRecord r = mService.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);
                              if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
                          } else if (r.treatLikeActivity) {
                              firstPids.add(pid);
                              if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
                          } else {
                              lastPids.put(pid, Boolean.TRUE);
                              if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
                          }
                      }
                  }
              }
          }
      }
  ...
      
        //2. 這里創(chuàng)建和打印ANR  trace文件
        File tracesFile = ActivityManagerService.dumpStackTraces(
                true, firstPids,
                (isSilentANR) ? null : processCpuTracker,
                (isSilentANR) ? null : lastPids,
                nativePids);
   //3. 打印CPUInfo
        String cpuInfo = null;
        if (ActivityManagerService.MONITOR_CPU_USAGE) {
            mService.updateCpuStatsNow();
            synchronized (mService.mProcessCpuTracker) {
                cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
            }
            info.append(processCpuTracker.printCurrentLoad());
            info.append(cpuInfo);
        }

        info.append(processCpuTracker.printCurrentState(anrTime));

        Slog.e(TAG, info.toString());
        if (tracesFile == null) {
            // There is no trace file, so dump (only) the alleged culprit's threads to the log
            Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
        }

       ... 
            
            // 4.顯示ANR dialog
            Message msg = Message.obtain();
            msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
            msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);

            mService.mUiHandler.sendMessage(msg);
        }
    }

這里我們重點(diǎn)關(guān)心第二部打印ANR trace文件罐脊,我們在ANR trace文件中會(huì)看到很多進(jìn)程都打出了他們的線程堆棧等信息定嗓,那么是怎么決定哪些進(jìn)程可以打印出來呢,就是上面計(jì)算的firstPids和lastPids決定:

  • 發(fā)生ANR的進(jìn)程第一個(gè)打印
  • 按最近使用進(jìn)程排序萍桌,persistent和treatLikeActivity進(jìn)程打印
  • 其余的最多使用CPU 的前5個(gè)進(jìn)程打印

android/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    public static File dumpStackTraces(ArrayList<Integer> firstPids,
            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
            ArrayList<Integer> nativePids) {
        ArrayList<Integer> extraPids = null;

        Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);

        ... 
        //開始創(chuàng)建data/anr路徑下的trace文件
        final File tracesDir = new File(ANR_TRACE_DIR);
        // Each set of ANR traces is written to a separate file and dumpstate will process
        // all such files and add them to a captured bug report if they're recent enough.
        maybePruneOldTraces(tracesDir);

        // NOTE: We should consider creating the file in native code atomically once we've
        // gotten rid of the old scheme of dumping and lot of the code that deals with paths
        // can be removed.
        File tracesFile = createAnrDumpFile(tracesDir);
        if (tracesFile == null) {
            return null;
        }
   
        dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids);
        return tracesFile;
    }

    public static void dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
            ArrayList<Integer> nativePids, ArrayList<Integer> extraPids) {

        Slog.i(TAG, "Dumping to " + tracesFile);

        // We don't need any sort of inotify based monitoring when we're dumping traces via
        // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
        // control of all writes to the file in question.

        // We must complete all stack dumps within 20 seconds.
        long remainingTime = 20 * 1000;

        // First collect all of the stacks of the most important pids.
        if (firstPids != null) {
            int num = firstPids.size();
            for (int i = 0; i < num; i++) {
                Slog.i(TAG, "Collecting stacks for pid " + firstPids.get(i));
                final long timeTaken = dumpJavaTracesTombstoned(firstPids.get(i), tracesFile,
                                                                remainingTime);

                remainingTime -= timeTaken;
                if (remainingTime <= 0) {
                    Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + firstPids.get(i) +
                           "); deadline exceeded.");
                    return;
                }

                if (DEBUG_ANR) {
                    Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms");
                }
            }
        }

     ∠Α... 
        Slog.i(TAG, "Done dumping");
    }

這個(gè)方法的最終目標(biāo)是中向各個(gè)java/native 進(jìn)程通過標(biāo)準(zhǔn)Linux接口sigqueue發(fā)送SIGQUIT.

2. 應(yīng)用進(jìn)程在trace中的信息是如何打印的?

catcher.png

Zygote fork 子進(jìn)程的時(shí)候會(huì)調(diào)用InitNonZygoteOrPostFork方法上炎,在這里創(chuàng)建SignalCatcher線程恃逻,而這個(gè)線程就是用于監(jiān)控ANR并打印trace

/art/runtime/runtime.cc

void Runtime::InitNonZygoteOrPostFork(
    JNIEnv* env,
    bool is_system_server,
    NativeBridgeAction action,
    const char* isa,
    bool profile_system_server) {
         ... 
         StartSignalCatcher();
         ... 
}

void Runtime::StartSignalCatcher() {
  if (!is_zygote_) {
    signal_catcher_ = new SignalCatcher(stack_trace_file_, use_tombstoned_traces_);
  }
}

接下來看SignalCatcher構(gòu)造方法:

/art/runtime/signal_catcher.cc

SignalCatcher::SignalCatcher(const std::string& stack_trace_file,
                             bool use_tombstoned_stack_trace_fd)
    : stack_trace_file_(stack_trace_file),
      use_tombstoned_stack_trace_fd_(use_tombstoned_stack_trace_fd),
      lock_("SignalCatcher lock"),
      cond_("SignalCatcher::cond_", lock_),
      thread_(nullptr) {
  ...

  SetHaltFlag(false);

  // Create a raw pthread; its start routine will attach to the runtime.
  CHECK_PTHREAD_CALL(pthread_create, (&pthread_, nullptr, &Run, this), "signal catcher thread");

}

這里使用pthread_create系統(tǒng)調(diào)用創(chuàng)建一個(gè)linux線程并開始執(zhí)行其run方法

void* SignalCatcher::Run(void* arg) {
  SignalCatcher* signal_catcher = reinterpret_cast<SignalCatcher*>(arg);
  CHECK(signal_catcher != nullptr);

  Runtime* runtime = Runtime::Current();
  //(1)將當(dāng)前線程鏈接到JavaVM,是Linux線程擁有java線程狀態(tài)可以打印堆棧等
  CHECK(runtime->AttachCurrentThread("Signal Catcher", true, runtime->GetSystemThreadGroup(),
                                     !runtime->IsAotCompiler()));

  Thread* self = Thread::Current();
  DCHECK_NE(self->GetState(), kRunnable);
  {
    MutexLock mu(self, signal_catcher->lock_);
    signal_catcher->thread_ = self;
    signal_catcher->cond_.Broadcast(self);
  }

  // Set up mask with signals we want to handle.
  SignalSet signals;
  //(2) 接收SIGQUIT信號
  signals.Add(SIGQUIT);
  signals.Add(SIGUSR1);
  //(3)循環(huán)等待SIGQUIT信號,當(dāng)有信號到來時(shí)辛块,WaitForSignal方法會(huì)返回
  while (true) {
    //
    int signal_number = signal_catcher->WaitForSignal(self, signals);
    if (signal_catcher->ShouldHalt()) {
      runtime->DetachCurrentThread();
      return nullptr;
    }

    switch (signal_number) {
    case SIGQUIT:
      signal_catcher->HandleSigQuit();
      break;
    case SIGUSR1:
      signal_catcher->HandleSigUsr1();
      break;
    default:
      LOG(ERROR) << "Unexpected signal %d" << signal_number;
      break;
    }
  }
}
  • AttachCurrentThread

    可參考JNI Tips:所有的線程都是Linux線程畔派,都有l(wèi)inux內(nèi)核統(tǒng)一調(diào)度。他們通常都是由托管代碼(Java或Kotlin)啟動(dòng)(通過使用Thread.start),但它們也能夠在其他任何地方創(chuàng)建润绵,然后連接(attach)到JavaVM线椰。例如,一個(gè)用pthread_create啟動(dòng)的線程能夠使用JNI AttachCurrentThread 或 AttachCurrentThreadAsDaemon函數(shù)連接到JavaVM尘盼。在一個(gè)線程成功連接(attach)之前憨愉,它沒有JNIEnv,不能夠調(diào)用JNI函數(shù)卿捎。

接受到SIGQUIT信號后在HandleSigQuit方法中去處理配紫,這里主要調(diào)用DumpForSigQuit

/art/runtime/runtime.cc

void Runtime::DumpForSigQuit(std::ostream& os) {
  GetClassLinker()->DumpForSigQuit(os);//打印ClassLoader信息
  GetInternTable()->DumpForSigQuit(os);
  GetJavaVM()->DumpForSigQuit(os);//虛擬機(jī)信息
  GetHeap()->DumpForSigQuit(os);//堆信息,對象分配情況
  oat_file_manager_->DumpForSigQuit(os);
  if (GetJit() != nullptr) {
    GetJit()->DumpForSigQuit(os);
  } else {
    os << "Running non JIT\n";
  }
  DumpDeoptimizations(os);
  TrackedAllocators::Dump(os);
  os << "\n";

  thread_list_->DumpForSigQuit(os);//線程調(diào)用棧午阵,這里首先suspendall 再打印堆棧
  BaseMutex::DumpAll(os);

  // Inform anyone else who is interested in SigQuit.
  {
    ScopedObjectAccess soa(Thread::Current());
    callbacks_->SigQuit();
  }
}

打印ClassLoader,堆躺孝,GC以及線程堆棧信息,除了我們平時(shí)最關(guān)心的線程堆棧底桂,其實(shí)還有很多信息植袍,我們以堆為例看下在anr文件中是什么樣子的
/art/runtime/gc/heap.cc

void Heap::DumpForSigQuit(std::ostream& os) {
  os << "Heap: " << GetPercentFree() << "% free, " << PrettySize(GetBytesAllocated()) << "/"
     << PrettySize(GetTotalMemory()) << "; " << GetObjectsAllocated() << " objects\n";
  DumpGcPerformanceInfo(os);
}
----- pid 18841 at 2020-07-01 11:54:25 -----
Cmd line: com.dunkinbrands.otgo
Build fingerprint: 'TCL/Alcatel_5002R/Seoul_ATT:10/QP1A.190711.020/vGZA4-0:user/release-keys'
ABI: 'arm'
Build type: optimized
Zygote loaded classes=9140 post zygote classes=8201
Dumping registered class loaders
#0 dalvik.system.PathClassLoader: [], parent #1
#1 java.lang.BootClassLoader: [], no parent
#2 dalvik.system.PathClassLoader: [/data/app/com.dunkinbrands.otgo--NP2B6q6I0_yAuykgwOpBA==/base.apk:/data/app/com.dunkinbrands.otgo--NP2B6q6I0_yAuykgwOpBA==/base.apk!classes2.dex], parent #1
#3 dalvik.system.InMemoryDexClassLoader: [/data/user/0/com.dunkinbrands.otgo/Anonymous-DexFile@2165240094.jar], parent #2
#4 dalvik.system.InMemoryDexClassLoader: [/data/user/0/com.dunkinbrands.otgo/Anonymous-DexFile@1741528175.jar], parent #2
#5 dalvik.system.DexClassLoader: [/data/user/0/com.dunkinbrands.otgo/cache/generated-3F92340A3D311257BAA79F7EDB761B9E6E665EE8.jar], parent #2
#6 dalvik.system.PathClassLoader: [/data/app/com.google.android.gms-7yZdufzTzp3vlSkd2gcjDQ==/base.apk:/data/app/com.google.android.gms-7yZdufzTzp3vlSkd2gcjDQ==/base.apk!classes2.dex:/data/app/com.google.android.gms-7yZdufzTzp3vlSkd2gcjDQ==/base.apk!classes3.dex:/data/app/com.google.android.gms-7yZdufzTzp3vlSkd2gcjDQ==/base.apk!classes4.dex:/data/app/com.google.android.gms-7yZdufzTzp3vlSkd2gcjDQ==/base.apk!classes5.dex:/data/app/com.google.android.gms-7yZdufzTzp3vlSkd2gcjDQ==/base.apk!classes6.dex], parent #1
#7 dalvik.system.PathClassLoader: [/data/app/com.google.android.trichromelibrary_410410680-CHXkMAKnKoz7r9p8q3qhLw==/base.apk], parent #1
#8 dalvik.system.PathClassLoader: [/data/app/com.google.android.webview-3b4RqE2Pf-rc8rUq7I6Mag==/base.apk], parent #1
#9 dalvik.system.PathClassLoader: [/system/framework/org.apache.http.legacy.jar], parent #1
#10 com.google.android.gms.dynamite.zzh: [/data/user_de/0/com.google.android.gms/app_chimera/m/0000000c/DynamiteLoader.apk], parent #0
#11 dalvik.system.DelegateLastClassLoader: [/data/user_de/0/com.google.android.gms/app_chimera/m/00000010/MapsDynamite.apk], parent #2
#12 dalvik.system.DelegateLastClassLoader: [/data/user_de/0/com.google.android.gms/app_chimera/m/0000000f/GoogleCertificates.apk], parent #2
Done dumping class loaders
Intern table: 49095 strong; 2443 weak
JNI: CheckJNI is off; globals=983 (plus 110 weak)
Libraries: /data/app/com.dunkinbrands.otgo--NP2B6q6I0_yAuykgwOpBA==/lib/arm/libag3.so /data/app/com.google.android.gms-7yZdufzTzp3vlSkd2gcjDQ==/lib/arm/libconscrypt_gmscore_jni.so /data/app/com.google.android.trichromelibrary_410410680-CHXkMAKnKoz7r9p8q3qhLw==/base.apk!/lib/armeabi-v7a/libmonochrome.so /data/user/0/com.dunkinbrands.otgo/files/libcrashreport.so /system/lib/libwebviewchromium_plat_support.so libandroid.so libcompiler_rt.so libdcfdecoderjni.so libjavacore.so libjavacrypto.so libjnigraphics.so libmedia_jni.so libopenjdk.so libsoundpool.so libwebviewchromium_loader.so (15)
Heap: 6% free, 38MB/41MB; 657564 objects
... ...

總結(jié)

本文主要主要介紹了如下內(nèi)容:

  • 以廣播超時(shí)和輸入時(shí)間處理超時(shí)為例介紹了ANR發(fā)生場景,這里簡單介紹了有序和無序廣播實(shí)現(xiàn)原理籽懦,而只有有序廣播才會(huì)存在觸發(fā)ANR的情況,輸入事件超時(shí)利用的是判斷waitQueue中事件處理的時(shí)間于个,即應(yīng)用處理事件反饋時(shí)長
  • 符合觸發(fā)ANR 條件后,AMS是如何處理的暮顺,主要包括Log中打印ANR原因以及CPU Info厅篓,創(chuàng)建和打印trace文件,彈出dialog殺應(yīng)用進(jìn)程等捶码,這里重點(diǎn)關(guān)注AMS通知應(yīng)用進(jìn)程dump進(jìn)程信息是通過信號通信方式羽氮,及發(fā)送SIGQUIT
  • 應(yīng)用進(jìn)程創(chuàng)建時(shí)會(huì)創(chuàng)建一個(gè)SignalCatcher線程,SignalCatcher線程監(jiān)聽到SIGQUIT宙项,首先suspendall 再打印線程堆棧等信息
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乏苦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子尤筐,更是在濱河造成了極大的恐慌,老刑警劉巖洞就,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盆繁,死亡現(xiàn)場離奇詭異,居然都是意外死亡旬蟋,警方通過查閱死者的電腦和手機(jī)油昂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冕碟,你說我怎么就攤上這事拦惋。” “怎么了安寺?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵厕妖,是天一觀的道長。 經(jīng)常有香客問我挑庶,道長言秸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任迎捺,我火速辦了婚禮举畸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凳枝。我一直安慰自己抄沮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布岖瑰。 她就那樣靜靜地躺著叛买,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锭环。 梳的紋絲不亂的頭發(fā)上聪全,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音辅辩,去河邊找鬼难礼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛玫锋,可吹牛的內(nèi)容都是我干的蛾茉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼撩鹿,長吁一口氣:“原來是場噩夢啊……” “哼谦炬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起节沦,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤键思,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后甫贯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吼鳞,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年叫搁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赔桌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片供炎。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疾党,靈堂內(nèi)的尸體忽然破棺而出音诫,到底是詐尸還是另有隱情,我是刑警寧澤雪位,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布竭钝,位于F島的核電站,受9級特大地震影響茧泪,放射性物質(zhì)發(fā)生泄漏蜓氨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一队伟、第九天 我趴在偏房一處隱蔽的房頂上張望穴吹。 院中可真熱鬧,春花似錦嗜侮、人聲如沸港令。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顷霹。三九已至,卻和暖如春击吱,著一層夾襖步出監(jiān)牢的瞬間淋淀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工覆醇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朵纷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓永脓,卻偏偏與公主長得像袍辞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子常摧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345