Android源码--Intent的查找和匹配

前言

Android开发中,Intent 是极其重要的一个类,它是各个组件、进程之间通讯的纽带。那么系统是如何通过Intent 来查找到对应的组件呢?比如Activity 跳转,用户设置好了一个Intent之后 ,系统如何查找与匹配符合要求的Activity? 这就是本篇需要学习的内容。

App信息表的创建

在文章Android源码中单例模式 中,我们知道系统启动之后就会注册各种系统服务,如WindowManagerServiceAcitivityManagerService等,其中有一个就是PackageManagerService (后续简称PMS)。PMS 启动之后,会扫描系统中已经安装的apk 目录,例如系统App安装的目录为/system/app ,第三方应用的目录为/data/app,PMS 会解析apk包下的AndroidMainfest.xml文件得到App的相关信息。AndroidMainfest.xml又包含了ActivityService 等组件的注册信息 ,当PMS 扫描并且解析完成这些信息之后就会构建好了整个apk 的信息树,大致流程如图:

解析apk文件.png

PMS解析已安装的apk

PMS是一个尽职尽责的类, 对于apk的解析工作它在构造函数时,就已经开始了!我们看下PMS 的部分构造函数代码:

        synchronized (mInstallLock) {
        // writer
        synchronized (mPackages) {
            ...
            //获取data目录
            File dataDir = Environment.getDataDirectory();
            // /data/app 目录也就是第三方app安装的目录
            mAppInstallDir = new File(dataDir, "app");
            ...
            File frameworkDir = new File(Environment.getRootDirectory(), "framework"); 
            ...
            // Find base frameworks (resource packages without code).
            //加载frameworks资源
            scanDirTracedLI(frameworkDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED,
                    scanFlags | SCAN_NO_DEX, 0);

            // Collect ordinary system packages.
            //获取系统app安装路径
            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
            //扫描系统app安装路径
            scanDirTracedLI(systemAppDir, mDefParseFlags
                    | PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
            ...
            //扫描第三方app安装路径
            scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
            ...
            
            //扫描已经安装完的apk包
            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);

从上述代码中可以看到,PMS 不仅需要加载系统已经安装的apk ,在此之前还有加载Framework资源,加载了Framework资源之后才开始对扫描的指定目录的apk文件进行解析,上述代码只给出了系统apk的安装目录和第三方应用安装目录,扫描函数为scanDirTracedLI 我们看看这里面的实现:

    private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
         scanDirLI(dir, parseFlags, scanFlags, currentTime);
    }

    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
        //获取该目录下的所有文件
        final File[] files = dir.listFiles();
        ...
        // Submit files for parsing in parallel
        //解析目录下所有的apk文件
        int fileCount = 0;
        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            //如果不是apk文件 忽略
            if (!isPackage) {
                // Ignore entries which are not packages
                continue;
            }
            //解析apk文件
            parallelPackageParser.submit(file, parseFlags);
            fileCount++;
        }
    }
    
    public static final boolean isApkFile(File file) {
        return isApkPath(file.getName());
    }
    //是否已.apk结尾
    public static boolean isApkPath(String path) {
        return path.endsWith(".apk");
    }

scanDirLI 就是扫描指定目录下的所有apk文件,然后通过调用submit 进行解析,重点应该在submit 方法中,我们继续往下看:

    /**
     * 提交文件解析   
     * Submits the file for parsing
     * @param scanFile file to scan
     * @param parseFlags parse falgs
     */
    public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            //解析结果
            ParseResult pr = new ParseResult();
            try {
                //创建一个包解析器
                PackageParser pp = new PackageParser();
                pp.setSeparateProcesses(mSeparateProcesses);
                pp.setOnlyCoreApps(mOnlyCore);
                pp.setDisplayMetrics(mMetrics);
                pp.setCacheDir(mCacheDir);
                pp.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                //解析apk包
                pr.pkg = parsePackage(pp, scanFile, parseFlags);
            } catch (Throwable e) {
                pr.throwable = e;
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
            //将解析结果放入队列
             mQueue.put(pr);
        });
    }
    
    //调用PackageParser中的parsePackage方法解析
    @VisibleForTesting
    protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
            int parseFlags) throws PackageParser.PackageParserException {
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }

scanDirLI 方法中首先构造了一个PackageParser, 也就是一个apk解析器,然后调用PackageParser中的parsePackage方法解析,具体代码:

    public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (packageFile.isDirectory()) {
            //是文件夹类型 解析整个文件夹
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            //解析单个apk
            parsed = parseMonolithicPackage(packageFile, flags);
        }
        //缓存结果
        cacheResult(packageFile, flags, parsed);
        return parsed;
    }

parsePackage 方法中会根据packageFile 的类型来选择不同的解析方法,我们直接看解析单个apkparseMonolithicPackage 方法;

    @Deprecated
    public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
        ...
        //构造应用资源
        final AssetManager assets = newConfiguredAssetManager();
        try {
            //会调用parseBaseApk(apkPath, res, parser, flags, outError)
            final Package pkg = parseBaseApk(apkFile, assets, flags);
            pkg.setCodePath(apkFile.getAbsolutePath());
            pkg.setUse32bitAbi(lite.use32bitAbi);
            return pkg;
        } finally {
            IoUtils.closeQuietly(assets);
        }
    }

parseMonolithicPackage 方法中我们看到会调用有三个参数的parseBaseApk(apkFile, assets, flags);这三个参数的parseBaseApk 会调用五个参数的parseBaseApk ,我们先看下三个参数的parseBaseApk

    //三个参数的parseBaseApk
    private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
            throws PackageParserException {
        //获取apk绝对路径
        final String apkPath = apkFile.getAbsolutePath();
        ...
        Resources res = null;
        XmlResourceParser parser = null;
        try {
            //获取资源
            res = new Resources(assets, mMetrics, null);
            //获取解析ANDROID_MANIFEST_FILENAME清单文件的解析器
            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

            final String[] outError = new String[1];
            //调用五个参数的parseBaseApk解析清单文件
            final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
            ...
        }
    }
    //五个参数的parseBaseApk
    private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {
        ...
        //创建一个包实例
        final Package pkg = new Package(pkgName);
        //获取清单文件中属性
        TypedArray sa = res.obtainAttributes(parser,
                com.android.internal.R.styleable.AndroidManifest);
        //获取清单文件中版本号
        pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
                com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
        //获取清单文件中版本名
        pkg.mVersionName = sa.getNonConfigurationString(
                com.android.internal.R.styleable.AndroidManifest_versionName, 0);
        //回收
        sa.recycle();
        //真正解析清单文件
        return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
    }

解析清单文件

上面代码主要是获取清单文件的信息,真正解析清单文件的各个节点的方法是parseBaseApkCommon,我们看下如何实现的:

    private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
            XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
            IOException {
        ...
        //解析AndroidMainfest中的元素
        int outerDepth = parser.getDepth();
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }

            String tagName = parser.getName();
            //解析application标签 四大组件都在这里解析
            if (tagName.equals(TAG_APPLICATION)) {
                ...
                if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
                    return null;
                }
             //解析权限标签
            }  else if (tagName.equals(TAG_PERMISSION)) {
                if (!parsePermission(pkg, res, parser, outError)) {
                    return null;
                }
            }
        return pkg;
    }

这个parseBaseApkCommon 方法才是真正解析AndroidMainfest.xml的方法,这里给出了两个标签,即ApplicationpermissionApplication 中包含Activity、``Service等标签,也就是Intent 所需要的标签。我们看下Application 标签的parseBaseApplication 方法:

    private boolean parseBaseApplication(Package owner, Resources res,
            XmlResourceParser parser, int flags, String[] outError)
        throws XmlPullParserException, IOException {
        //应用信息
        final ApplicationInfo ai = owner.applicationInfo;
        //包名
        final String pkgName = owner.applicationInfo.packageName;
        //获取Application标签下的TypedArray
        TypedArray sa = res.obtainAttributes(parser,
                com.android.internal.R.styleable.AndroidManifestApplication);
        
        //解析applicatinon的属性 包括name lable icon logo roundIcon
        if (!parsePackageItemInfo(owner, ai, outError,
                "<application>", sa, false /*nameRequired*/,
                com.android.internal.R.styleable.AndroidManifestApplication_name,
                com.android.internal.R.styleable.AndroidManifestApplication_label,
                com.android.internal.R.styleable.AndroidManifestApplication_icon,
                com.android.internal.R.styleable.AndroidManifestApplication_roundIcon,
                com.android.internal.R.styleable.AndroidManifestApplication_logo,
                com.android.internal.R.styleable.AndroidManifestApplication_banner)) {
            sa.recycle();
            mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
            return false;
        }
        ...
        //解析Application标签下的Activity等标签
        final int innerDepth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                continue;
            }
            //获取标签名
            String tagName = parser.getName();
            //解析Activity
            if (tagName.equals("activity")) {
                Activity a = parseActivity(owner, res, parser, flags, outError, false,
                        owner.baseHardwareAccelerated);
                owner.activities.add(a);
            //解析receiver
            } else if (tagName.equals("receiver")) {
                Activity a = parseActivity(owner, res, parser, flags, outError, true, false);
                owner.receivers.add(a);
             //解析service
            } else if (tagName.equals("service")) {
                Service s = parseService(owner, res, parser, flags, outError);
                owner.services.add(s);
             //解析provider
            } else if (tagName.equals("provider")) {
                Provider p = parseProvider(owner, res, parser, flags, outError);
                owner.providers.add(p);
             //解析meta-data
            } else if (parser.getName().equals("meta-data")) {
                // note: application meta-data is stored off to the side, so it can
                // remain null in the primary copy (we like to avoid extra copies because
                // it can be large)
                if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData,
                        outError)) == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }
            }
        return true;
    }

看到ActivityServiceProviderReceived等标签时这个过程我们有了深刻的了解,从parseBaseApplication 中我们看到这个过程就是普通的xml 解析,根据不同的标签调用不同的解析方法,例如,解析Activity 会调用parseActivity 方法,然后返回一个Activity的实例, 并将这个实例添加到Package对象的activitys的列表中。

PackageManagerService的scanPackageTracedLI

此时,我们需要回到上述构造方法scanDirTracedLI 解析完成之后的scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null); 方法,该函数的具体实现如下:

    private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
            final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user) {
        final PackageParser.Package scannedPkg;
            // 调用scanPackageLI进行扫描apk包
            scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);
        return scannedPkg;
    }


    //scanPackageLI方法
    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user){
        ...
        //调用scanPackageDirtyLI进行扫描apk包
        final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
                    currentTime, user);
        return res;
    }


    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
            final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user){
        ...
        //提交包信息设置
        commitPackageSettings(pkg, pkgSetting, user, scanFlags,
                    (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
        ...
        return pkg;
    }

以上函数依次调用,最后scanPackageDirtyLI 函数会调用commitPackageSettings 对所解析完成的包提交信息,我们看下具体实现:

     */
    private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting,
            UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException {
        ...
        synchronized (mPackages) {
            int N = pkg.providers.size();
            int i;
            for (i=0; i<N; i++) {
                PackageParser.Provider p = pkg.providers.get(i);
                p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        p.info.processName);
                mProviders.addProvider(p);
                ...
                }
            }

            N = pkg.services.size();
            for (i=0; i<N; i++) {
                PackageParser.Service s = pkg.services.get(i);
                s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        s.info.processName);
                mServices.addService(s);

            }

            N = pkg.receivers.size();
            for (i=0; i<N; i++) {
                PackageParser.Activity a = pkg.receivers.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName);
                mReceivers.addActivity(a, "receiver");       
            }

            N = pkg.activities.size();
            for (i=0; i<N; i++) {
                PackageParser.Activity a = pkg.activities.get(i);
                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        a.info.processName);
                mActivities.addActivity(a, "activity");
            }
        }
    }

我们看到,这里将上一步解析的Activity、 Service添加到mActivitiesmServices 中,这些类型定义是PMS 的字段,我们看看下面的程序。

    public class PackageManagerService extends IPackageManager.Stub
            implements PackageSender {
        ...
        // All available activities, for your resolving pleasure.
        final ActivityIntentResolver mActivities =
                new ActivityIntentResolver();

        // All available receivers, for your resolving pleasure.
        final ActivityIntentResolver mReceivers =
                new ActivityIntentResolver();

        // All available services, for your resolving pleasure.
        final ServiceIntentResolver mServices = new ServiceIntentResolver();

        // All available providers, for your resolving pleasure.
        final ProviderIntentResolver mProviders = new ProviderIntentResolver();
        ...
    }

到这一步,整个已安装的apk的信息树就建立起来了,每个apk的应用名、包名、图标、ActivityService等信息都存储在系统中,当用户使用Intent跳转到某个Activity或者启动某个Service时,系统则会到这个信息表中进行查找,符合要求的组建就会被启动。这样就通过Intent将整个系统的组件连接在一起,使得Android系统成为一个组件可复用、灵活的系统。

Intent的精确匹配

上面分析了apk 信息表的构建过程,下面我们分析一下Intent 的查找与匹配的过程。在开发中,我们需要启动每个具体的Activity ,代码大致是这样的:

        Intent intent = new Intent(this, MyActivity.class);
        startActivity(intent);

这种情况下指定了具体的组件,也就是MyActivty,此时在系统查找组件时会使用精确匹配,我们称为显示Intent ,还有一种情况是不指定具体的组件,而是给出一些模糊的查询属性,例如:

        Intent intent = new Intent(Intent.ACTION_SEND);
        startActivity(intent);

这类我们称为隐式Intent

下面我们一起看看这些Intent的查找与匹配过程,startActivity这个函数经过几个函数的转发,最终会调用startActivityForResult,我们看看这个函数。

     */
    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {

            Instrumentation.ActivityResult ar =
                //启动Activity
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            ...
             //发送启动请求
             mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());

    }

Activity中的startActivityForResult 函数直接调用了InstrumentationexecStartActivity 方法,具体代码如下:

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        ...
        try {
            //将Intent中的数据迁移到粘贴板中
            intent.migrateExtraStreamToClipData();
            //准备离开当前进程
            intent.prepareToLeaveProcess(who);
            //调用AMS的startActivity
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        }
        return null;
    }

execStartActivity 里面其实就是调用ActivityManagerServicestartActivity 方法,这个方法里面调用了

ActivityStarter对象的startActivityMayWait 方法,这个方法里面调用了ActivityStackSupervisorresolveIntent 方法,这个方法里面最后调用了PMSresolveIntent 方法,PMS 又出现在了我们的视野中,在resolveIntent 方法中就调用了自身的queryIntentActivitiesInternal 方法,这个方法返回一个ResolveInfo的列表,

    private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
            String resolvedType, int flags, int userId) {
        //获取Intent的ComponentName对象
        ComponentName comp = intent.getComponent();
        //精确跳转时这个对象不为空
        if (comp != null) {
            final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
            //通过Intent直接获取ActivityInfo对象
            final ActivityInfo ai = getActivityInfo(comp, flags, userId);
            if (ai != null) {
                final ResolveInfo ri = new ResolveInfo();
                ri.activityInfo = ai;
                list.add(ri);
            }
            return list;
        }
        ...
        //Component为空则为隐式Intent
        synchronized (mPackages) {
                ...
                if (pkgName == null) {
                    //包名为空就模糊匹配
                    result = filterIfNotSystemUser(mActivities.queryIntent(
                    intent, resolvedType, flags, userId), userId);
                ...
                }else{
                  
                    //通过包名获取到Package对象
                    final PackageParser.Package pkg = mPackages.get(pkgName);
                    if (pkg != null) {
                        result = filterIfNotSystemUser(
                            //通过获取Package对象获取到ResoloveInfo
                            mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities, userId),userId);
                ...
                }

        }
   
        return result;
    }

上述函数大致过程:如果Intent 指明了Comonet ,那么直接通过Componet 就可以找到ActivityInfo 列表,这个列表的数量只有一个,这个ActivityInfo 就是指定的那个组件,如果没有指定具体的组件,那么Component

为空,此时先看Intent 是否指定了要调转到的目标组件所在的包名,如果没有包名,调用queryIntent 模糊匹配,例如Action、Category等,如果有包名,就会调用queryIntentForPackage 通过包名获取到对应的ActivityInfo。这里需要注意mActivities 是就是上一节说到的存储了从AndroidMainfest.xml中解析到的Activity 既然已经找到了对应的Activity的信息,那么最好就是启动对应的Activity 。对于显示Intent来说,就是直接跳转到具体的Activity 中,对于隐式Intent 来说,可能会弹出系统的列表框让用户选择相应的应用。

总结

在系统启动时PackageManagerService 会启动,此时PMS 将解析所有已安装的应用的信息,构建一个信息表,当用户通过Intent 来跳转到某个组件时,会根据Intent 中包含的信息到PMS 中查找对应的组件列表,最后跳转到目标组件中。

参考

《Android源码设计模式》

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

推荐阅读更多精彩内容