細(xì)數(shù)Tinker接入的那些坑

替換Application

按照TInker官方文檔,接入Tinker Patch需要把原來(lái)項(xiàng)目中Application的代碼移動(dòng)到ApplicationLike中开仰,然而這可不是件小事情刀崖,我們的application可能包含各種初始化,并且很多地方調(diào)用了application的public方法楞抡。比如


import android.support.multidex.MultiDexApplication;

public class MyApp extends MultiDexApplication {

    private static MyApp sInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
        initNetWork();
        initFresco();
        initStetho();
        initXXX1();
        initXXX2();
        ActivityFetcher.init();
    }


    public String getAccount() {
        return "xxx";
    }

    public String getXXX() {
        return "xxx";
    }

    private void initXXX2() {
    }

    private void initXXX1() {
    }

    private void initNetWork() {
    }

    private void initFresco() {
    }

    private void initStetho() {
    }

    public static MyApp getApplication() {
        return sInstance;
    }
}

調(diào)用MyApp.getApplication()矢炼,注冊(cè)activity監(jiān)聽(tīng)

public class ActivityFetcher {
    private static List<WeakReference<Activity>> sActivities = new ArrayList<>();

    public static void init() {
        MyApp.getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                sActivities.add(new WeakReference<Activity>(activity));
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                for (WeakReference<Activity> reference : sActivities) {
                    if (reference.get() == activity) {
                        sActivities.remove(reference);
                        return;
                    }
                }
            }
        });
    }

    public static List<WeakReference<Activity>> getActivities() {
        return Collections.unmodifiableList(sActivities);
    }
}

再比如需要context的地方直接把 MyApp.getApplication()作為了參數(shù)

Toast.makeText(MyApp.getApplication(), "請(qǐng)求失敗", Toast.LENGTH_SHORT).show();

還有某些地方調(diào)用了application種的各種public方法

String account = MyApp.getApplication().getAccount();
String xxx = MyApp.getApplication().getXXX();

如果把a(bǔ)pplication的代碼都搬到ApplicationLike中的話改動(dòng)量可能會(huì)很大,有沒(méi)有更好的方案呢慷垮?肯定是有的。

首選我們看一下Application的源碼揍堕,發(fā)現(xiàn)并沒(méi)有什么特殊的料身,只不過(guò)是繼承自ContextWrapper,并新增了幾個(gè)public registXXX方法衩茸,所以Application其實(shí)只是一個(gè)代理芹血,真整的context其實(shí)是ContextImpl對(duì)象,Application繼承來(lái)得所有方法其實(shí)最終都是交給了ContextImpl對(duì)象處理楞慈。Application對(duì)象的創(chuàng)建過(guò)程大致如下

相關(guān)代碼如下
LoadedApk#makeApplication

public Application makeApplication(boolean forceDefaultAppClass,
                                       Instrumentation instrumentation) {
        Application app = null;
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
        instrumentation.callApplicationOnCreate(app);    
        return app;
    }

ContextImpl#createAppContext

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
                null);
        context.setResources(packageInfo.getResources());
        return context;
    }

Instrumentation#newApplication

    static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance();
        app.attach(context);
        return app;
    }

Application#attach

final void attach(Context context) {
      attachBaseContext(context);
}

Instrumentation#callApplicationOnCreate

   public void callApplicationOnCreate(Application app) {
        app.onCreate();
    }

可以看到只不過(guò)是先new了一個(gè)ContextImpl對(duì)象幔烛,然后通過(guò)反射創(chuàng)建了一個(gè)Application對(duì)象,并把contextimpl對(duì)象設(shè)置給了Application囊蓝,然后調(diào)用了Application的onCreate等方法饿悬。所以我們完全可以手動(dòng)new這個(gè)MyApp,然后把真正的context attach給MyApp聚霜,然后調(diào)用相關(guān)的方法即可狡恬。

所以我們可以這么做


public class MyApp extends TinkerCtxWrap {

    private static MyApp sInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
        initNetWork();
        initFresco();
        initStetho();
        initXXX1();
        initXXX2();
        ActivityFetcher.init();
    }


    public String getAccount() {
        return "xxx";
    }

    public String getXXX() {
        return "xxx";
    }

    private void initXXX2() {
    }

    private void initXXX1() {
    }

    private void initNetWork() {
    }

    private void initFresco() {
    }

    private void initStetho() {
    }

    public static MyApp getApplication() {
        return sInstance;
    }
}


public class TinkerCtxWrap extends Application {

    @Override
    public void attachBaseContext(Context base) {
        super.attachBaseContext(base);
    }


    @Override
    public void registerComponentCallbacks(ComponentCallbacks callback) {
        Application application = getRealApplication();
        application.registerComponentCallbacks(callback);
    }

    @Override
    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
        Application application = getRealApplication();
        application.unregisterComponentCallbacks(callback);
    }

    public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        Application application = getRealApplication();
        application.registerActivityLifecycleCallbacks(callback);
    }

    public void unregisterActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        Application application = getRealApplication();
        application.unregisterActivityLifecycleCallbacks(callback);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public void registerOnProvideAssistDataListener(Application.OnProvideAssistDataListener callback) {
        Application application = getRealApplication();
        application.registerOnProvideAssistDataListener(callback);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public void unregisterOnProvideAssistDataListener(Application.OnProvideAssistDataListener callback) {
        Application application = getRealApplication();
        application.unregisterOnProvideAssistDataListener(callback);
    }


    protected Application getRealApplication() {
        return (Application) getBaseContext().getApplicationContext();
    }
}




public class ApplicationLike extends DefaultApplicationLike {
    private TinkerCtxWrap ctxWrap;

    public ApplicationLike(Application application, int i, boolean b, long l, long l1, Intent intent) {
        super(application, i, b, l, l1, intent);
    }

    public void onCreate() {
        super.onCreate();
        ctxWrap.onCreate();
    }


    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        MultiDex.install(base);
        ctxWrap = new MyApp();
        ctxWrap.attachBaseContext(base);
    }


    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ctxWrap.onConfigurationChanged(newConfig);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        ctxWrap.onLowMemory();
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        ctxWrap.onTerminate();
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        ctxWrap.onTrimMemory(level);
    }
}



我們只是把MyApp的父類改成了TinkerCtxWrap,TinkerCtxWrap其實(shí)是繼承自Application蝎宇,并把a(bǔ)ttachBaseContext改成了public弟劲,然后新增了幾個(gè)registerXXX等方法。
然后我們?cè)贏pplicationLike的onBaseContextAttached中先調(diào)用Multidex.install加載了所有dex文件姥芥,然后new了MyApp兔乞,并把context attach了進(jìn)去,然后在ApplicationLike的生命周期方法中調(diào)用了MyApp的對(duì)應(yīng)方法凉唐。這樣我們就不需要把原來(lái)MyApp中的代碼搬到ApplicationLike中了庸追,并且MyApp還是繼承自Application,需要application對(duì)象的地方仍舊可以使用MyApp.getApplication()獲取熊榛。不過(guò)要注意锚国,MyApp其實(shí)只是一個(gè)代理了,真正的Application其實(shí)是TinkerApplication玄坦,所以activity等中通過(guò)getApplicationContext得到的context就不能強(qiáng)轉(zhuǎn)成MyApp了血筑。

打包失敗

Too many classes in --main-dex-list, main dex capacity exceeded

用了一年tinker绘沉,最近打包突然一直失敗,不開啟tinker打包可以成功豺总,開啟打包就提示Too many classes in --main-dex-list, main dex capacity exceeded车伞。意思是主dex中類太多了,我們知道Application及引用的類會(huì)被打倒主dex中喻喳,沒(méi)辦法另玖,只能精簡(jiǎn)一下,由于我們的業(yè)務(wù)代碼最開始執(zhí)行的地方是MyApp表伦,所以理論上所有的業(yè)務(wù)代碼都可以不在主dex中谦去,所以我們把ApplicationLike的onBaseContextAttached改成了通過(guò)反射方式加載

 @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        MultiDex.install(base);
        try {
            ctxWrap = (TinkerCtxWrap) Class.forName("xx.xx.MyApp").newInstance();
        } catch (Exception e) {
            throw new RuntimeException("創(chuàng)建MyApp失敗");
        }
        ctxWrap.attachBaseContext(base);
    }

本以為這樣就不會(huì)把MyApp打到主dex中,從而所有的業(yè)務(wù)代碼都不會(huì)打到主dex中蹦哼,不過(guò)試了下還是打包失敗鳄哭。于是我們又改成了這樣,唯一的區(qū)別就是new了一個(gè)String纲熏,同時(shí)我們還把stetho等線上包用不到的庫(kù)去掉妆丘,終于打包成功了。


    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        MultiDex.install(base);
        try {
            ctxWrap = (TinkerCtxWrap) Class.forName(new String("xx.xx.MyApp")).newInstance();
        } catch (Exception e) {
            throw new RuntimeException("創(chuàng)建MyApp失敗");
        }
        ctxWrap.attachBaseContext(base);
    }

然后我們看了下編譯時(shí)生成的maindexlist文件局劲,里面列出了將近8000條勺拣!不開啟tinker時(shí)這個(gè)文件中只有不到1000條,經(jīng)過(guò)多次嘗試鱼填,發(fā)現(xiàn)8000大概是上限药有,這就意味著我們下次發(fā)版時(shí)可能又無(wú)法打包了。

反編譯了下勉強(qiáng)打包成功的apk包剔氏,發(fā)現(xiàn)主dex中有好多kotlin的類塑猖,貌似所有打了@SerializedName,@Deprecated等注解的類也都打到了主dex中谈跛,另外kotlin類上也打了一個(gè)@Metadata注解羊苟,這些注解有個(gè)共同特點(diǎn),都是RUNTIME的感憾,比如

package com.google.gson.annotations; 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface SerializedName {
    String value();
    String[] alternate() default {};
}

懷疑所有打了這種運(yùn)行時(shí)注解的類都會(huì)打到主dex中蜡励,于是下載了 tinker-patch-sample工程,通過(guò)gradle命令生成了2000個(gè)類阻桅,每個(gè)中30個(gè)方法凉倚,所有類上都打上 @Anno這個(gè)注解,@Anno這個(gè)注解是我們自定義的嫂沉,定義如下

@Retention(RUNTIME)
@Target({TYPE})
public @interface Anno {
}

這時(shí)開起tinker打release包時(shí)問(wèn)題復(fù)現(xiàn)了稽寒!一直打包失敗,提示 Too many classes in --main-dex-list, main dex capacity exceeded 趟章,當(dāng)我們把Anno上的RUNTIME改成CLASS時(shí)杏糙,即

@Retention(CLASS)
@Target({TYPE})
public @interface Anno {
}

這時(shí)就可以打包成功慎王!所以問(wèn)題最終出在了tinker上,tinker會(huì)把所有打了RUNTIME注解的類打到主dex中宏侍,tinker官方一直沒(méi)有解決方案赖淤。沒(méi)辦法,我們只能自己解決

分包

當(dāng)然你可以把最低兼容版本改成api21谅河,這時(shí)打包就沒(méi)問(wèn)了咱旱,不過(guò)如果不想丟棄5.0以下用戶的話只能分包解決了。
gradle2.x可以使用dex-knife分包绷耍,不過(guò)我們是3.1.4吐限,dex-knife分包可能存在兼容問(wèn)題,gradle3.1.4打包時(shí)會(huì)生成一個(gè)maindexlist文件褂始,這個(gè)文件決定了主dex中的類毯盈,我們只需要在生成這個(gè)文件后把不需要打包到主dex的類移除掉就可以了。maindexlist文件會(huì)在執(zhí)行transformClassesWithMultidexlistForXXX task時(shí)生成病袄,我們只需要在這個(gè)task執(zhí)行后把maindexlist的文件內(nèi)容換掉即可。

我們?cè)赼pp module下創(chuàng)建了一個(gè)main_dex_split_for_tinker.gradle文件赘阀,里面代碼如下

project.afterEvaluate {
    //解決開啟tinker時(shí)打包失敗問(wèn)題  Too many classes in --main-dex-list, main dex capacity exceeded益缠。 exclude_class.txt中配置排除的類
    //一定要驗(yàn)證5.0以下android啟動(dòng)時(shí)是否崩潰!
    if (android.defaultConfig.minSdkVersion.getApiLevel() >= 21) {
        return
    }
    if (project.hasProperty("tinkerPatch") == false) {
        return
    }
    def configuration = project.tinkerPatch

    if (!configuration.tinkerEnable) {
        return
    }

    android.applicationVariants.all { variant ->

        def variantName = variant.name.capitalize()

        def multidexTask = project.tasks.findByName("transformClassesWithMultidexlistFor${variantName}")
        if (multidexTask != null) {
            def splitTask = createSplitDexTask(variant);
            multidexTask.finalizedBy splitTask
        }
    }


}


def createSplitDexTask(variant) {
    def variantName = variant.name.capitalize()

    return task("replace${variantName}MainDexClassList").doLast {

        //從主dex移除的列表
        def excludeClassList = []
        File excludeClassFile = new File("${project.projectDir}/exclude_class.txt")
        if (excludeClassFile.exists()) {
            excludeClassFile.eachLine { line ->
                if (!line.trim().isEmpty() && line.startsWith("#") == false) {
                    excludeClassList.add(line.trim())
                }
            }
            excludeClassList.unique()
        }

        def mainDexList = []
        File mainDexFile = new File("${project.buildDir}/intermediates/multi-dex/${variant.dirName}/maindexlist.txt")
        println "${project.buildDir}/intermediates/multi-dex/${variant.dirName}/maindexlist.txt exist: ${mainDexFile.exists()}"
        if (mainDexFile.exists()) {
            mainDexFile.eachLine { line ->
                if (!line.isEmpty()) {
                    mainDexList.add(line.trim())
                }
            }
            mainDexList.unique()
            if (!excludeClassList.isEmpty()) {
                def newMainDexList = mainDexList.findResults { mainDexItem ->
                    def isKeepMainDexItem = true
                    for (excludeClassItem in excludeClassList) {
                        if (mainDexItem.contains(excludeClassItem)) {
                            isKeepMainDexItem = false
                            break
                        }
                    }
                    if (isKeepMainDexItem) mainDexItem else null
                }
                if (newMainDexList.size() < mainDexList.size()) {
                    mainDexFile.delete()
                    mainDexFile.createNewFile()
                    mainDexFile.withWriterAppend { writer ->
                        newMainDexList.each {
                            writer << it << '\n'
                            writer.flush()
                        }
                    }
                }
            }
        }

    }
}


然后再app module下的build.gradle文件中引入上面的文件

apply from: "main_dex_split_for_tinker.gradle"

然后我們?cè)赼pp module下新建exclude_class.txt文件基公,用于配置需要從maindexlist中移除的類幅慌,例如

#類路徑包含如下的,都會(huì)建議打包工具不要打到主dex中轰豆,但可能還會(huì)被打到主dex中胰伍。由于所有的業(yè)務(wù)代碼都會(huì)在multidex.install后執(zhí)行,理論上所有的業(yè)務(wù)代碼都可以不在主dex中
com/facebook/fresco
com/facebook/drawee
com/facebook/imageformat
com/facebook/imagepipeline
com/alibaba
com/taobao
com/eclipsesource/v8

最終我們打包成功了酸休,maindexlist中只剩了3000多個(gè)類骂租,當(dāng)然還可以更少,這樣很長(zhǎng)一段時(shí)間內(nèi)我們不用擔(dān)心打包時(shí)主dex超限了斑司。測(cè)試了下兼容性良好渗饮,5.0以下也沒(méi)有啟動(dòng)崩潰。

最近幾天研究了下build gradle源碼宿刮,發(fā)現(xiàn)根本不需要上面的分包代碼互站,只要配置下就可以了 。默認(rèn)會(huì)把所有打了運(yùn)行時(shí)注解的類全部打到主dex中僵缺,可以通過(guò)如下配置禁止掉

android{
    dexOptions {
        keepRuntimeAnnotatedClasses false
    }
}

相關(guān)源碼如下:


public class MainDexListBuilder {
    private static final String CLASS_EXTENSION = ".class";

    private static final int STATUS_ERROR = 1;

    private static final String EOL = System.getProperty("line.separator");

    private static final String USAGE_MESSAGE =
            "Usage:" + EOL + EOL +
            "Short version: Don't use this." + EOL + EOL +
            "Slightly longer version: This tool is used by mainDexClasses script to build" + EOL +
            "the main dex list." + EOL;

    /**
     By default we force all classes annotated with runtime annotation to be kept in the
      main dex list. This option disable the workaround, limiting the index pressure in the main
      dex but exposing to the Dalvik resolution bug. The resolution bug occurs when accessing
      annotations of a class that is not in the main dex and one of the annotations as an enum
      parameter.
     
     * @see <a >bug discussion</a>
     *
     */
    private static final String DISABLE_ANNOTATION_RESOLUTION_WORKAROUND =
            "--disable-annotation-resolution-workaround";

    private Set<String> filesToKeep = new HashSet<String>();

    public static void main(String[] args) {

        int argIndex = 0;
        boolean keepAnnotated = true;
        while (argIndex < args.length -2) {
            if (args[argIndex].equals(DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) {
                keepAnnotated = false;
            } else {
                System.err.println("Invalid option " + args[argIndex]);
                printUsage();
                System.exit(STATUS_ERROR);
            }
            argIndex++;
        }
        if (args.length - argIndex != 2) {
            printUsage();
            System.exit(STATUS_ERROR);
        }

        try {
            MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex],
                    args[argIndex + 1]);
            Set<String> toKeep = builder.getMainDexList();
            printList(toKeep);
        } catch (IOException e) {
            System.err.println("A fatal error occured: " + e.getMessage());
            System.exit(STATUS_ERROR);
            return;
        }
    }

    public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)
            throws IOException {
        ZipFile jarOfRoots = null;
        Path path = null;
        try {
            try {
                jarOfRoots = new ZipFile(rootJar);
            } catch (IOException e) {
                throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. ("
                        + e.getMessage() + ")", e);
            }
            path = new Path(pathString);

            ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);
            mainListBuilder.addRoots(jarOfRoots);
            for (String className : mainListBuilder.getClassNames()) {
                filesToKeep.add(className + CLASS_EXTENSION);
            }
            if (keepAnnotated) {
                keepAnnotated(path);
            }
        } finally {
            try {
                jarOfRoots.close();
            } catch (IOException e) {
                // ignore
            }
            if (path != null) {
                for (ClassPathElement element : path.elements) {
                    try {
                        element.close();
                    } catch (IOException e) {
                        // keep going, lets do our best.
                    }
                }
            }
        }
    }

    /**
     * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list.
     */
    public Set<String> getMainDexList() {
        return filesToKeep;
    }

    private static void printUsage() {
        System.err.print(USAGE_MESSAGE);
    }

    private static void printList(Set<String> fileNames) {
        for (String fileName : fileNames) {
            System.out.println(fileName);
        }
    }

    /**
     * Keep classes annotated with runtime annotations.
     */
    private void keepAnnotated(Path path) throws FileNotFoundException {
        for (ClassPathElement element : path.getElements()) {
            forClazz:
                for (String name : element.list()) {
                    if (name.endsWith(CLASS_EXTENSION)) {
                        DirectClassFile clazz = path.getClass(name);
                        if (hasRuntimeVisibleAnnotation(clazz)) {
                            filesToKeep.add(name);
                        } else {
                            MethodList methods = clazz.getMethods();
                            for (int i = 0; i<methods.size(); i++) {
                                if (hasRuntimeVisibleAnnotation(methods.get(i))) {
                                    filesToKeep.add(name);
                                    continue forClazz;
                                }
                            }
                            FieldList fields = clazz.getFields();
                            for (int i = 0; i<fields.size(); i++) {
                                if (hasRuntimeVisibleAnnotation(fields.get(i))) {
                                    filesToKeep.add(name);
                                    continue forClazz;
                                }
                            }
                        }
                    }
                }
        }
    }

    private boolean hasRuntimeVisibleAnnotation(HasAttribute element) {
        Attribute att = element.getAttributes().findFirst(
                AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
        return (att != null && ((AttRuntimeVisibleAnnotations)att).getAnnotations().size()>0);
    }
}

相關(guān)注釋

 /**
     * Keep all classes with runtime annotations in the main dex in legacy multidex.
     *
     * <p>This is enabled by default and works around an issue that will cause the app to crash
     * when using java.lang.reflect.Field.getDeclaredAnnotations on older android versions.
     *
     * <p>This can be disabled for for apps that do not use reflection and need more space in their
     * main dex.
     *
     * <p>See <a >http://b.android.com/78144</a>.
     */
    @Override
    public boolean getKeepRuntimeAnnotatedClasses() {
        return keepRuntimeAnnotatedClasses;
    }

    public void setKeepRuntimeAnnotatedClasses(
            boolean keepRuntimeAnnotatedClasses) {
        this.keepRuntimeAnnotatedClasses = keepRuntimeAnnotatedClasses;
    }

關(guān)于

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胡桃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子磕潮,更是在濱河造成了極大的恐慌翠胰,老刑警劉巖容贝,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亡容,居然都是意外死亡嗤疯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門闺兢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)茂缚,“玉大人,你說(shuō)我怎么就攤上這事屋谭〗拍遥” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵桐磁,是天一觀的道長(zhǎng)悔耘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)我擂,這世上最難降的妖魔是什么衬以? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮校摩,結(jié)果婚禮上看峻,老公的妹妹穿的比我還像新娘。我一直安慰自己衙吩,他們只是感情好互妓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坤塞,像睡著了一般冯勉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摹芙,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天灼狰,我揣著相機(jī)與錄音,去河邊找鬼瘫辩。 笑死伏嗜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伐厌。 我是一名探鬼主播承绸,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挣轨!你這毒婦竟也來(lái)了军熏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卷扮,失蹤者是張志新(化名)和其女友劉穎荡澎,沒(méi)想到半個(gè)月后均践,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摩幔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年彤委,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片或衡。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡焦影,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出封断,到底是詐尸還是另有隱情斯辰,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布坡疼,位于F島的核電站彬呻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柄瑰。R本人自食惡果不足惜闸氮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望教沾。 院中可真熱鬧湖苞,春花似錦、人聲如沸详囤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)藏姐。三九已至,卻和暖如春该贾,著一層夾襖步出監(jiān)牢的瞬間羔杨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工杨蛋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兜材,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓逞力,卻偏偏與公主長(zhǎng)得像曙寡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寇荧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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