微信Tinker熱修復(fù)接入及操作流程

微信Tinker熱修復(fù)接入及操作流程

阿里百川HotFix斗忌,RocooFix,及Tinker兔院,多方對(duì)比,選擇Tinker

接入流程

  1. 先配置遠(yuǎn)程依賴谆吴,(目前最新的版本是1.7.5)
    Project的Gradle中添加:
        dependencies {
            classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.5')
        }

Module的Gradle中添加:

    dependencies {
    //optional, help to generate the final application
    provided('com.tencent.tinker:tinker-android-anno:1.7.5')
    //tinker's main Android lib
    compile('com.tencent.tinker:tinker-android-lib:1.7.5')
    }

接下來(lái)還需要在其中添加一些Tinke插件:


    /**Tinker 相關(guān)配置----------------開始-----------------------------------*/
        def bakPath = file("${buildDir}/bakApk/")
        def gitSha() {
        try {
        String gitRev = '11.2.3.4'
        if (gitRev == null) {
            throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
        }
        return gitRev
        } catch (Exception e) {
            throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
            }
        }
        ext {
            //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
            //Tinker是否可用
        tinkerEnabled = false
        //for normal build
        //old apk file to build patch apk 舊包名
        tinkerOldApkPath = "${bakPath}/tinker_demo-debug-1121-17-43-24.apk"
        //proguard mapping file to build patch apk  舊包混淆文件目錄
        tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
        //resource R.txt to build patch apk, must input if there is resource changed 舊包R文件
        tinkerApplyResourcePath = "${bakPath}/tinker_demo-debug-1121-17-43-24-R.txt"
        //only use for build all flavor, if not, just ignore this field  多渠道
        tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
        }
        def getOldApkPath() {
            return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
        }
        def getApplyMappingPath() {
            return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
        }
        def getApplyResourceMappingPath() {
            return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
        }
        def getTinkerIdValue() {
            return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
        }
        def buildWithTinker() {
            return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
        }
        def getTinkerBuildFlavorDirectory() {
            return ext.tinkerBuildFlavorDirectory
        }
        if (buildWithTinker()) {
            apply plugin: 'com.tencent.tinker.patch'
            tinkerPatch {
                /**
                 * 基準(zhǔn)apk包路徑倒源,也就是舊包路徑
                 * */
                oldApk = getOldApkPath()
                /**
                 * 如果出現(xiàn)以下的情況,并且ignoreWarning為false句狼,我們將中斷編譯笋熬。因?yàn)檫@些情況可能會(huì)導(dǎo)致編譯出來(lái)的patch包
                 * 帶來(lái)風(fēng)險(xiǎn):
                 * 1. minSdkVersion小于14,但是dexMode的值為"raw";
                 * 2. 新編譯的安裝包出現(xiàn)新增的四大組件(Activity, BroadcastReceiver...)腻菇;
                 * 3. 定義在dex.loader用于加載補(bǔ)丁的類不在main dex中;
                 * 4. 定義在dex.loader用于加載補(bǔ)丁的類出現(xiàn)修改胳螟;
                 * 5. resources.arsc改變,但沒有使用applyResourceMapping編譯筹吐。
                 * */
                ignoreWarning = false
                /**
                 * 在運(yùn)行過(guò)程中糖耸,我們需要驗(yàn)證基準(zhǔn)apk包與補(bǔ)丁包的簽名是否一致,我們是否需要為你簽名
                 * */
                useSign = true

        buildConfig {
            /**
             * 可選參數(shù)丘薛;在編譯新的apk時(shí)候嘉竟,我們希望通過(guò)保持舊apk的proguard混淆方式,從而減少補(bǔ)丁包的大小洋侨。這個(gè)只
             * 是推薦的舍扰,但設(shè)置applyMapping會(huì)影響任何的assemble編譯。
             * */
            applyMapping = getApplyMappingPath()
            /**
             * 可選參數(shù)希坚;在編譯新的apk時(shí)候边苹,我們希望通過(guò)舊apk的R.txt文件保持ResId的分配,這樣不僅可以減少補(bǔ)丁包的
             * 大小裁僧,同時(shí)也避免由于ResId改變導(dǎo)致remote view異常勾给。
             * */
            applyResourceMapping = getApplyResourceMappingPath()
            /**
             * 在運(yùn)行過(guò)程中,我們需要驗(yàn)證基準(zhǔn)apk包的tinkerId是否等于補(bǔ)丁包的tinkerId锅知。這個(gè)是決定補(bǔ)丁包能運(yùn)行在哪些
             * 基準(zhǔn)包上面,一般來(lái)說(shuō)我們可以使用git版本號(hào)脓钾、versionName等等售睹。
             * */
            tinkerId = getTinkerIdValue()
        }
        dex {
            /**
             * 只能是'raw'或者'jar'。
             * 對(duì)于'raw'模式可训,我們將會(huì)保持輸入dex的格式昌妹。
             * 對(duì)于'jar'模式,我們將會(huì)把輸入dex重新壓縮封裝到j(luò)ar握截。如果你的minSdkVersion小于14飞崖,你必須選擇‘jar’模式
             * ,而且它更省存儲(chǔ)空間谨胞,但是驗(yàn)證md5時(shí)比'raw'模式耗時(shí)()
             * */
            dexMode = "jar"
            /**
             * 是否提前生成dex固歪,而非合成的方式。這套方案即回退成Qzone的方案,對(duì)于需要使用加固或者多flavor打包(建
             * 議使用其他方式生成渠道包)的用戶可使用牢裳。但是這套方案需要插樁逢防,會(huì)造成Dalvik下性能損耗以及Art補(bǔ)丁包可
             * 能過(guò)大的問(wèn)題,務(wù)必謹(jǐn)慎使用蒲讯。另外一方面忘朝,這種方案在Android N之后可能會(huì)產(chǎn)生問(wèn)題,建議過(guò)濾N之后的用戶判帮。
             */
            usePreGeneratedPatchDex = false
            /**
             * 需要處理dex路徑局嘁,支持*、?通配符晦墙,必須使用'/'分割悦昵。路徑是相對(duì)安裝包的,例如/assets/...
             */
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            /**
             *     這一項(xiàng)非常重要偎痛,它定義了哪些類在加載補(bǔ)丁包的時(shí)候會(huì)用到旱捧。這些類是通過(guò)Tinker無(wú)法修改的類,也是一定要放在main dex的類踩麦。
             這里需要定義的類有:
             1. 你自己定義的Application類枚赡;
             2. Tinker庫(kù)中用于加載補(bǔ)丁包的部分類,即com.tencent.tinker.loader.*谓谦;
             3. 如果你自定義了TinkerLoader贫橙,需要將它以及它引用的所有類也加入loader中;
             4. 其他一些你不希望被更改的類反粥,例如Sample中的BaseBuildInfo類卢肃。這里需要注意的是,這些類的直接引用類也
             需要加入到loader中才顿∧妫或者你需要將這個(gè)類變成非preverify。
             */
            loader = ["com.tencent.tinker.loader.*",
                      //warning, you must change it with your application
                    //TODO 換成自己的Application
                      "com.material.hbl.tinker_demo.MyApplication",
            ]
        }
        lib {
            /**
             * 需要處理lib路徑郑气,支持*幅垮、?通配符,必須使用'/'分割尾组。與dex.pattern一致, 路徑是相對(duì)安裝包的忙芒,例如/assets/...
             */
            pattern = ["lib/armeabi/*.so"]
        }
        res {
            /**
             * 需要處理res路徑,支持*讳侨、?通配符呵萨,必須使用'/'分割。與dex.pattern一致, 路徑是相對(duì)安裝包的跨跨,例如/assets/...潮峦,
             * 務(wù)必注意的是,只有滿足pattern的資源才會(huì)放到合成后的資源包。
             */
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
            /**
             * 支持*跑杭、?通配符铆帽,必須使用'/'分割。若滿足ignoreChange的pattern德谅,在編譯時(shí)會(huì)忽略該文件的新增爹橱、刪除與修改。
             * 最極端的情況窄做,ignoreChange與上面的pattern一致愧驱,即會(huì)完全忽略所有資源的修改。
             */
            ignoreChange = ["assets/sample_meta.txt"]
            /**
             * 對(duì)于修改的資源椭盏,如果大于largeModSize组砚,我們將使用bsdiff算法。這可以降低補(bǔ)丁包的大小掏颊,但是會(huì)增加合成
             * 時(shí)的復(fù)雜度糟红。默認(rèn)大小為100kb
             */
            largeModSize = 100
        }
        packageConfig {
            /**
             * configField("key", "value"), 默認(rèn)我們自動(dòng)從基準(zhǔn)安裝包與新安裝包的Manifest中讀取tinkerId,并自動(dòng)
             * 寫入configField。在這里乌叶,你可以定義其他的信息盆偿,在運(yùn)行時(shí)可以通過(guò)TinkerLoadResult.getPackageConfigByName得到相應(yīng)的數(shù)值。但是建議直接通過(guò)修改代碼來(lái)實(shí)現(xiàn)准浴,例如BuildConfig事扭。
             */
            configField("patchMessage", "tinker is sample to use")
        }
        sevenZip {
            /**
             * 例如"com.tencent.mm:SevenZip:1.1.10",將自動(dòng)根據(jù)機(jī)器屬性獲得對(duì)應(yīng)的7za運(yùn)行文件乐横,推薦使用
             */
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
        }
        /**
         *  文件名                              描述
         *  patch_unsigned.apk                  沒有簽名的補(bǔ)丁包
         *  patch_signed.apk                  簽名后的補(bǔ)丁包
         *  patch_signed_7zip.apk              簽名后并使用7zip壓縮的補(bǔ)丁包求橄,也是我們通常使用的補(bǔ)丁包。但正式發(fā)布的時(shí)候葡公,最好不要以.apk結(jié)尾罐农,防止被運(yùn)營(yíng)商挾持。
         *  log.txt                              在編譯補(bǔ)丁包過(guò)程的控制臺(tái)日志
         *  dex_log.txt                          在編譯補(bǔ)丁包過(guò)程關(guān)于dex的日志
         *  so_log.txt                          在編譯補(bǔ)丁包過(guò)程關(guān)于lib的日志
         *  tinker_result                      最終在補(bǔ)丁包的內(nèi)容催什,包括diff的dex啃匿、lib以及assets下面的meta文件
         *  resources_out.zip                  最終在手機(jī)上合成的全量資源apk,你可以在這里查看是否有文件遺漏
         *  resources_out_7z.zip              根據(jù)7zip最終在手機(jī)上合成的全量資源apk
         *  tempPatchedDexes                  在Dalvik與Art平臺(tái)蛆楞,最終在手機(jī)上合成的完整Dex,我們可以在這里查看dex合成的產(chǎn)物夹厌。
         *
         *
         * */

        /**
         * 獲得所有渠道集合豹爹,并判斷數(shù)量
         */
        List<String> flavors = new ArrayList<>();
        project.android.productFlavors.each {flavor ->
            flavors.add(flavor.name)
        }
        boolean hasFlavors = flavors.size() > 0
        /**
         * bak apk and mapping
         *  創(chuàng)建Task并執(zhí)行文件操作
         */
        android.applicationVariants.all { variant ->
            /**
             * task type, you want to bak
             */
            def taskName = variant.name
            def date = new Date().format("MMdd-HH-mm-ss")

            tasks.all {
                if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {

                    it.doLast {
                        copy {
                            def fileNamePrefix = "${project.name}-${variant.baseName}"
                            def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"

                            def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
                            from variant.outputs.outputFile
                            into destPath
                            rename { String fileName ->
                                fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                            }

                            from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
                            into destPath
                            rename { String fileName ->
                                fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
                            }

                            from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                            into destPath
                            rename { String fileName ->
                                fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                            }
                        }
                    }
                }
            }
        }
        /**
         * 如果有渠道則進(jìn)行多渠道打包
         */
        project.afterEvaluate {
            //sample use for build all flavor for one time
            if (hasFlavors) {
                task(tinkerPatchAllFlavorRelease) {
                    group = 'tinker'
                    def originOldPath = getTinkerBuildFlavorDirectory()
                    for (String flavor : flavors) {
                        def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
                        dependsOn tinkerTask
                        def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
                        preAssembleTask.doFirst {
                            String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
                            project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
                            project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
                            project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"

                        }

                    }
                }

                task(tinkerPatchAllFlavorDebug) {
                    group = 'tinker'
                    def originOldPath = getTinkerBuildFlavorDirectory()
                    for (String flavor : flavors) {
                        def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
                        dependsOn tinkerTask
                        def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
                        preAssembleTask.doFirst {
                            String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
                            project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
                            project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
                            project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
                        }

                    }
                }
            }
        }
        }
        }
        /**Tinker 相關(guān)配置----------------結(jié)束-----------------------------------*/

代碼很多,在每一個(gè)的配置上面都有相應(yīng)的注釋矛纹,比較關(guān)注的事ext中的配置臂聋,和Loader中的配置。

  1. 創(chuàng)建一個(gè)SampleApplication類,繼承DefaultApplicationLike:
    @DefaultLifeCycle(application = "com.material.hbl.tinkerdemo.MyApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
        public class SampleApplicarion extends DefaultApplicationLike{
            public SampleApplicarion(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) {
                super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, resources, classLoader, assetManager);
            }
            @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
            @Override
            public void onBaseContextAttached(Context base) {
                super.onBaseContextAttached(base);
        /************************Action*************************/
                TinkerInstaller.install(this);
        /************************Action*************************/
            }
            @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
            public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
                getApplication().registerActivityLifecycleCallbacks(callback);
            }
            }

其中DefaultLifeCycle中的Application為我們真正的Application孩等,flags表示需要支持的修復(fù)的范圍艾君,然后將AndroidManifest.xml文件中的Application的name改為我們的Application,添加讀寫磁盤的權(quán)限肄方。重點(diǎn)只有標(biāo)示的那一句冰垄,初始化Tinker。

  1. 將上面的Gradle配置中的loader中的Application改為之前我們的Application权她。至此接入工作基本配置完成虹茶。

操作流程

  1. 在將Tinker成功接入以后,可以寫一個(gè)簡(jiǎn)單的例子來(lái)檢測(cè)一下是否可以使用隅要。在MainActivity中創(chuàng)建四個(gè)按鈕蝴罪,分別對(duì)應(yīng)Load,Show,Bug,F(xiàn)ix
    ,其中l(wèi)oad對(duì)應(yīng)加載補(bǔ)丁步清,show會(huì)顯示一些加載的信息要门,Bug為簡(jiǎn)單創(chuàng)建一個(gè)bug,Fix為修復(fù)此bug.布局就不貼了,直接看代碼:
        public class MainActivity extends AppCompatActivity {
        String TAG="MainActivity";
        TextView tv;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Log.e(TAG, "i am on onCreate string:" );
        //        Log.e(TAG, "i am on patch onCreate");
                tv= (TextView) findViewById(R.id.title);
                findViewById(R.id.btn_load).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //加載補(bǔ)丁的方法
                        TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
                        Log.d(TAG, "onClick: load");
                    }
                });
                findViewById(R.id.btn_show).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        showInfo(MainActivity.this);
                    }
                });
                findViewById(R.id.btn_bug).setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Log.d(TAG, "onClick: btn_bug");
                        tv.setText("出現(xiàn)BUG");
                    }
                });
        //        findViewById(R.id.btn_fix).setOnClickListener(new View.OnClickListener() {
        //            @Override
        //            public void onClick(View v) {
        //                Log.d(TAG, "onClick: btn_fix");
        //                tv.setText("修復(fù)BUG");
        //            }
        //        });
        }

showInfo使用的是官方的Demo中的方法廓啊,不過(guò)多介紹了欢搜,也可以簡(jiǎn)單的使用:

         Tinker tinker = Tinker.with(getApplicationContext());
         boolean isLoadSuccess = tinker.isTinkerLoaded();

方法來(lái)判斷是否補(bǔ)丁加載成功。

  1. 將Gradle中ext中的tinkerEnabled置為true崖瞭,然后點(diǎn)擊studio頂部的Build中Build APk狂巢,打一個(gè)DeBug包,成功之后會(huì)在Module的build目錄下生成一個(gè)文件夾书聚,如圖:

因?yàn)槭荄ebug包所以只會(huì)有兩個(gè)文件唧领,一個(gè)是APK,一個(gè)是對(duì)應(yīng)的R文件雌续。如果有使用混淆的話斩个,還會(huì)有一個(gè)mapping的混淆映射文件。
我們把outputs/apk/中的apk文件拷出去驯杜,并安裝到自己的手機(jī)上受啥,運(yùn)行。

  1. 將Gradle中ext中的tinkerOldApkPath后的路徑設(shè)置為上一步中bakApk中的apk名稱鸽心,同理把tinkerApplyResourcePath設(shè)置為R文件的名稱滚局。其他的不用管。
  2. 修改MainActivity中代碼顽频,將注釋解開藤肢,并將 Log.e(TAG, "i am on onCreate string:" );注釋掉。
  3. 點(diǎn)擊Studio右側(cè)的Gradle工具欄糯景,展開之后找到自己的Module找到tinker,雙擊其中的tinkerPathDeBug,運(yùn)行之后會(huì)在


outputs下多出一個(gè)目錄嘁圈,都是一些編譯打差異包過(guò)程的log和相關(guān)生成的apk省骂,選擇其中的patch_signed_7zip.apk,拷出來(lái)最住,放在手機(jī)根目錄下钞澳。

  1. 重新啟動(dòng)app,點(diǎn)擊load涨缚,之后點(diǎn)擊show轧粟,會(huì)提示patch load success,重啟apk仗岖,點(diǎn)擊fix就會(huì)發(fā)現(xiàn)逃延,bug已經(jīng)被修復(fù)了。

遇到的問(wèn)題

  1. 在執(zhí)行tinkerPatchDebug的時(shí)候失敗轧拄,提示其他Module中的ext中文件不存在揽祥,將其他module中的tinkerenable關(guān)閉。

待優(yōu)化

  1. tinkerId 目前是寫死的
  2. Application 其他初始化的代碼檩电。

文章持續(xù)更新拄丰,不斷會(huì)更新一些在實(shí)際使用中遇到的問(wèn)題和解決辦法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俐末,一起剝皮案震驚了整個(gè)濱河市料按,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卓箫,老刑警劉巖载矿,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異烹卒,居然都是意外死亡闷盔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門旅急,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逢勾,“玉大人币励,你說(shuō)我怎么就攤上這事崩溪∶飧猓” “怎么了晕讲?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)兆沙。 經(jīng)常有香客問(wèn)我轴踱,道長(zhǎng)汤踏,這世上最難降的妖魔是什么泥从? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任句占,我火速辦了婚禮,結(jié)果婚禮上歉闰,老公的妹妹穿的比我還像新娘辖众。我一直安慰自己,他們只是感情好和敬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布凹炸。 她就那樣靜靜地躺著,像睡著了一般昼弟。 火紅的嫁衣襯著肌膚如雪啤它。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天舱痘,我揣著相機(jī)與錄音变骡,去河邊找鬼。 笑死芭逝,一個(gè)胖子當(dāng)著我的面吹牛塌碌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播旬盯,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼台妆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了胖翰?” 一聲冷哼從身側(cè)響起接剩,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萨咳,沒想到半個(gè)月后懊缺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡培他,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年鹃两,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靶壮。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怔毛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腾降,到底是詐尸還是另有隱情拣度,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布螃壤,位于F島的核電站抗果,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏奸晴。R本人自食惡果不足惜冤馏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寄啼。 院中可真熱鬧逮光,春花似錦代箭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至杜漠,卻和暖如春极景,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驾茴。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工盼樟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锈至。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓晨缴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親裹赴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喜庞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容