熱修復(fù)框架 - Tinker 安裝流程分析

代碼tinker 1.9.14.7
TinkerApplication初始化完成之后泽论,接著會在繼承DefaultApplicationLike的子類中進(jìn)行Tinker初始化:

@Override
public void onBaseContextAttached(Context base) {
    super.onBaseContextAttached(base);
    Log.d(TAG, "HotFixApplicationLike onBaseContextAttached");

    MultiDex.install(base);//使應(yīng)用支持分包

    LoadReporter loadReporter = new DefaultLoadReporter(base);
    PatchReporter patchReporter = new DefaultPatchReporter(base);
    PatchListener patchListener = new DefaultPatchListener(base);
    AbstractPatch upgradePatchProcessor = new UpgradePatch();

    TinkerInstaller.install(this,
            loadReporter,//加載合成的包的報告類
            patchReporter,//打修復(fù)包過程中的報告類
            patchListener,//對修復(fù)包最開始的檢查
            DefaultTinkerResultService.class, //patch包合成完成的后續(xù)操作服務(wù)
            upgradePatchProcessor);//生成一個新的patch合成包
}

這篇文章就研究下TinkerInstaller.install過程娱仔。

TinkerInstaller.java
public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
                             PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
                             AbstractPatch upgradePatchProcessor) {

    Tinker tinker = new Tinker.Builder(applicationLike.getApplication())
        .tinkerFlags(applicationLike.getTinkerFlags())
        .loadReport(loadReporter)
        .listener(listener)
        .patchReporter(patchReporter)
        .tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build();

    Tinker.create(tinker);
    tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor);
    return tinker;
}

Tinker類的初始化完成其做,然后調(diào)用Tinker的install

Tinker.java
public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,
                    AbstractPatch upgradePatch) {
    sInstalled = true;
   //將UpgradePatch和DefaultTinkerResultService 組合進(jìn)TinkerPatchService
    TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);

    TinkerLog.i(TAG, "try to install tinker, isEnable: %b, version: %s", isTinkerEnabled(), ShareConstants.TINKER_VERSION);

    if (!isTinkerEnabled()) {
        TinkerLog.e(TAG, "tinker is disabled");
        return;
    }
    if (intentResult == null) {
        throw new TinkerRuntimeException("intentResult must not be null.");
    }
    tinkerLoadResult = new TinkerLoadResult();
   //解析TinkerApplication啟動過程中反饋的加載補(bǔ)丁結(jié)果
    tinkerLoadResult.parseTinkerResult(getContext(), intentResult);
    //after load code set
    loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime);

    if (!loaded) {
        TinkerLog.w(TAG, "tinker load fail!");
    }
}

先看 TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);

TinkerPatchService.java
public static void setPatchProcessor(AbstractPatch upgradePatch, Class<? extends AbstractResultService> serviceClass) {
    upgradePatchProcessor = upgradePatch;
    resultServiceClass = serviceClass;
    //try to load
    try {
        Class.forName(serviceClass.getName());//確認(rèn)AbstractResultService實現(xiàn)類存在,如果存在會預(yù)先將.class加載到虛擬機(jī)中
    } catch (ClassNotFoundException e) {
        TinkerLog.printErrStackTrace(TAG, e, "patch processor class not found.");
    }
}

TinkerPatchService是執(zhí)行patch合成的服務(wù)铆铆,upgradePatch是進(jìn)行合成的功能類建车,AbstractResultService是返回結(jié)果處理的服務(wù),它可以自定義裳凸,默認(rèn)是合成成功會killPorcess贱鄙。

TinkerPatchService服務(wù)的啟動是在觸發(fā)合成patch的時候啟動的:

Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);

接著看 tinkerLoadResult.parseTinkerResult(getContext(), intentResult);

TinkerLoadResult.java
public boolean parseTinkerResult(Context context, Intent intentResult) {
    Tinker tinker = Tinker.with(context);
    loadCode = ShareIntentUtil.getIntentReturnCode(intentResult);

    costTime = ShareIntentUtil.getIntentPatchCostTime(intentResult);
    systemOTA = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, false);
    oatDir = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OAT_DIR);
    useInterpretMode = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH.equals(oatDir);

    final boolean isMainProcess = tinker.isMainProcess();

    TinkerLog.i(TAG, "parseTinkerResult loadCode:%d, process name:%s, main process:%b, systemOTA:%b, fingerPrint:%s, oatDir:%s, useInterpretMode:%b",
        loadCode, ShareTinkerInternals.getProcessName(context), isMainProcess, systemOTA, Build.FINGERPRINT, oatDir, useInterpretMode);

    //@Nullable
    final String oldVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OLD_VERSION);
    //@Nullable
    final String newVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_NEW_VERSION);

    final File patchDirectory = tinker.getPatchDirectory();
    final File patchInfoFile = tinker.getPatchInfoFile();

    if (oldVersion != null && newVersion != null) {
        if (isMainProcess) {
            currentVersion = newVersion;
        } else {
            currentVersion = oldVersion;
        }

        TinkerLog.i(TAG, "parseTinkerResult oldVersion:%s, newVersion:%s, current:%s", oldVersion, newVersion,
            currentVersion);
        //current version may be nil
        String patchName = SharePatchFileUtil.getPatchVersionDirectory(currentVersion);
        if (!ShareTinkerInternals.isNullOrNil(patchName)) {
            patchVersionDirectory = new File(patchDirectory.getAbsolutePath() + "/" + patchName);
            patchVersionFile = new File(patchVersionDirectory.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(currentVersion));
            dexDirectory = new File(patchVersionDirectory, ShareConstants.DEX_PATH);
            libraryDirectory = new File(patchVersionDirectory, ShareConstants.SO_PATH);
            resourceDirectory = new File(patchVersionDirectory, ShareConstants.RES_PATH);
            resourceFile = new File(resourceDirectory, ShareConstants.RES_NAME);
        }
        final boolean isProtectedApp = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_IS_PROTECTED_APP, false);
        patchInfo = new SharePatchInfo(oldVersion, newVersion, isProtectedApp, false, Build.FINGERPRINT, oatDir, false);
        versionChanged = !(oldVersion.equals(newVersion));
    }

    //found uncaught exception, just return
    Throwable exception = ShareIntentUtil.getIntentPatchException(intentResult);
    if (exception != null) {
        TinkerLog.i(TAG, "Tinker load have exception loadCode:%d", loadCode);
        int errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN;
        switch (loadCode) {
            case ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN;
                break;
            case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_DEX;
                break;
            case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE;
                break;
            case ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION:
                errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT;
                break;
            default:
                break;
        }
        tinker.getLoadReporter().onLoadException(exception, errorCode);
        return false;
    }

    switch (loadCode) {
        case ShareConstants.ERROR_LOAD_GET_INTENT_FAIL:
            TinkerLog.e(TAG, "can't get the right intent return code");
            throw new TinkerRuntimeException("can't get the right intent return code");
        case ShareConstants.ERROR_LOAD_DISABLE:
            TinkerLog.w(TAG, "tinker is disable, just return");
            break;
        // case ShareConstants.ERROR_LOAD_PATCH_NOT_SUPPORTED:
        //     TinkerLog.w(TAG, "tinker is not supported, just return");
        //     break;
        case ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST:
        case ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST:
            TinkerLog.w(TAG, "can't find patch file, is ok, just return");
            break;

        case ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED:
            TinkerLog.e(TAG, "path info corrupted");
            tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
            break;

        case ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK:
            TinkerLog.e(TAG, "path info blank, wait main process to restart");
            break;

        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST:
            TinkerLog.e(TAG, "patch version directory not found, current version:%s", currentVersion);
            tinker.getLoadReporter().onLoadFileNotFound(patchVersionDirectory,
                ShareConstants.TYPE_PATCH_FILE, true);
            break;

        case ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST:
            TinkerLog.e(TAG, "patch version file not found, current version:%s", currentVersion);
            if (patchVersionFile == null) {
                throw new TinkerRuntimeException("error load patch version file not exist, but file is null");
            }
            tinker.getLoadReporter().onLoadFileNotFound(patchVersionFile,
                ShareConstants.TYPE_PATCH_FILE, false);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL:
            TinkerLog.i(TAG, "patch package check fail");
            if (patchVersionFile == null) {
                throw new TinkerRuntimeException("error patch package check fail , but file is null");
            }
            int errorCode = intentResult.getIntExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_LOAD_GET_INTENT_FAIL);
            tinker.getLoadReporter().onLoadPackageCheckFail(patchVersionFile, errorCode);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST:
            if (dexDirectory != null) {
                TinkerLog.e(TAG, "patch dex file directory not found:%s", dexDirectory.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(dexDirectory,
                    ShareConstants.TYPE_DEX, true);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch dex file directory not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch dex file directory not found, warning why the path is null!!!!");
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST:
            String dexPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH);
            if (dexPath != null) {
                //we only pass one missing file
                TinkerLog.e(TAG, "patch dex file not found:%s", dexPath);
                tinker.getLoadReporter().onLoadFileNotFound(new File(dexPath),
                    ShareConstants.TYPE_DEX, false);

            } else {
                TinkerLog.e(TAG, "patch dex file not found, but path is null!!!!");
                throw new TinkerRuntimeException("patch dex file not found, but path is null!!!!");
                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_DEX, false);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST:
            String dexOptPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH);
            if (dexOptPath != null) {
                //we only pass one missing file
                TinkerLog.e(TAG, "patch dex opt file not found:%s", dexOptPath);
                tinker.getLoadReporter().onLoadFileNotFound(new File(dexOptPath),
                    ShareConstants.TYPE_DEX_OPT, false);

            } else {
                TinkerLog.e(TAG, "patch dex opt file not found, but path is null!!!!");
                throw new TinkerRuntimeException("patch dex opt file not found, but path is null!!!!");
                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_DEX, false);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_DIRECTORY_NOT_EXIST:
            if (patchVersionDirectory != null) {
                TinkerLog.e(TAG, "patch lib file directory not found:%s", libraryDirectory.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(libraryDirectory,
                    ShareConstants.TYPE_LIBRARY, true);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch lib file directory not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch lib file directory not found, warning why the path is null!!!!");

                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_LIBRARY, true);
            }

            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_LIB_FILE_NOT_EXIST:
            String libPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISSING_LIB_PATH);
            if (libPath != null) {
                //we only pass one missing file and then we break
                TinkerLog.e(TAG, "patch lib file not found:%s", libPath);
                tinker.getLoadReporter().onLoadFileNotFound(new File(libPath),
                    ShareConstants.TYPE_LIBRARY, false);
            } else {
                TinkerLog.e(TAG, "patch lib file not found, but path is null!!!!");
                throw new TinkerRuntimeException("patch lib file not found, but path is null!!!!");
                // tinker.getLoadReporter().onLoadFileNotFound(null,
                //     ShareConstants.TYPE_LIBRARY, false);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL:
            TinkerLog.e(TAG, "patch dex load fail, classloader is null");
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH:
            String mismatchPath = ShareIntentUtil.getStringExtra(intentResult,
                ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH);
            if (mismatchPath == null) {
                TinkerLog.e(TAG, "patch dex file md5 is mismatch, but path is null!!!!");
                throw new TinkerRuntimeException("patch dex file md5 is mismatch, but path is null!!!!");
            } else {
                TinkerLog.e(TAG, "patch dex file md5 is mismatch: %s", mismatchPath);
                tinker.getLoadReporter().onLoadFileMd5Mismatch(new File(mismatchPath),
                    ShareConstants.TYPE_DEX);
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL:
            TinkerLog.i(TAG, "rewrite patch info file corrupted");
            tinker.getLoadReporter().onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_DIRECTORY_NOT_EXIST:
            if (patchVersionDirectory != null) {
                TinkerLog.e(TAG, "patch resource file directory not found:%s", resourceDirectory.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(resourceDirectory,
                    ShareConstants.TYPE_RESOURCE, true);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch resource file directory not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch resource file directory not found, warning why the path is null!!!!");
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_FILE_NOT_EXIST:
            if (patchVersionDirectory != null) {
                TinkerLog.e(TAG, "patch resource file not found:%s", resourceFile.getAbsolutePath());
                tinker.getLoadReporter().onLoadFileNotFound(resourceFile,
                    ShareConstants.TYPE_RESOURCE, false);
            } else {
                //should be not here
                TinkerLog.e(TAG, "patch resource file not found, warning why the path is null!!!!");
                throw new TinkerRuntimeException("patch resource file not found, warning why the path is null!!!!");
            }
            break;
        case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH:
            if (resourceFile == null) {
                TinkerLog.e(TAG, "resource file md5 mismatch, but patch resource file not found!");
                throw new TinkerRuntimeException("resource file md5 mismatch, but patch resource file not found!");
            }
            TinkerLog.e(TAG, "patch resource file md5 is mismatch: %s", resourceFile.getAbsolutePath());

            tinker.getLoadReporter().onLoadFileMd5Mismatch(resourceFile,
                ShareConstants.TYPE_RESOURCE);
            break;
        case ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION:
            tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult));
            break;
        case ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION:
            tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_COMMAND_ERROR, ShareIntentUtil.getIntentInterpretException(intentResult));
            break;
        case ShareConstants.ERROR_LOAD_OK:
            TinkerLog.i(TAG, "oh yeah, tinker load all success");
            tinker.setTinkerLoaded(true);
            // get load dex
            dexes = ShareIntentUtil.getIntentPatchDexPaths(intentResult);
            libs = ShareIntentUtil.getIntentPatchLibsPaths(intentResult);

            packageConfig = ShareIntentUtil.getIntentPackageConfig(intentResult);

            if (useInterpretMode) {
                tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_OK, null);
            }
            if (isMainProcess && versionChanged) {
                //change the old version to new
                tinker.getLoadReporter().onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectory, patchVersionDirectory.getName());
            }
            return true;
        default:
            break;
    }
    return false;
}

這里intentResult是在TinkerLoader.tryLoadPatchFilesInternal過程中put進(jìn)去的執(zhí)行結(jié)果
例如:

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
    final int tinkerFlag = app.getTinkerFlags();
    //確保tinker enable 且非patch進(jìn)程
    if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {
        Log.w(TAG, "tryLoadPatchFiles: tinker is disable, just return");
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
        return;
    }
    if (ShareTinkerInternals.isInPatchProcess(app)) {
        Log.w(TAG, "tryLoadPatchFiles: we don't load patch with :patch process itself, just return");
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
        return;
    }
...
}

這里通過ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);設(shè)置了對應(yīng)的執(zhí)行結(jié)果。

熱修復(fù)加載失敗姨谷,定位問題就看這個parseTinkerResult結(jié)果贰逾。

舉例Demo中遇到的問題:

I/Tinker.TinkerLoadResult: parseTinkerResult loadCode:-3, process name:com.stan.tinkersdkdemo, main process:true, systemOTA:false, fingerPrint:Xiaomi/dipper/dipper:9/PKQ1.180729.001/9.10.122:user/test-keys, oatDir:null, useInterpretMode:false

loadCode -3 :對應(yīng)ERROR_LOAD_PATCH_INFO_NOT_EXIST
看看是什么原因set的

if (!patchInfoFile.exists()) {
    Log.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath());
    ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST);
    return;
}

patch.info不存在, adb查看下果然是,patch.info生成是在合成patch的地方菠秒,debug過去疙剑,最終問題是對應(yīng)patch合成的service沒有在manifest注冊,原因是我打的是jar而非aar,所以
需要向manifest注冊下幾個service,問題解決移袍。

完了沮协。

這個過程非常簡單,總結(jié)這個過程干了兩件事:

  • Tinker install過程就是初始化過程缓熟,初始化一些report類和監(jiān)聽,以及完成熱修復(fù)相關(guān)功能的service。一切都是為后續(xù)主動觸發(fā)patch包合成做準(zhǔn)備僻孝。
  • 通過parseTinkerResult解析TinkerApplication啟動過程中加載合成補(bǔ)丁包的結(jié)果导帝,并通過onLoadResult反饋結(jié)果。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載穿铆,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者您单。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市荞雏,隨后出現(xiàn)的幾起案子虐秦,更是在濱河造成了極大的恐慌,老刑警劉巖凤优,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悦陋,死亡現(xiàn)場離奇詭異,居然都是意外死亡筑辨,警方通過查閱死者的電腦和手機(jī)俺驶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棍辕,“玉大人痒钝,你說我怎么就攤上這事×《荆” “怎么了送矩?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哪替。 經(jīng)常有香客問我栋荸,道長,這世上最難降的妖魔是什么凭舶? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任晌块,我火速辦了婚禮,結(jié)果婚禮上帅霜,老公的妹妹穿的比我還像新娘匆背。我一直安慰自己,他們只是感情好身冀,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布钝尸。 她就那樣靜靜地躺著,像睡著了一般搂根。 火紅的嫁衣襯著肌膚如雪珍促。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天剩愧,我揣著相機(jī)與錄音猪叙,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛穴翩,可吹牛的內(nèi)容都是我干的犬第。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼芒帕,長吁一口氣:“原來是場噩夢啊……” “哼歉嗓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起副签,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎基矮,沒想到半個月后淆储,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡家浇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年本砰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钢悲。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡点额,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出莺琳,到底是詐尸還是另有隱情还棱,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布惭等,位于F島的核電站珍手,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辞做。R本人自食惡果不足惜琳要,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秤茅。 院中可真熱鬧稚补,春花似錦、人聲如沸框喳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽五垮。三九已至撰豺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拼余,已是汗流浹背污桦。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留匙监,地道東北人凡橱。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓小作,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稼钩。 傳聞我的和親對象是個殘疾皇子顾稀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353