Broadcast(三)sendBroadcast

sendBroadcast、sendStickyBroadcast、sendOrderedBroadcast基本一样,只差一个参数broadcastIntent方法倒数第二个参数代表sticky,倒数第三个参数代表serialized

@Override
@Deprecated
public void sendStickyBroadcast(Intent intent) {
    warnIfCallingFromSystemProcess();
    String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
    try {
        intent.prepareToLeaveProcess(this);
        // 这个true就代表是sticky
        ActivityManagerNative.getDefault().broadcastIntent(
            mMainThread.getApplicationThread(), intent, resolvedType, null,
            Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true,
            getUserId());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

ActivityManagerService.java
broadcastIntent→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) {
    // step1:设置flag
    // step2:广播权限校验
    // step3:处理系统广播
    // step4:增加sticky广播
    // step5:查询receivers(静态注册的接收者)和registeredReceivers(动态注册的接收者)
    // step6:处理无序广播
    // step7:合并registeredReceivers到receivers
    // step8:处理有序广播
    return ActivityManager.BROADCAST_SUCCESS;
}

sendBroadcast→→→→→1 2 3 x 5 6 7 8
sendStickyBroadcast→→1 2 3 4 5 6 7 8
sendOrderedBroadcast→1 2 3 x 5 x 7 8
注意这3个方法执行的step,下面会详细说明

step1:设置广播flag

intent = new Intent(intent);
//广播不会发送给已停止的package
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

//当没有启动完成时,不允许启动新进程
if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
userId = handleIncomingUser(callingPid, callingUid, userId,
        true, ALLOW_NON_FULL, "broadcast", callerPackage);

//当非USER_ALL广播且当前用户并没有处于Running的情况下,除非是系统升级广播或者关机广播,否则直接返回
if (userId != UserHandle.USER_ALL && !isUserRunningLocked(userId, false)) {
    if ((callingUid != Process.SYSTEM_UID
            || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
            && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
        return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
    }
}

BroadcastReceiver还有其他flag,位于Intent.java常量

FLAG_RECEIVER_REGISTERED_ONLY //只允许已注册receiver接收广播
FLAG_RECEIVER_REPLACE_PENDING //新广播会替代相同广播
FLAG_RECEIVER_FOREGROUND //只允许前台receiver接收广播
FLAG_RECEIVER_NO_ABORT //对于有序广播,先接收到的receiver无权抛弃广播
FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT //Boot完成之前,只允许已注册receiver接收广播
FLAG_RECEIVER_BOOT_UPGRADE //升级模式下,允许系统准备就绪前可以发送广播

step2:广播权限验证

//对于callingAppId为SYSTEM_UID,PHONE_UID,SHELL_UID,BLUETOOTH_UID,NFC_UID或者callingUid == 0时直接通过
int callingAppId = UserHandle.getAppId(callingUid);
if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID
    || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID
    || callingAppId == Process.NFC_UID || callingUid == 0) {
    //直接通过
} else if (callerApp == null || !callerApp.persistent) {
    try {
        if (AppGlobals.getPackageManager().isProtectedBroadcast(
                intent.getAction())) {
            //不允许发送给受保护的广播
            throw new SecurityException(msg);
        } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {
            ...
        }
    } catch (RemoteException e) {
        return ActivityManager.BROADCAST_SUCCESS;
    }
}

step3:处理系统相关广播

final String action = intent.getAction();
if (action != null) {
    switch (action) {
        case Intent.ACTION_UID_REMOVED:
        case Intent.ACTION_PACKAGE_REMOVED:
        case Intent.ACTION_PACKAGE_CHANGED:
        case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
        case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
            // Handle special intents: if this broadcast is from the package
            // manager about a package being removed, we need to remove all of
            // its activities from the history stack.
            if (checkComponentPermission(
                    android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
                    callingPid, callingUid, -1, true)
                    != PackageManager.PERMISSION_GRANTED) {
                String msg = "Permission Denial: " + intent.getAction()
                        + " broadcast from " + callerPackage + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")"
                        + " requires "
                        + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
                Slog.w(TAG, msg);
                throw new SecurityException(msg);
            }
            switch (action) {
                case Intent.ACTION_UID_REMOVED:
                    final Bundle intentExtras = intent.getExtras();
                    final int uid = intentExtras != null
                            ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
                    if (uid >= 0) {
                        mBatteryStatsService.removeUid(uid);
                        mAppOpsService.uidRemoved(uid);
                    }
                    break;
                case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
                    // If resources are unavailable just force stop all those packages
                    // and flush the attribute cache as well.
                    String list[] =
                            intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                    if (list != null && list.length > 0) {
                        for (int i = 0; i < list.length; i++) {
                            forceStopPackageLocked(list[i], -1, false, true, true,
                                    false, false, userId, "storage unmount");
                        }
                        mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
                        sendPackageBroadcastLocked(
                                IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list,
                                userId);
                    }
                    break;
                case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
                    mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
                    break;
                case Intent.ACTION_PACKAGE_REMOVED:
                case Intent.ACTION_PACKAGE_CHANGED:
                    Uri data = intent.getData();
                    String ssp;
                    if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
                        boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(action);
                        boolean fullUninstall = removed &&
                                !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                        if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) {
                            forceStopPackageLocked(ssp, UserHandle.getAppId(
                                    intent.getIntExtra(Intent.EXTRA_UID, -1)),
                                    false, true, true, false, fullUninstall, userId,
                                    removed ? "pkg removed" : "pkg changed");
                        }
                        if (removed) {
                            sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED,
                                    new String[] {ssp}, userId);
                            if (fullUninstall) {
                                mAppOpsService.packageRemoved(
                                        intent.getIntExtra(Intent.EXTRA_UID, -1), ssp);

                                // Remove all permissions granted from/to this package
                                removeUriPermissionsForPackageLocked(ssp, userId, true);

                                removeTasksByPackageNameLocked(ssp, userId);
                                mBatteryStatsService.notePackageUninstalled(ssp);
                            }
                        } else {
                            cleanupDisabledPackageComponentsLocked(ssp, userId,
                                    intent.getStringArrayExtra(
                                            Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
                        }
                    }
                    break;
            }
            break;
        case Intent.ACTION_PACKAGE_ADDED:
            // Special case for adding a package: by default turn on compatibility mode.
            Uri data = intent.getData();
            String ssp;
            if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
                final boolean replacing =
                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                mCompatModePackages.handlePackageAddedLocked(ssp, replacing);

                try {
                    ApplicationInfo ai = AppGlobals.getPackageManager().
                            getApplicationInfo(ssp, 0, 0);
                    mBatteryStatsService.notePackageInstalled(ssp,
                            ai != null ? ai.versionCode : 0);
                } catch (RemoteException e) {
                }
            }
            break;
        case Intent.ACTION_TIMEZONE_CHANGED:
            // If this is the time zone changed action, queue up a message that will reset
            // the timezone of all currently running processes. This message will get
            // queued up before the broadcast happens.
            mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
            break;
        case Intent.ACTION_TIME_CHANGED:
            // If the user set the time, let all running processes know.
            final int is24Hour =
                    intent.getBooleanExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1
                            : 0;
            mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0));
            BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
            synchronized (stats) {
                stats.noteCurrentTimeChangedLocked();
            }
            break;
        case Intent.ACTION_CLEAR_DNS_CACHE:
            mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
            break;
        case Proxy.PROXY_CHANGE_ACTION:
            ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);
            mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
            break;
    }
}

主要处理系统相关的广播,10个case

case Intent.ACTION_UID_REMOVED: //uid移除
case Intent.ACTION_PACKAGE_REMOVED: //package移除,
case Intent.ACTION_PACKAGE_CHANGED: //package改变
case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE: //外部设备不可用时,强制停止所有波及的应用并清空cache数据
case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: //外部设备可用
case Intent.ACTION_PACKAGE_ADDED: //增加package,处于兼容考虑

case Intent.ACTION_TIMEZONE_CHANGED: //时区改变,通知所有运行中的进程
case Intent.ACTION_TIME_CHANGED: //时间改变,通知所有运行中的进程
case Intent.ACTION_CLEAR_DNS_CACHE: //dns缓存清空
case Proxy.PROXY_CHANGE_ACTION: //网络代理改变

step4:增加sticky广播

// Add to the sticky list if requested.
if (sticky) {
    if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
                callingPid, callingUid)
                != PackageManager.PERMISSION_GRANTED) {
          throw new SecurityException(msg);
    }
    if (requiredPermissions != null && requiredPermissions.length > 0) {
        return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
    }
    if (intent.getComponent() != null) {
        throw new SecurityException(
                    "Sticky broadcasts can't target a specific component");
    }
    // We use userId directly here, since the "all" target is maintained
    // as a separate set of sticky broadcasts.
    if (userId != UserHandle.USER_ALL) {
        // But first, if this is not a broadcast to all users, then
        // make sure it doesn't conflict with an existing broadcast to
        // all users.
        ...
    }
    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
    if (stickies == null) {
        stickies = new ArrayMap<>();
        mStickyBroadcasts.put(userId, stickies);
    }
    ArrayList<Intent> list = stickies.get(intent.getAction());
    if (list == null) {
        list = new ArrayList<>();
        stickies.put(intent.getAction(), list);
    }
    final int stickiesCount = list.size();
    int i;
    for (i = 0; i < stickiesCount; i++) {
        if (intent.filterEquals(list.get(i))) {
            // This sticky already exists, replace it.
            list.set(i, new Intent(intent));
            break;
        }
    }
    // 新的intent追加到list
    if (i >= stickiesCount) {
        list.add(new Intent(intent));
    }
}

简单说,就是如果sticky==true,则mStickyBroadcasts增加一条广播
step5:查询receivers和registeredReceivers

int[] users;
if (userId == UserHandle.USER_ALL) {
    // 广播给所有已启动用户
    // Caller wants broadcast to go to all started users.
    users = mStartedUserArray;
} else {
    // Caller wants broadcast to go to one specific user.
    users = new 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) {
    // 根据intent查找静态注册的广播,已经按priority(默认值是0)从高到低排好顺序
    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
        UserManagerService ums = getUserManagerLocked();
        for (int i = 0; i < users.length; i++) {
            if (ums.hasUserRestriction(
                    UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
                continue;
            }
            // 查询动态注册的广播,已经按priority从高到低排好顺序
            List<BroadcastFilter> registeredReceiversForUser =
                    mReceiverResolver.queryIntent(intent,
                            resolvedType, false, users[i]);
            if (registeredReceivers == null) {
                registeredReceivers = registeredReceiversForUser;
            } else if (registeredReceiversForUser != null) {
                registeredReceivers.addAll(registeredReceiversForUser);
            }
        }
    } else {
        // 查询动态注册的广播,已经按priority从高到低排好顺序
        registeredReceivers = mReceiverResolver.queryIntent(intent,
                resolvedType, false, userId);
    }
}

查询静态注册的广播

private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
        int callingUid, int[] users) {
    List<ResolveInfo> receivers = null;
    try {
        HashSet<ComponentName> singleUserReceivers = null;
        boolean scannedFirstReceivers = false;
        for (int user : users) {
            // Skip users that have Shell restrictions
            if (callingUid == Process.SHELL_UID
                    && getUserManagerLocked().hasUserRestriction(
                            UserManager.DISALLOW_DEBUGGING_FEATURES, user)) {
                continue;
            }
            // 调用PackageManagerService.queryIntentReceivers,获取AndroidManifest.xml声明的接收者信息
            List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
                    .queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);
            if (user != UserHandle.USER_OWNER && newReceivers != null) {
                // If this is not the primary user, we need to check for
                // any receivers that should be filtered out.
                for (int i=0; i<newReceivers.size(); i++) {
                    ResolveInfo ri = newReceivers.get(i);
                    if ((ri.activityInfo.flags&ActivityInfo.FLAG_PRIMARY_USER_ONLY) != 0) {
                        // 移除FLAG_PRIMARY_USER_ONLY的receiver
                        newReceivers.remove(i);
                        i--;
                    }
                }
            }
            if (newReceivers != null && newReceivers.size() == 0) {
                newReceivers = null;
            }
            if (receivers == null) {
                receivers = newReceivers;
            } else if (newReceivers != null) {
                // We need to concatenate the additional receivers
                // found with what we have do far.  This would be easy,
                // but we also need to de-dup any receivers that are
                // singleUser.
                // 将所有用户的receiver整合到receivers
                ...
            }
        }
    } catch (RemoteException ex) {
        // pm is in same process, this will never happen.
    }
    return receivers;
}

step6:处理无序广播
如果ordered ==false,并且有动态注册的接收者,那么就会进入这个步骤。
在无序广播队列增加一条广播,然后遍历动态注册的接收者并通知。

final boolean replacePending =
        (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;

int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
    // If we are not serializing this broadcast, then send the
    // registered receivers separately so they don't wait for the
    // components to be launched.
    final BroadcastQueue queue = broadcastQueueForIntent(intent);
    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) {
        // 详情参考[Broadcast(二)registerReceiver时sticky广播]
        queue.enqueueParallelBroadcastLocked(r);
        queue.scheduleBroadcastsLocked();
    }
    // 注意这2变量重置
    registeredReceivers = null;
    NR = 0;
}

step7:合并registeredReceivers到receivers
按priority从高到低,把动态注册的接收者和静态注册的接收者合并。相同的priority,动态注册的接收者优先

// Merge into one list.
int ir = 0;
if (receivers != null) {
    // A special case for PACKAGE_ADDED: do not allow the package
    // being added to see this broadcast.  This prevents them from
    // using this as a back door to get run as soon as they are
    // installed.  Maybe in the future we want to have a special install
    // broadcast or such for apps, but we'd like to deliberately make
    // this decision.
    // 防止应用监听该广播,在安装时直接运行
    String skipPackages[] = null;
    if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
            || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
            || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
        Uri data = intent.getData();
        if (data != null) {
            String pkgName = data.getSchemeSpecificPart();
            if (pkgName != null) {
                skipPackages = new String[] { pkgName };
            }
        }
    } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
        skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
    }
    // 将skipPackages相关的广播接收者从receivers中移除
    if (skipPackages != null && (skipPackages.length > 0)) {
        for (String skipPackage : skipPackages) {
            if (skipPackage != null) {
                int NT = receivers.size();
                for (int it=0; it<NT; it++) {
                    ResolveInfo curt = (ResolveInfo)receivers.get(it);
                    if (curt.activityInfo.packageName.equals(skipPackage)) {
                        receivers.remove(it);
                        it--;
                        NT--;
                    }
                }
            }
        }
    }

    int NT = receivers != null ? receivers.size() : 0;
    int it = 0;
    ResolveInfo curt = null;
    BroadcastFilter curr = null;
    // 如果处理了无序广播,那么NR=0
    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++;
}

step8:处理有序广播
如果你明白上面7步做了什么,那你应该知道当ordered==true或者有静态注册的接收者的时候(显然只有这2个条件至少满足一个,receivers.size()才会大于0)就会进入这个步骤。

if ((receivers != null && receivers.size() > 0)
        || resultTo != null) {
    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);
    boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
    if (!replaced) {
        // 见[Broadcast (四)有序广播和ANR]
        queue.enqueueOrderedBroadcastLocked(r);
        queue.scheduleBroadcastsLocked();
    }
}

参考引用

Gityuan Android Broadcast广播机制分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容