Replugin 全面解析(3)

上一篇分析中我們分析了Replugin框架Host端的一些核心概念俗冻,還梳理了Activity啟動(dòng)的流程,但是有兩個(gè)重要部分沒有提及或者詳細(xì)講述牍颈,那就是Plugin的加載過程迄薄,Plugin端的初始化,所以本篇會(huì)重點(diǎn)看看這兩個(gè)方面的內(nèi)容煮岁。

目錄

  • Plugin加載的詳細(xì)過程
  • Plugin端初始化
  • Plugin中啟動(dòng)Activity

Plugin加載的詳細(xì)過程

要講解這個(gè)過程讥蔽,我們得從Plugin.doLoad函數(shù)開始,在上一篇分析中其實(shí)已經(jīng)提到過画机,只是當(dāng)時(shí)并沒有分析它的細(xì)節(jié)冶伞。這個(gè)函數(shù)做了三件事情:

  • 釋放插件文件到相應(yīng)的目錄,比如so庫步氏,dex文件
  • 加載dex文件响禽,比如組件信息,組件屬性,資源等
  • 要運(yùn)行Plugin芋类,還需要初始化Plugin的運(yùn)行環(huán)境

釋放文件這一步很簡(jiǎn)單隆嗅,就是將APK包打開以后將相應(yīng)的文件放到不同的目錄中,有興趣的同學(xué)可以取跟以下代碼侯繁,這里就不贅述了胖喳。

來看看dex文件的加載,這是通過Loader.loadDex函數(shù)實(shí)現(xiàn)的巫击。這個(gè)方法比較長(zhǎng)禀晓,我們拆開來看。

第一步坝锰,獲取PackageInfo并緩存插件相關(guān)的信息粹懒,比如組件,組件屬性顷级,資源等凫乖,下次就可以直接從緩存中讀取。

if (mPackageInfo == null) {
    // 通過PackageManager獲取PackageInfo
    mPackageInfo = pm.getPackageArchiveInfo(mPath,
            PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
    ......
    // 添加針對(duì)SO庫的加載
    PluginInfo pi = mPluginObj.mInfo;
    File ld = pi.getNativeLibsDir();
    mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
    // 緩存表: pkgName -> pluginName
    synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
        Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
    }
    // 緩存表: pluginName -> fileName
    synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
        Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
    }
    // 緩存表: fileName -> PackageInfo
    synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
        Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
    }
}

第二步弓颈,解析組件信息帽芽,注冊(cè)Plugin的Manifest中聲明的BroadcastReceiver。

if (mComponents == null) {
    mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
    // 動(dòng)態(tài)注冊(cè)插件中聲明的 receiver
    regReceivers();
    // 緩存表Components
    synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
        Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
    }
    ......
}

這一步用到了ComponentList類翔冀,在它的構(gòu)造函數(shù)中完成了對(duì)Plugin的Manifest文件的解析导街,調(diào)用的是Minifestparser類。有興趣的同學(xué)可以跟一跟這里的代碼纤子,并不復(fù)雜搬瑰。讀取到Manifest文件以后,使用ManifestParser.INS.parse函數(shù)調(diào)用XmlHandler解析控硼,把解析以后的信息緩存到ManifestParser中泽论。

public void parse(PluginInfo pli, String manifestStr) {
    XmlHandler handler = parseManifest(manifestStr);

    Map<String, List<IntentFilter>> activityFilterMap = new HashMap<>();
    putToMap(mPluginActivityInfoMap, activityFilterMap, pli);
    parseComponent(pli.getName(), activityFilterMap, handler.getActivities(), mActivityActionPluginsMap);

    Map<String, List<IntentFilter>> serviceFilterMap = new HashMap<>();
    putToMap(mPluginServiceInfoMap, serviceFilterMap, pli);
    parseComponent(pli.getName(), serviceFilterMap, handler.getServices(), mServiceActionPluginsMap);

    Map<String, List<IntentFilter>> receiverFilterMap = new HashMap<>();
    putToMap(mPluginReceiverInfoMap, receiverFilterMap, pli);
    parseComponent(pli.getName(), receiverFilterMap, handler.getReceivers(), null);
}

第三步,獲取Plugin的資源卡乾,先查找緩存翼悴,如果找不到就通過PackageManager創(chuàng)建Resources對(duì)象,但這里做了一個(gè)多余的賦值操作是為了修復(fù)一個(gè)BUG幔妨,不必在意鹦赎。最后將資源對(duì)象緩存下來。

mPkgResources = Plugin.queryCachedResources(mPath);
if (mPkgResources == null) {
    try {
        if (BuildConfig.DEBUG) {
            // 如果是Debug模式的話误堡,防止與Instant Run沖突钙姊,資源重新New一個(gè)
            Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
            mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
        } else {
            mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
        }
    } catch (NameNotFoundException e) {
        return false;
    }
    // 緩存Resources
    synchronized (Plugin.FILENAME_2_RESOURCES) {
        Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
    }
}

第四步,注意咯埂伦,這里就是創(chuàng)建Plugin的PluginDexClassLoader的地方煞额,將它緩存起來,不必每次都創(chuàng)建一個(gè)新的。

if (mClassLoader == null) {
    String out = mPluginObj.mInfo.getDexParentDir().getPath();
    ......
    String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
    // 創(chuàng)建PluginDexClassLoader對(duì)象
    mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);   
    ......
    synchronized (Plugin.FILENAME_2_DEX) {
        Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader)); // 緩存ClassLoader
    }
}

第五步膊毁, 為Plugin創(chuàng)建一個(gè)全局的PluginContext胀莹,并用上面創(chuàng)建的ClassLoader以及Resources作為參數(shù)。而這個(gè)PluginContext對(duì)象會(huì)被賦值給Plugin的Application對(duì)象(后面會(huì)講到)婚温。其實(shí)每一個(gè)Plugin的Activity都會(huì)創(chuàng)建一個(gè)PluginContext對(duì)象描焰,并使用相同的ClassLoaderResources,因此在Plugin中就可以加載相關(guān)的類和使用資源了栅螟,跟原生程序一樣荆秦。

mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);

到這里,Plugin的加載就算是全部完成了力图!

Plugin端初始化

Dex文件加載完成以后步绸,要運(yùn)行Plugin還需要初始化Plugin的運(yùn)行環(huán)境相關(guān)的類,在Plugin.doLoad的最后階段調(diào)用了loadEntryLocked函數(shù)吃媒,這個(gè)函數(shù)負(fù)責(zé)初始化Plugin的運(yùn)行環(huán)境瓤介。

private boolean loadEntryLocked(PluginCommImpl manager) {
    if (mDummyPlugin) {
        ......
    } else {
        ......
        } else if (mLoader.loadEntryMethod3()) {  // 通過反射拿到Entry的create函數(shù)
            if (!mLoader.invoke2(manager)) {
                return false;
            }
        } else {
            return false;
        }
    }
    return true;
}

Loader.loadEntryMethod3通過反射將Plugin中的Entry類的create函數(shù)對(duì)象得到并保存在mCreateMethod2中。注意這里的mClassLoader是插件的PluginDexClassLoader赘那,所以才能得到插件中的類刑桑。

final boolean loadEntryMethod3() {
    try {
        String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
        Class<?> c = mClassLoader.loadClass(className);
        mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
    } catch (Throwable e) {
    }
    return mCreateMethod2 != null;
}

接著Loader.invoke2函數(shù)會(huì)用反射的方式調(diào)用上面拿到的create函數(shù)。

final boolean invoke2(PluginCommImpl x) {
    try {
        IBinder manager = null;
        final ClassLoader classLoader = getClass().getClassLoader();
        IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, classLoader, manager);   // 調(diào)用create函數(shù)
        ......
        mBinderPlugin = new ProxyPlugin(b);
        mPlugin = mBinderPlugin;
    } catch (Throwable e) {
        return false;
    }
    return true;
}

注意募舟,這里我們將會(huì)進(jìn)入到Plugin端的代碼中祠斧,來看看Entry.create做了什么。這里有一個(gè)參數(shù)是ClassLoader拱礁,這個(gè)ClassLoader是Host中的RepluginClassLoader琢锋,有了它,Plugin才能找到Host當(dāng)中的類并調(diào)用這些類的方法觅彰。

public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
    RePluginFramework.init(cl); // 初始化一些類,這些類用于調(diào)用Host中類的方法
    RePluginEnv.init(context, cl, manager);

    return new IPlugin.Stub() {
        @Override
        public IBinder query(String name) throws RemoteException {
            return RePluginServiceManager.getInstance().getService(name);
        }
    };
}

這里RepluginFramework.init初始化了一些代理類钮热,這些代理類可以在Plugin中調(diào)用Host中的函數(shù)填抬。

private static boolean initLocked(ClassLoader cl) {
        ......
        try {
            RePluginInternal.ProxyRePluginInternalVar.initLocked(cl);
            RePlugin.ProxyRePluginVar.initLocked(cl);
            PluginLocalBroadcastManager.ProxyLocalBroadcastManagerVar.initLocked(cl);
            PluginProviderClient.ProxyRePluginProviderClientVar.initLocked(cl);
            PluginServiceClient.ProxyRePluginServiceClientVar.initLocked(cl);
            IPC.ProxyIPCVar.initLocked(cl);

            mHostInitialized = true;

        } catch (final Throwable e) {
        }
        return mHostInitialized;
    }

我們拿RePlugin.ProxyRePluginVar.initLocked作為例子來講解。這里還是通過反射隧期,在Plugin中去獲取Host中Replugin的相關(guān)方法并保存在一系列的MethodInvoker對(duì)象中飒责,比如Replugin.installReplugin.preload仆潮,Replugin.startActivity等宏蛉。

static void initLocked(final ClassLoader classLoader) {
        // 初始化Replugin的相關(guān)方法
        final String rePlugin = "com.qihoo360.replugin.RePlugin";
        install = new MethodInvoker(classLoader, rePlugin, "install", new Class<?>[]{String.class});
        preload = new MethodInvoker(classLoader, rePlugin, "preload", new Class<?>[]{String.class});
        ......
        startActivity = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class});
        startActivity2 = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class, String.class, String.class});
       ......
    }
}

當(dāng)我們?cè)赑lugin中使用Replugin.startActivity時(shí),實(shí)際上是通過MethodInvoker.call函數(shù)去執(zhí)行Host中Replugin對(duì)應(yīng)的startActivity函數(shù)性置。所以Plugin中的Replugin類的實(shí)現(xiàn)就只是一個(gè)空殼代理而已拾并,你可以看到它的實(shí)現(xiàn)如下:

public static boolean startActivity(Context context, Intent intent) {
        if (!RePluginFramework.mHostInitialized) {
            return false;
        }
        try {  // 用反射的方式調(diào)用Host中Replugin的startActivity函數(shù)來啟動(dòng)Activity
            Object obj = ProxyRePluginVar.startActivity.call(null, context, intent);
            if (obj != null) {
                return (Boolean) obj;
            }
        } catch (Exception e) {
            if (LogDebug.LOG) {
                e.printStackTrace();
            }
        }
        return false;
    }

看到這里,你就跟上一篇中 插件Activity啟動(dòng)流程 這一節(jié)連起來啦!

插件中啟動(dòng)Activity

Android中ActivityContext都有startActivity函數(shù)嗅义,為了在插件中能正常啟動(dòng)Activity屏歹,我們要將這些函數(shù)都屏蔽掉,轉(zhuǎn)而使用Replugin提供的startActivity之碗。

為什么一定要用Replugin提供startActivity方法呢蝙眶?因?yàn)槲覀儾寮慕M件在Host的Manifest中是沒有聲明的,

只能通過坑位來啟動(dòng)褪那,而啟動(dòng)坑位的動(dòng)作只能在Host中完成幽纷。

Replugin是怎么做的呢?來看看吧博敬。

將官方Demo中的插件APK反編譯出來友浸,會(huì)發(fā)現(xiàn)所有繼承了Activity的類都被強(qiáng)制修改成繼承PluginActivity,這個(gè)工作是由replugin-plugin-gradle在編譯階段完成的冶忱。

實(shí)際上在replugin-plugin-lib中的com.qihoo360.replugin.loader.a包中尾菇,你可以找到更多的Activity關(guān)的類。

首先PluginActivity重寫了startActivity函數(shù)囚枪,并在其中通過反射調(diào)用Host中的Replugin.startActivity函數(shù)派诬,這樣就能做到在插件中啟動(dòng)Activity了。這里的反射調(diào)用指的就是調(diào)用前面講過的RePlugin.ProxyRePluginVar類在初始化時(shí)得到的函數(shù)链沼。

public void startActivity(Intent intent) {
    if (RePluginInternal.startActivity(this, intent)) {
        return;
    }
    super.startActivity(intent);
}

然后默赂,PluginActivity重寫了attachBaseContext函數(shù),同樣在代理類中通過反射調(diào)用Host中Factory2.createActivityContext函數(shù)創(chuàng)建一個(gè)PluginContext對(duì)象括勺,并用這個(gè)對(duì)象替換掉原生的Context對(duì)象缆八。

protected void attachBaseContext(Context newBase) {
    newBase = RePluginInternal.createActivityContext(this, newBase); //創(chuàng)建PluginContext對(duì)象
    super.attachBaseContext(newBase); //替換原生的Context
}

那么這個(gè)PluginContext又做了什么呢?原來PluginContext也重寫了startActivity函數(shù)疾捍,并且在其中調(diào)用了Factory2.startActivity函數(shù)奈辰,接著又會(huì)調(diào)用PluginLibraryInternalProxy.startActivity。后面的事情如果讀過第一篇分析文章乱豆,你應(yīng)該都知道啦奖恰!

@Override
public void startActivity(Intent intent) {
    if (!Factory2.startActivity(this, intent)) { 
        if (mContextInjector != null) {
            mContextInjector.startActivityBefore(intent);
        }
        super.startActivity(intent);
        if (mContextInjector != null) {
            mContextInjector.startActivityAfter(intent);
        }
    }
}

到這里,你無論是使用getContext().startActivity還是直接在Activity中使用startActivity都會(huì)走Replugin的啟動(dòng)流程宛裕。但是事情并沒有完瑟啃,還有一個(gè)地方需要修改,那就是Application中的Context揩尸。

PluginApplicationClient為插件創(chuàng)建了Application對(duì)象蛹屿,在PluginApplicationClient.callAttachBaseContext函數(shù)中通過反射調(diào)用Applicaiton.attach函數(shù),用一個(gè)PluginContext對(duì)象替換掉原來的Context對(duì)象岩榆。

這里的PluginContext對(duì)象就是在前面講到的Dex加載過程中創(chuàng)建的错负,作為Plugin的全局Context坟瓢。

public void callAttachBaseContext(Context c) {
    try {
        sAttachBaseContextMethod.setAccessible(true);   
        sAttachBaseContextMethod.invoke(mApplication, c);  // 將PluginContext對(duì)象傳遞給Application對(duì)象
    } catch (Throwable e) {
        if (BuildConfig.DEBUG) {
            e.printStackTrace();
        }
    }
}

OK,到這里我們?cè)诓寮芯湍苷5氖褂肦eplugin的啟動(dòng)流程在插件中啟動(dòng)Activity了JT芈獭!

注意

實(shí)際上在Plugin中調(diào)用getApplication獲取到的是Host的Application對(duì)象油航,因?yàn)閯e忘了我們是利用坑位原理運(yùn)行插件組件的崭庸,所以在插件中我們用到的都是Host的Application。但是萬一Plugin的Application中有客戶定制的任何動(dòng)作要完成呢谊囚?所以在加載 Plugin之后怕享,Host也會(huì)通過反射創(chuàng)建Plugin的Application對(duì)象,并反射調(diào)用它的attachcreate函數(shù)镰踏。

Plugin.load

final boolean load(int load, boolean useCache) {
   PluginInfo info = mInfo;
   boolean rc = loadLocked(load, useCache); //Plugin加載完成

   if (load == LOAD_APP && rc) {
       callApp();   // 關(guān)于Plugin的Application的操作都在這里了
   }
     ......
   return rc;
}

Plugin.callAppLocked

private void callAppLocked() {
    // 獲取并調(diào)用Application的幾個(gè)核心方法
    if (!mDummyPlugin) {
          ......
        mApplicationClient = PluginApplicationClient.getOrCreate(
                mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);  // 創(chuàng)建Plugin的Appication對(duì)象

        if (mApplicationClient != null) { // 調(diào)用Application.attach
            mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
            mApplicationClient.callOnCreate(); //調(diào)用Application.onCreate
        }
    } else {
    }
}

總結(jié):

通過這兩篇文章函筋,我們總結(jié)一下一個(gè)插件Activity啟動(dòng)的流程大致是:

  • 加載目標(biāo)Activity信息

    開始查找Activity信息 —> 找到對(duì)應(yīng)的Pugin信息 —> 解壓APK文件 —> 加載Dex文件內(nèi)容 —> 創(chuàng)建Application對(duì)象 —> 創(chuàng)建Entry對(duì)象初始化Plugin環(huán)境

  • 尋找坑位

    查找坑位 —> 將目標(biāo)Activity與找到的坑位以ActivityState的形式緩存在PluginContainers中 —> 啟動(dòng)坑位Activity —> 系統(tǒng)調(diào)用RepluginClassLoader —> 調(diào)用PluginDexClassLoader加載坑位Activity類 —> 通過PluginContainers找到坑位對(duì)應(yīng)的目標(biāo)Activity類 —> 系統(tǒng)調(diào)用PluginActivityattachBaseContext函數(shù) —> 創(chuàng)建PluginContext對(duì)象并替換 —> 目標(biāo)Activity的正常啟動(dòng)流程(onCreateonStart奠伪,onResume.....)

    ?

如果你已經(jīng)讀完了這兩篇分析文章跌帐,但還沒有將Replugin的里面如此多的類的關(guān)系理清楚,那么下面這張圖也許可以幫到你绊率。不過這張圖目前還不完整谨敛,只針對(duì)目前已經(jīng)講到的部分,后面會(huì)逐漸補(bǔ)全滤否。

replugin_1.jpg

綠色的部分是replugin-plugin-lib脸狸,藍(lán)色和紅色部分都是是replugin-host-lib包中的類,但是藍(lán)色部分是運(yùn)行在UI進(jìn)程中藐俺,而紅色部分是運(yùn)行在Persistent進(jìn)程中炊甲。

綠色部分和藍(lán)色部分之間的調(diào)用都是通過反射來實(shí)現(xiàn)的,所以用類虛線箭頭欲芹,同樣藍(lán)色部分和紅色部分是通過Binder進(jìn)程間通信機(jī)制來調(diào)用的卿啡,也用虛線箭頭表示。

下一篇Replugin 全面解析 (4)我們會(huì)講解Service以及進(jìn)程相關(guān)內(nèi)容菱父!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颈娜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滞伟,更是在濱河造成了極大的恐慌揭鳞,老刑警劉巖炕贵,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梆奈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡称开,警方通過查閱死者的電腦和手機(jī)亩钟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門乓梨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人清酥,你說我怎么就攤上這事。” “怎么了雁歌?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵聪舒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我辱志,道長(zhǎng)蝠筑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任揩懒,我火速辦了婚禮什乙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘已球。我一直安慰自己臣镣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布智亮。 她就那樣靜靜地躺著忆某,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸽素。 梳的紋絲不亂的頭發(fā)上褒繁,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音馍忽,去河邊找鬼棒坏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛遭笋,可吹牛的內(nèi)容都是我干的坝冕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼瓦呼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼喂窟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起央串,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤磨澡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后质和,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稳摄,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年饲宿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厦酬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胆描。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖仗阅,靈堂內(nèi)的尸體忽然破棺而出昌讲,到底是詐尸還是另有隱情,我是刑警寧澤减噪,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布短绸,位于F島的核電站,受9級(jí)特大地震影響筹裕,放射性物質(zhì)發(fā)生泄漏鸠按。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一饶碘、第九天 我趴在偏房一處隱蔽的房頂上張望目尖。 院中可真熱鬧,春花似錦扎运、人聲如沸瑟曲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洞拨。三九已至,卻和暖如春负拟,著一層夾襖步出監(jiān)牢的瞬間烦衣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工掩浙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留花吟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓厨姚,卻偏偏與公主長(zhǎng)得像衅澈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谬墙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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