【長(zhǎng)篇】插件化架構(gòu)設(shè)計(jì)

一次讓你徹底掌握Android插件化架構(gòu)設(shè)計(jì)

插件化簡(jiǎn)介

宿主host 與 插件(免安裝:不需要安裝apk,下載即可)

-->插件加載
-->插件化中的組件支持(startActivity如何去啟動(dòng))
-->插件化中資源(布局文件圖片)的加載

插件化優(yōu)點(diǎn)

1減小apk的體積箩朴,按需求下載模塊
2動(dòng)態(tài)更新插件
3宿主和插件分開編譯,提升團(tuán)隊(duì)開發(fā)效率
4解決方法數(shù)查過65535問題

缺點(diǎn):
項(xiàng)目復(fù)雜度變高了辐益,難度變高,版本兼容問題(插件化的兼容)

組件化和插件化區(qū)別

組件化:是將一個(gè)APP分成多個(gè)模塊,每個(gè)模塊都是一個(gè)組件(module)颓遏,開發(fā)的過程中我們可以讓這些組件相互依賴或者單獨(dú)調(diào)試部分組件孵坚,但是最終發(fā)布的時(shí)候?qū)⑦@些組件合并成一個(gè)統(tǒng)一的APK粮宛。

插件化:是將整個(gè)APP拆分成很多模塊,每個(gè)模塊都是一個(gè)APK(組件化的每個(gè)模塊是一個(gè)Lib)最終打包的時(shí)候?qū)⑺拗鰽PK和插件APK分開打包卖宠,插件APK通過動(dòng)態(tài)下發(fā)到宿主APK巍杈。

插件化框架對(duì)比

1.png

選中DroidPlugin
因?yàn)?60大廠,四大組件全支持扛伍,插件不需在清單文件注冊(cè)筷畦,最重要的是幾乎全部支持Android特性。

插件化架構(gòu)的設(shè)計(jì)思路

2.png

面試題 簡(jiǎn)述Java類加載的過程

3.png

加載階段刺洒,虛擬機(jī)主要完成三件事:
1通過一個(gè)類的權(quán)限定名來獲取定義此類的二進(jìn)制字節(jié)流(class文件—>字節(jié)流)鳖宾;
2將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)域的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)(字節(jié)流-->對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu));
3在java堆中生成一個(gè)代表這個(gè)類的class對(duì)象逆航,作為方法區(qū)域數(shù)據(jù)的訪問入口鼎文。

驗(yàn)證:是否符合java字節(jié)碼編碼規(guī)范
初始化完就會(huì)得到class對(duì)象(一切反射的基石)

Android中類加載器的集成結(jié)構(gòu)

4.png

常用的類加載器

BootClassLoader:系統(tǒng)啟動(dòng)時(shí)用于加載系統(tǒng)常用類,ClassLoader內(nèi)部類因俐;

PathClassLoader:加載系統(tǒng)類和應(yīng)用程序類拇惋,一般不建議開發(fā)者使用周偎;

DexClassLoader:加載DEX文件及包含dex文件的apk或jar。也支持從SD卡進(jìn)行加載蚤假,這也意味著DexClassLoader可以在應(yīng)用未安裝的情況下加載dex相關(guān)文件栏饮。因此它是熱修復(fù)和插件化技術(shù)的基礎(chǔ)。

驗(yàn)證上述的Demo

5.png

使用一個(gè)dexclassloader:

7.png
6.png

記得給讀寫權(quán)限A籽觥袍嬉!

最后參數(shù)父類為什么用pathClassLoader而不是BaseDexClassLoader,之后再說(在下一節(jié))

面試題:雙親委派機(jī)制與自定義String類

8.png
9.png

流程:
DexClassLoader加載前問PathClassLoader你加載過嗎灶平,如果沒有伺通,PathClassLoader問BootClassLoader你加載過嗎,如果沒有逢享,BootClassLoader問是夠可以加載罐监,能加載自己就加載了。不能再向下回傳瞒爬。

雙親委派機(jī)制優(yōu)點(diǎn):
避免重復(fù)加載弓柱,若已經(jīng)加載直接從緩存中讀取。
更加安全侧但,避免開發(fā)者修改系統(tǒng)類矢空。

參數(shù)父類為什么用pathClassLoader而不是BaseDexClassLoader:根據(jù)ClassLoader繼承圖他們倆不是繼承關(guān)系,這個(gè)方法參數(shù)里parent不是父類的意思(父類是super)禀横,這里是優(yōu)先級(jí)屁药、上一層的意思。

當(dāng)發(fā)生Activity跳轉(zhuǎn)時(shí)自動(dòng)加載插件Activity

10.png

如何手動(dòng) 變 自動(dòng)

即:把插件的classloader由DexClassloader變成 pathClassLoader柏锄,讓其認(rèn)為是自己人酿箭。就會(huì)自動(dòng)加載了。

11.png

為什么行得通:DexClassloader和PathClassLoader都是調(diào)用的一樣的父類方法趾娃,區(qū)別就是8.0之前可以指定生成后的odex目錄缭嫡,8.0之后都是系統(tǒng)目錄了。

加載指定類的時(shí)序圖

12.png

BaseDexClassLoader有一個(gè)DexPathList屬性抬闷,調(diào)用findClass遍歷dexElements妇蛀。

搞清時(shí)序圖是為了Activity跳轉(zhuǎn)時(shí)能自動(dòng)加載。我們知道了pathClassLoader的dex都放到哪里了饶氏。然后把dexClassLoader放進(jìn)去讥耗。

App里的dex是用一個(gè)Element[ ] dexElements數(shù)組來存放的有勾。
*為什么是數(shù)組疹启?為了分包,一個(gè)app可以有多個(gè)dex蔼卡。

插件dex的處理

即上面說的流程
把dexClassLoader放進(jìn)pathClassLoader的Element[ ] dexElements數(shù)組

13.png

如果想改dexElements數(shù)組要用到Hook技術(shù)喊崖。

Hook與Hook技巧

14.png

Hook技巧:
1要掌握反射和代理模式挣磨;
2盡量Hook靜態(tài)變量或者單例對(duì)象;(因?yàn)殪o態(tài)變量不用實(shí)例化一個(gè)對(duì)象)
3盡量Hook public的對(duì)象和方法荤懂。(如果是private容易搞壞內(nèi)部結(jié)構(gòu))

插件化架構(gòu)的模塊關(guān)系

創(chuàng)建兩個(gè)module

15.png
16.png
17.png

如何加載插件茁裙、插入dexElements數(shù)組等復(fù)雜工作都會(huì)在PluginCore里去完成

編碼實(shí)現(xiàn)宿主與插件dex數(shù)組合并

插入dexElements數(shù)組在什么時(shí)候比較好呢?
應(yīng)用程序啟動(dòng)時(shí)候最好节仿。即Application
配置權(quán)限

18.png

插件管理器 (單例模式)

19.png
public class PluginManager{
  private static PluginManager instance;
  private  Context context;
  
  private PluginManager(Context context){
    this.context = context;
  }
  public static PluginManager getInstance(Context context){
    if(instance == null){
      instance = new PluginManager(context);
    }
    return instance;
  }
  public void init(){
    try{
      loadApk();
    }catch(Exception e){e.printStackTrace();}   
  }
  //加載插件APK文件并且合并dexElements
  private void loadApk() throws Exception{
    //加載插件的apk
    String pluginApkPath = context.getExternalFilesDir(null).getAbsolutePath()+"/pluginapp-debug.apk";
    //即插件apk地址
    String cachePath = context.getDir("cache_plugin",Context.MODE_PRIVATE).getAbsolutePath();
    DexClassLoader dexClassLoader = new DexClassLoader(pluginApkPath ,cachePath ,null,context.getClassLoader());
    
    //反射操作
    Class<?> baseDexClassLoader = dexClassLoader.getClass().getSuperclass();
    Field pathListField = baseDexClassLoader.getDeclareField("pathList")
    pathListField.setAccessible(true);
    
    //1獲取plugin的dexElements
    Object pluginPathListObject = pathListField.get(dexClassLoader);
    Class<?> pathListClass = pluginPathListObject .getClass();
    Field dexElementsField = pathListClass.getDeclaredField("dexElements");
    dexElementsField.setAccessible(true);
    Object pluginDexElements = dexElementsField.get(pluginPathListObject);

    Log.d("z","pluginDexElements: "+pluginDexElements );

    //2獲取host的dexElements
    ClassLoader pathClassLoader =context.getClassLoader();
    Object hostPathListObject = pathClassLoader.get(pathClassLoader);
    Object hostDexElements = dexElementsField.get(hostPathListObject )
    Log.d("z","hostDexElements : "+hostDexElements );
    //3合并
    int pluginDexElementsLength = Array.getLength(pluginDexElements );
    int hostDexElementsLength = Array.getLength(hostDexElements);
    int newDexElementsLength = pluginDexElementsLength +hostDexElementsLength ;
    
    Object newDexElements = Array.newInstance(hostDexElements.getClass().getComponentType(),newDexElementsLength);

    for(int i=0;i<newDexElementsLength;i++){
      if(i<pluginDexElementsLength){//plugin
        Array.set(newDexElements,i,Array.get(pluginDexElements,i));
      }else{//host
        Array.set(newDexElements,i,Array.get(hostDexElements,i-pluginDexElementsLength));
      }
    }
    dexElementsField.set(hostPathListObject,newDexElements);
    Log.d("z","newDexElements: "+newDexElements);
    
  }
}

把插件apk文件放到sdcard/Android/包名/files下

23.png

其他:

24.png
21.png
22.png

結(jié)果:

非常輕松地獲得插件的class

25.png

接下來是如何啟動(dòng)我們的組件晤锥。Activity如何跳轉(zhuǎn)的

Activity啟動(dòng)中的跨進(jìn)程訪問

我們?cè)噲D這樣來做跳轉(zhuǎn):

26.png

報(bào)錯(cuò)了:

27.png

提示沒有在宿主里注冊(cè)(雖然在插件自己的里面注冊(cè)了)

28.png

Activity1 --》Activity2 要走兩次跨進(jìn)程訪問

AMS會(huì)檢查我們的activity是否在清單文件里完成注冊(cè)。所以報(bào)錯(cuò)了

Hook在插件化架構(gòu)中的運(yùn)用

29.png

我們寫一個(gè)RegisteredActivity廊宪,里面什么都不需要矾瘾,只要在清單文件里注冊(cè)。

分析activity啟動(dòng)流程

30.png
31.png

IActivityManager 就是AMS對(duì)象

我們hook這個(gè)IActivityManager 或者直接拿到mInstance這個(gè)屬性 即可拿到AMS對(duì)象

Hook AMS

32.png
33.png
public class HookUtils{
  //hook 我們的AMS對(duì)象箭启,對(duì)其startActivity攔截
  //把里面intent處理
  public static void hookAMS(Context context) throws Exception{
    //1.獲取AMS對(duì)象
    //1.1獲取靜態(tài)屬性ActivityManager.IActivityManager Singleton的靜態(tài)屬性值
    //它是Singleton類型
    Field iActivityManagerSingletonField = ActivityManager.class.getDeclaredField("IActivityManagerSingleton");
    iActivityManagerSingletonField.setAccessible(true);
    Object iActivityManagerSingletonObject = iActivityManagerSingletonField.get(null);
    
    //1.2獲取Singleton的mInstance屬性值
    Class<?> singletonClazz = Class.forName("android.util.Singleton");

    Field mInstanceField = singletonClazz.getDeclaredField("mInstance");
    mInstanceField.setAccessible(true);
    Object AMSSubject = mInstanceField.get(iActivityManagerSingletonObject);


    //2對(duì)AMS對(duì)象進(jìn)行代理
    Class<?> IActivityManagerInterface=Class.forName("android.app.IActivityManager");
    AMSInvocationHandler handler = new AMSInvocationHandler(context,AMSSubject);
    Object AMSProxy = Proxy.newProxyInstance(
      Thread.currentThread().getContextClassLoader()
      ,new Class[]{IActivityManagerInterface}
      ,handler 

    );

    mInstanceField.set(iActivityManagerSingletonObject,AMSProxy);
    
    //3InvocationHandler對(duì)AMS對(duì)象的方法進(jìn)行攔截

  }

}
public class AMSInvocationHandler implements InvocationHandler{
  private Context context;
  private Object subject;
  
  public AMSInvocationHandler (Context context,Object subject){
    this.context = context;
    this.subject = subject;
   }
  
  @Override
  public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
    
    if("startActivity".equals(method.getName())){
      Log.d("z","AMSInvocationHandler startActivity invoke");
      //要把PluginActivity替換成RegisteredActivity
      //找到intent參數(shù)
      for(int i = 0;i<args.length;i++){
        Object arg = args[i];
        if(arg instanceof Intent){
          Intent intentNew = new Intent();
          intentNew.setClass(context,RegisteredActivity.class);    
          //原來的保存
          intentNew.putExtra("actionIntent",(Intent)arg);
          args[i] = intentNew;
          Log.d("z","AMSInvocationHandler new Intent");
          break;
        }
      }
      
    }
  
    return method.invoke(subject,args);
  }

}

在上面pluginManager 代碼段里加上

public void init(){
    try{
      loadApk();
      HookUtils.hookAMS(context);
      HookUtils.hookHandler();
    }catch(Exception e){e.printStackTrace();}   
  }

Hook Handler思路

現(xiàn)在等AMS驗(yàn)證完后壕翩,我們什么時(shí)候把想要啟動(dòng)的拿出來?

34.png

dispatchMessage會(huì)判斷callback是否為空
如果不為空傅寡,會(huì)執(zhí)行callback的handlerMessage,然后在執(zhí)行子類的handlerMessage.
所以我們給callback一個(gè)值放妈,先執(zhí)行callback里handlerMessage,修改我們需要的內(nèi)容荐操。

ActivityThread有一個(gè)H 內(nèi)部類 繼承自handler芜抒,里面記錄了hangler的信息(activityInfo)。

Handler發(fā)消息淀零,looper輪詢到 后都會(huì)dispatchMessage挽绩,里面判斷mCallback是否為空。(一般都為空)

35.png

所以我們修改callback驾中,在里面做事唉堪。

編碼實(shí)現(xiàn)Hook Handler

在上面HookUtils里加入一個(gè)方法

//hook 獲取到 Handler的特定消息(LAUNCH_ACTIVITY)中的intent,進(jìn)行處理肩民。
//將intent對(duì)象里的RegisteredActivity替換成PluginActivity

public static void hookHandler() throws Exception{
  //1.獲取到handler對(duì)象(mH屬性值)
  //1.1獲取到ActivityThread對(duì)象
  Class<?>  activityThreadClazz= Class.forName("android.app.ActivityThread");
  //拿到activityThread對(duì)象唠亚,他有一個(gè)靜態(tài)的屬性(sCurrentActivityThread)
  Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
  sCurrentActivityThreadField.setAccessible(true);
  Object activityThreadObject = sCurrentActivityThreadField.get(null);

  //1.2獲取ActivityThread對(duì)象的mH屬性值
  Field mHField = activityThreadClazz.getDeclaredField("mH");
  mHField.setAccessible(true);
  Object handler = mHField.get(activityThreadObject);

  //2.給我們的Handler的mCallBack屬性進(jìn)行賦值
  Field mCallbackField = Handler.class.getDeclaredField("mCallback");
  mCallbackField .setAccessible(true);


  //3.在callback里面將intent對(duì)象里的RegisteredActivity替換成PluginActivity
    //創(chuàng)建了MyCallback 
    mCallbackField.set(handler,new MyCallback());
}
public class MyCallback implements Handler.Callback{
     private static final int LAUNCH_ACTIVITY = 100;

  @Override
   public boolean handleMessage(Message msg){
      switch(msg.what){
        case LAUNCH_ACTIVITY:
          Log.d("z","MyCallback  handleMessage LAUNCH_ACTIVITY");
          try{
            Field intentField = msg.obj.getClass().getDeclaredField("intent");
            intentField.setAcessible(true);
            Intent intent = intentField.get(msg.obj);
            //取出我們放入的actionIntent
            Parcelable actionIntent = intent.getParcelableExtra("actionIntent");
            if(actionIntent!= null){
              //替換
              Log.d("z","MyCallback  intent replaced");
              intentField.set(msg.obj,actionIntent);
            }
            
          }catch(Exception e){
            e.printStackTrace();
          }
          break;
      }
      return false;//這里true直接結(jié)束了,return false則執(zhí)行子類的hangleMessage持痰。灶搜!
    }
}

AMS版本適配

37.png

修改之前代碼

  public static void hookAMS(Context context) throws Exception{
    //1.獲取AMS對(duì)象
    //1.1獲取靜態(tài)屬性ActivityManager.IActivityManager Singleton的靜態(tài)屬性值
    //它是Singleton類型
    Field iActivityManagerSingletonField =null;
    if(Build.VERSION.SDK_INT>=BUILD.VERSION_O){
      iActivityManagerSingletonField = ActivityManager.class.getDeclaredField("IActivityManagerSingleton");
    }else{
    //低版本拿ActivityManagerNative的gDefault
      Class<?> ActivityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
      iActivityManagerSingletonField = ActivityManagerNativeClazz.getDeclaredField("gDefault");
    }
    iActivityManagerSingletonField.setAccessible(true);
    Object iActivityManagerSingletonObject = iActivityManagerSingletonField.get(null);

Handler版本適配

38.png
39.png

修改MyCallback

public class MyCallback implements Handler.Callback{
     private static final int LAUNCH_ACTIVITY = 100;

    private static final int EXECUTE_TRANSACTION= 159;

  @Override
   public boolean handleMessage(Message msg){
      switch(msg.what){
        case LAUNCH_ACTIVITY:
          Log.d("z","MyCallback  handleMessage LAUNCH_ACTIVITY");
          try{
            Field intentField = msg.obj.getClass().getDeclaredField("intent");
            intentField.setAcessible(true);
            Intent intent = intentField.get(msg.obj);
            //取出我們放入的actionIntent
            Parcelable actionIntent = intent.getParcelableExtra("actionIntent");
            if(actionIntent!= null){
              //替換
              Log.d("z","MyCallback  intent replaced");
              intentField.set(msg.obj,actionIntent);
            }
            
          }catch(Exception e){
            e.printStackTrace();
          }
          break;
        //API 28
        case EXECUTE_TRANSACTION:
          try{
            //Intent 
            //1獲取mActivityCallbacks集合
            Object clientTransactionObject = msg.obj;
            Class<?>clientTransactionClazz = clientTransactionObject.getClass();
            Field mActivityCallbacksField = clientTransactionClazz.getDeclaredField("mActivityCallbacks");
            mActivityCallbacksField.setAccessible(true); 
            List mActivityCallbacks = (List)mActivityCallbacksField.get(clientTransactionObject);
            //2遍歷集合里的元素得到LaunchActivityItem
            for(Object item:mActivityCallbacks ){
              if("android.app.servertransaction.LaunchActivityItem".equals(item.getClass().getName())){
                Field mIntentField = item.getClass().getDeclaredField("mIntent");
                mIntentField.setAccessible(true);

                Intent intent = (Intent)mIntentField.get(item);
                Parcelable actionIntent =intent.getParcelableExtra("actionIntent");
                if(actionIntent !=null){
                  Log.d("z","MyCallback handleMessage intent replaced");
            //3替換LaunchActivityItem的Intent     
                  mIntentField.set(item,actionIntent);
                }
              }
            }     
          }catch(Exception e){
            e.printStackTrace();
          }
          
          break;
      }
      return false;//這里true直接結(jié)束了,return false則執(zhí)行子類的hangleMessage工窍。割卖!
    }
}

面試題 簡(jiǎn)述Activity啟動(dòng)流程

我們從Context的starstActivity說起,其實(shí)現(xiàn)時(shí)ContextImpl的startActivity,然后內(nèi)部通過Instrumentation來嘗試啟動(dòng)Activity患雏,它會(huì)調(diào)用AMS的startActivity方法鹏溯,這是一個(gè)跨進(jìn)程過程,當(dāng)AMS效驗(yàn)完成Activity的合法性后淹仑,會(huì)通過Application回調(diào)到我們的進(jìn)程丙挽,也是一次跨進(jìn)程過程肺孵,而ApplicationThread就是一個(gè)Binder,毀掉邏輯是在binder線程池中完成的颜阐,所以需要通過Handler H將其切換到UI線程平窘,第一個(gè)消息是LAUNCH_ACTIVITY,它對(duì)應(yīng)handleLaunchActivity凳怨,在這個(gè)方法里玩成了Activity的創(chuàng)建和啟動(dòng)瑰艘。

面試題raw目錄和assets目錄有什么區(qū)別

raw:Android 會(huì)自動(dòng)的為目錄中的所有資源文件生成一個(gè)ID,這意味著很容易就可以訪問到這個(gè)資源肤舞,甚至在xml中都是可以訪問的磅叛,使用ID訪問的速度是最快的

assets:不會(huì)生成ID,只能通過AssetManager訪問萨赁,xml中不能訪問弊琴,訪問速度會(huì)慢一些,不過操作更加方便杖爽。

插件化中的資源加載

Resources資源加載過程分析

我們是否可以new 一個(gè)Resource來加載資源呢敲董?

40.png

Activity構(gòu)建上下文時(shí)也會(huì)構(gòu)建Resources。

ActivityThread-->
創(chuàng)建上下文

41.png
42.png

ResourceManager如何創(chuàng)建的慰安?

實(shí)際上是ResourcesImpl 創(chuàng)建AssetManager腋寨,其中指定要去加載資源的路徑

所以我們new Resources()對(duì)象時(shí)指定AssetManager,并且AssetManager指定我們插件資源的路徑化焕,那么這個(gè)Resources對(duì)象就可以加載我們的資源了萄窜。

編碼實(shí)現(xiàn)插件資源加載

在PluginManager中寫入方法:

//獲取插件的Resources對(duì)象
public Resources loadResources() throws Exception{
  String pluginApkPath = context.getExternalFilesDir(null).getAbsolutePath()+"/pluginapp-debug.apk";
  AssetManager assetManager = AssetManager.class.newInstance();
  Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath",String.class);
  addAssetPathMethod.invoke(assetManager,pluginApkPath);
  
  return new Resources(assetManager,context.getResources().getDisplayMetrics(),context.getResources().getConfiguration());
}

那在什么時(shí)候使用?

43.png

Plugin 和宿主在同一個(gè)Application之下

所以在宿主的Application里調(diào)用

修改Application,重寫父類的getResources()

45.png

在插件Activity里 重寫getResource撒桨,從Application里拿

44.png

運(yùn)行時(shí)查刻,插件會(huì)進(jìn)入到宿主的Application里找到資源

如果好多插件,Resource需要分組凤类。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穗泵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谜疤,更是在濱河造成了極大的恐慌佃延,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夷磕,死亡現(xiàn)場(chǎng)離奇詭異履肃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)坐桩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門尺棋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撕攒,你說我怎么就攤上這事陡鹃。” “怎么了抖坪?”我有些...
    開封第一講書人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵萍鲸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我擦俐,道長(zhǎng)脊阴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任蚯瞧,我火速辦了婚禮嘿期,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘埋合。我一直安慰自己备徐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開白布甚颂。 她就那樣靜靜地躺著蜜猾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪振诬。 梳的紋絲不亂的頭發(fā)上蹭睡,一...
    開封第一講書人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音赶么,去河邊找鬼肩豁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辫呻,可吹牛的內(nèi)容都是我干的清钥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼放闺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼循捺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雄人,我...
    開封第一講書人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤从橘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后础钠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恰力,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年旗吁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了踩萎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡很钓,死狀恐怖香府,靈堂內(nèi)的尸體忽然破棺而出董栽,到底是詐尸還是另有隱情,我是刑警寧澤企孩,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布锭碳,位于F島的核電站,受9級(jí)特大地震影響勿璃,放射性物質(zhì)發(fā)生泄漏擒抛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一补疑、第九天 我趴在偏房一處隱蔽的房頂上張望歧沪。 院中可真熱鬧,春花似錦莲组、人聲如沸诊胞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厢钧。三九已至,卻和暖如春嬉橙,著一層夾襖步出監(jiān)牢的瞬間早直,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工市框, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霞扬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓枫振,卻偏偏與公主長(zhǎng)得像喻圃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子粪滤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

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