Hook方式實(shí)現(xiàn)Activity插件化

隨著應(yīng)用程序的功能模塊越來(lái)越多,復(fù)雜度越來(lái)越高份乒,導(dǎo)致了應(yīng)用程序模塊之間的耦合度越來(lái)越高恕汇,App的體積也隨之越來(lái)越大。與此同時(shí)或辖,隨著應(yīng)用程序代碼量的不斷增大瘾英,引入的庫(kù)越來(lái)越多,那么方法數(shù)很容易就超過(guò)了65535個(gè)颂暇,占用內(nèi)存也會(huì)隨之增大缺谴。于是,為了解決上述困境耳鸯,出現(xiàn)了插件化的思想湿蛔,其核心理念就是由宿主App去加載和運(yùn)行插件App。

宿主App是指預(yù)先被安裝在我們手機(jī)上的App县爬,可以獨(dú)立運(yùn)行阳啥,同時(shí)也可以加載插件。插件App是指那些子功能模塊的App财喳,它們可以被宿主App加載和運(yùn)行察迟,同時(shí)也可以作為獨(dú)立App進(jìn)行單獨(dú)運(yùn)行斩狱。這樣,各個(gè)功能模塊就可以單獨(dú)開(kāi)發(fā)扎瓶,宿主與插件之間所踊,以及插件與插件之間的耦合度就會(huì)大大降低,而且靈活性大大提高概荷。與此同時(shí)污筷,dex的體積也會(huì)隨之減小,從而避免65535問(wèn)題乍赫。在內(nèi)存占用方面瓣蛀,由于我們是只有在使用到某個(gè)插件時(shí)才會(huì)去進(jìn)行相應(yīng)的加載,這樣就可以減少內(nèi)存的使用雷厂。

插件化的知識(shí)體系還是比較多的惋增,包括Java反射原理,ClassLoader加載Dex原理改鲫,Android資源的加載诈皿,四大組件的加載,Android系統(tǒng)服務(wù)的運(yùn)行原理等等像棘。其中稽亏,四大組件的加載是插件化技術(shù)的核心,而Activity的插件化則更是重中之重缕题,因此本文主要介紹Activity的插件化截歉。Activity的插件化主要有3種實(shí)現(xiàn)方式,分別是反射實(shí)現(xiàn)烟零、接口實(shí)現(xiàn)和Hook技術(shù)實(shí)現(xiàn)瘪松。目前Hook技術(shù)實(shí)現(xiàn)是主流,對(duì)于前2種技術(shù)實(shí)現(xiàn)自身了解也很有限锨阿,因此本文主要介紹如何利用Hook技術(shù)實(shí)現(xiàn)Activity的插件化宵睦。


啟動(dòng)插件Activity.gif

貼一下Demo的地址

https://github.com/lxbnjupt/PluginActivityDemo

一、Activity啟動(dòng)流程

首先墅诡,需要說(shuō)明的是壳嚎,如果一個(gè)Activity沒(méi)有在AndroidManifest中注冊(cè),此時(shí)如果去啟動(dòng)它的話將會(huì)得到ActivityNotFoundException末早,因?yàn)樵趩?dòng)的過(guò)程中烟馅,存在一個(gè)校驗(yàn)的過(guò)程,而這個(gè)校驗(yàn)則是由AMS來(lái)完成的荐吉,這個(gè)我們無(wú)法干預(yù)焙糟。而我們也很明確的知道,插件App中的Activity預(yù)先是不可能在宿主App的AndroidManifest中進(jìn)行注冊(cè)的样屠。所以,我們要想要實(shí)現(xiàn)Activity的插件化,就要重點(diǎn)去解決這個(gè)問(wèn)題痪欲。

那么如何實(shí)現(xiàn)Activity的插件化呢悦穿,我們的首要任務(wù)就是要徹底搞清楚Activity的啟動(dòng)流程,只有這樣才能從中找出解決問(wèn)題的實(shí)現(xiàn)方案业踢。在Activity啟動(dòng)流程源碼解析一文中栗柒,我們主要從源碼的角度分析了普通Activity的啟動(dòng)流程。這個(gè)過(guò)程主要涉及了兩個(gè)進(jìn)程知举,分別是AMS所在SystemServer進(jìn)程和應(yīng)用程序進(jìn)程瞬沦,通過(guò)Binder機(jī)制進(jìn)行跨進(jìn)程通信,相互配合雇锡,最終完成Activity的啟動(dòng)逛钻。

對(duì)于我們而言,AMS在SystemServer進(jìn)程中锰提,我們無(wú)法直接進(jìn)行修改曙痘,只能在應(yīng)用程序進(jìn)程中做文章。因此立肘,Activity的插件化方案大多是采用占坑的思想边坤,即預(yù)先在AndroidManifest.xml中顯示注冊(cè)一個(gè)Activity來(lái)進(jìn)行占坑,用來(lái)通過(guò)AMS的校驗(yàn)谅年,在通過(guò)校驗(yàn)之后再用插件Activity替換占坑的Activity茧痒。

通過(guò)分析Activity的啟動(dòng)流程,目前Hook技術(shù)實(shí)現(xiàn)Activity的插件化主要有2種解決方案 融蹂,一種是通過(guò)Hook IActivityManager來(lái)實(shí)現(xiàn)文黎,而另一種則是Hook Instrumentation實(shí)現(xiàn)。接下來(lái)殿较,我們來(lái)看一下具體的實(shí)現(xiàn)過(guò)程耸峭。聲明一下,我這邊的源碼版本是基于Android 8.0的淋纲。

二劳闹、Hook IActivityManager實(shí)現(xiàn)Activity插件化

2.1 AndroidManifest.xml中注冊(cè)占坑Activity

很簡(jiǎn)單,我們創(chuàng)建一個(gè)SubActivity洽瞬,并且在AndroidManifest.xml中進(jìn)行注冊(cè)本涕,目的就是用來(lái)占坑。

    <application
        android:name=".application.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".activity.StubActivity" />

    </application>

2.2 使用占坑Activity通過(guò)AMS校驗(yàn)

仔細(xì)想一下伙窃,我們的目標(biāo)其實(shí)很簡(jiǎn)單菩颖,就是在AMS執(zhí)行startActivity()方法之前,將要啟動(dòng)的插件Activity替換成占坑Activity为障。而調(diào)用AMS的startActivity()方法是由AMS在本地的代理對(duì)象來(lái)完成的晦闰,所以我們就把目光聚焦到了這個(gè)AMS本地代理對(duì)象放祟。

Activity啟動(dòng)流程源碼解析中我們提到,關(guān)于獲取AMS代理對(duì)象的方式呻右,Android 8.0和之前的版本是有一些差別的跪妥。Android 8.0采用的是AIDL的實(shí)現(xiàn)方式獲取AMS的代理對(duì)象,而Android 8.0之前是通過(guò)ActivityManagerNative.getDefault()來(lái)獲取AMS的代理對(duì)象的声滥。不過(guò)這個(gè)對(duì)我們影響不是很大眉撵,做好兼容處理就行。

    // Android 8.0源碼
    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };


    // Android 8.0之前源碼
    static public IActivityManager getDefault() {
        return gDefault.get();
    }
     private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

由上述源碼可知落塑,不管是Android 8.0纽疟,還是Android之前,最終返回的AMS本地代理對(duì)象都是IActivityManager類型的對(duì)象憾赁。因此污朽,IActivityManager就是一個(gè)很好的Hook點(diǎn),我們只需要去攔截它的startActivity()方法缠沈,并且將要啟動(dòng)的插件Activity替換成占坑Activity膘壶。為了簡(jiǎn)單起見(jiàn),省略了加載插件Activity的過(guò)程洲愤,直接創(chuàng)建了一個(gè)PluginActivity來(lái)代表已經(jīng)加載進(jìn)來(lái)的插件Activity颓芭,并且沒(méi)有在AndroidManifest.xml中進(jìn)行注冊(cè)。同時(shí)柬赐,由于IActivityManager又是一個(gè)接口亡问,所以我們完全可以采用動(dòng)態(tài)代理來(lái)攔截它的startActivity()方法,具體實(shí)現(xiàn)如下:

public class IActivityManagerProxy implements InvocationHandler {

    private static final String TAG = "IActivityManagerProxy";
    private Object mActivityManager;

    public IActivityManagerProxy(Object activityManager) {
        this.mActivityManager = activityManager;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startActivity".equals(method.getName())) {
            Log.e(TAG, "invoke startActivity");
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            // 獲取啟動(dòng)PluginActivity的Intent
            Intent pluginIntent = (Intent) args[index];

            // 新建用來(lái)啟動(dòng)StubActivity的Intent
            Intent stubIntent = new Intent();
            stubIntent.setClassName("com.lxbnjupt.pluginactivitydemo",
                    "com.lxbnjupt.pluginactivitydemo.activity.StubActivity");
            // 將啟動(dòng)PluginActivity的Intent保存在subIntent中肛宋,便于之后還原
            stubIntent.putExtra(HookHelper.PLUGIN_INTENT, pluginIntent);

            // 通過(guò)stubIntent賦值給args州藕,從而將啟動(dòng)目標(biāo)變?yōu)镾tubActivity,以此達(dá)到通過(guò)AMS校驗(yàn)的目的
            args[index] = stubIntent;
        }
        return method.invoke(mActivityManager, args);
    }
}

之后酝陈,創(chuàng)建代理類IActivityManagerProxy床玻,并且使用IActivityManagerProxy去替換原來(lái)的IActivityManager即可:

    /**
     * Hook IActivityManager
     *
     * @throws Exception
     */
    public static void hookAMS() throws Exception {
        Log.e(TAG, "hookAMS");
        Object singleton = null;
        if (Build.VERSION.SDK_INT >= 26) {
            Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");
            // 獲取ActivityManager中的IActivityManagerSingleton字段
            Field iActivityManagerSingletonField = ReflectUtils.getField(activityManageClazz, "IActivityManagerSingleton");
            singleton = iActivityManagerSingletonField.get(activityManageClazz);
        } else {
            Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
            // 獲取ActivityManagerNative中的gDefault字段
            Field gDefaultField = ReflectUtils.getField(activityManagerNativeClazz, "gDefault");
            singleton = gDefaultField.get(activityManagerNativeClazz);
        }

        Class<?> singletonClazz = Class.forName("android.util.Singleton");
        // 獲取Singleton中mInstance字段
        Field mInstanceField = ReflectUtils.getField(singletonClazz, "mInstance");
        // 獲取IActivityManager
        Object iActivityManager = mInstanceField.get(singleton);

        Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
        // 獲取IActivityManager代理對(duì)象
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[]{iActivityManagerClazz}, new IActivityManagerProxy(iActivityManager));

        // 將IActivityManager代理對(duì)象賦值給Singleton中mInstance字段
        mInstanceField.set(singleton, proxy);
    }

2.3 還原插件Activity

在AMS執(zhí)行startActivity()方法之前,我們使用占坑Activity替換插件Activity沉帮,從而通過(guò)了AMS的校驗(yàn)锈死。但是,我們真正要啟動(dòng)的是插件Activity穆壕,那么勢(shì)必還是要替換回來(lái)的待牵。那么,回想一下Activity的啟動(dòng)流程喇勋,我們的目標(biāo)就換成了要在ActivityThread執(zhí)行handleLaunchActivity()方法之前缨该,將占坑Activity替換回插件Activity。ActivityThread會(huì)通過(guò)Handler內(nèi)部類H將代碼的邏輯切換到主線程中川背,H中重寫(xiě)的handleMessage方法會(huì)對(duì)LAUNCH_ACTIVITY類型的消息進(jìn)行處理贰拿,我們可以將H的mCallback作為Hook點(diǎn)蛤袒。

public class HCallback implements Handler.Callback {

    private static final int LAUNCH_ACTIVITY = 100;
    Handler mHandler;

    public HCallback(Handler handler) {
        mHandler = handler;
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == LAUNCH_ACTIVITY) {
            Object obj = msg.obj;
            try {
                // 獲取啟動(dòng)SubActivity的Intent
                Intent stubIntent = (Intent) ReflectUtils.getField(obj.getClass(), "intent", obj);

                // 獲取啟動(dòng)PluginActivity的Intent(之前保存在啟動(dòng)SubActivity的Intent之中)
                Intent pluginIntent = stubIntent.getParcelableExtra(HookHelper.PLUGIN_INTENT);

                // 將啟動(dòng)SubActivity的Intent替換為啟動(dòng)PluginActivity的Intent
                stubIntent.setComponent(pluginIntent.getComponent());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mHandler.handleMessage(msg);
        return true;
    }
}

由上述代碼可知,HCallback實(shí)現(xiàn)了Handler.Callback壮不,并重寫(xiě)了handleMessage方法汗盘,當(dāng)收到消息的類型為L(zhǎng)AUNCH_ACTIVITY時(shí)皱碘,將啟動(dòng)占坑Activity的Intent替換為啟動(dòng)插件Activity的Intent询一。
之后,我們創(chuàng)建HCallback的實(shí)例癌椿,并且用它來(lái)替換H的mCallback:

    /**
     * Hook ActivityThread中Handler成員變量mH
     *
     * @throws Exception
     */
    public static void hookHandler() throws Exception {
        Log.e(TAG, "hookHandler");
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
        // 獲取ActivityThread中成員變量sCurrentActivityThread字段
        Field sCurrentActivityThreadField = ReflectUtils.getField(activityThreadClazz, "sCurrentActivityThread");
        // 獲取ActivityThread主線程對(duì)象
        Object currentActivityThread = sCurrentActivityThreadField.get(activityThreadClazz);

        // 獲取ActivityThread中成員變量mH字段
        Field mHField = ReflectUtils.getField(activityThreadClazz, "mH");
        // 獲取ActivityThread主線程中Handler對(duì)象
        Handler mH = (Handler) mHField.get(currentActivityThread);

        // 將我們自己的HCallback對(duì)象賦值給mH的mCallback
        ReflectUtils.setField(Handler.class, "mCallback", mH, new HCallback(mH));
    }

2.4 測(cè)試運(yùn)行

自定義Application健蕊,調(diào)用hookAMS()方法、hookHandler() 方法:

public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            // 通過(guò)Hook IActivityManager實(shí)現(xiàn)Activity插件化
            HookHelper.hookAMS();
            HookHelper.hookHandler();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MainActivity代碼:

public class MainActivity extends AppCompatActivity {

    private Button btnStartPluginActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnStartPluginActivity = (Button) findViewById(R.id.tv_start_plugin_activity);
        btnStartPluginActivity.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, PluginActivity.class);
                startActivity(intent);
            }
        });
    }
}

運(yùn)行程序缩功,當(dāng)我們點(diǎn)擊啟動(dòng)插件Activity按鈕,發(fā)現(xiàn)啟動(dòng)的是插件PluginActivity嫡锌。

三、Hook Instrumentation實(shí)現(xiàn)Activity插件化

3.1 替換和還原插件Activity

Hook Instrumentation實(shí)現(xiàn)Activity插件化的思想同樣也是使用占坑Activity势木,與Hook IActivityManager不同的地方是替換和還原的地方不同而已,而且相對(duì)來(lái)說(shuō)會(huì)稍微簡(jiǎn)潔一些啦桌。

由Activity啟動(dòng)流程可知,啟動(dòng)一個(gè)Activity的過(guò)程中會(huì)調(diào)用到Instrumentation的execStartActivity()方法甫男,在此方法中我們可以用占坑Activity來(lái)替換插件Activity,以此來(lái)通過(guò)AMS的驗(yàn)證验烧。然后板驳,在回到ActivityThread主線程的performLaunchActivity方法中時(shí),會(huì)調(diào)用Instrumentation的newActivity方法創(chuàng)建Activity碍拆,在此方法中我們可以用插件Activity來(lái)替換占坑Activity若治。

public class InstrumentationProxy extends Instrumentation {

    private Instrumentation mInstrumentation;
    private PackageManager mPackageManager;

    public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
        this.mInstrumentation = instrumentation;
        this.mPackageManager = packageManager;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        // 查找要啟動(dòng)的Activity是否已經(jīng)在AndroidManifest.xml中注冊(cè)
        List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        if (infos == null || infos.size() == 0) {
            // 要啟動(dòng)的Activity沒(méi)有注冊(cè),則將啟動(dòng)它的Intent保存在Intent中倔监,便于之后還原
            intent.putExtra(HookHelper.PLUGIN_INTENT, intent.getComponent().getClassName());
            // 替換要啟動(dòng)的Activity為StubActivity
            intent.setClassName(who, "com.lxbnjupt.pluginactivitydemo.activity.StubActivity");
        }
        try {
            Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
            // 通過(guò)反射調(diào)用execStartActivity方法直砂,將啟動(dòng)目標(biāo)變?yōu)镾tubActivity,以此達(dá)到通過(guò)AMS校驗(yàn)的目的
            return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token,
                    target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
            IllegalAccessException, ClassNotFoundException {
        String intentName = intent.getStringExtra(HookHelper.PLUGIN_INTENT);
        if (!TextUtils.isEmpty(intentName)) {
            // 還原啟動(dòng)目標(biāo)Activity
            return super.newActivity(cl, intentName, intent);
        }
        return super.newActivity(cl, className, intent);
    }
}

接著,我們需要?jiǎng)?chuàng)建InstrumentationProxy對(duì)象浩习,并且讓其替換主線程中的Instrumentation對(duì)象即可:

    /**
    /**
     * Hook Instrumentation
     *
     * @param context 上下文環(huán)境
     * @throws Exception
     */
    public static void hookInstrumentation(Context context) throws Exception {
        Log.e(TAG, "hookInstrumentation");
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
        // 獲取ActivityThread中成員變量sCurrentActivityThread字段
        Field sCurrentActivityThreadField = ReflectUtils.getField(activityThreadClazz, "sCurrentActivityThread");
        // 獲取ActivityThread中成員變量mInstrumentation字段
        Field mInstrumentationField = ReflectUtils.getField(activityThreadClazz, "mInstrumentation");
        // 獲取ActivityThread主線程對(duì)象(應(yīng)用程序啟動(dòng)后就會(huì)在attach方法中賦值)
        Object currentActivityThread = sCurrentActivityThreadField.get(activityThreadClazz);
        // 獲取Instrumentation對(duì)象
        Instrumentation instrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
        PackageManager packageManager = context.getPackageManager();
        // 創(chuàng)建Instrumentation代理對(duì)象
        InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation, packageManager);

        // 用InstrumentationProxy代理對(duì)象替換原來(lái)的Instrumentation對(duì)象
        ReflectUtils.setField(activityThreadClazz, "mInstrumentation", currentActivityThread, instrumentationProxy);
    }

3.2 測(cè)試運(yùn)行

自定義Application静暂,調(diào)用hookInstrumentation()方法:

public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            // 通過(guò)Hook Instrumentation實(shí)現(xiàn)Activity插件化
            HookHelper.hookInstrumentation(base);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MainActivity的代碼我就不貼了,跟Hook IActivityManager實(shí)現(xiàn)Activity插件化里面是一毛一樣的谱秽。同樣洽蛀,運(yùn)行程序摹迷,當(dāng)我們點(diǎn)擊啟動(dòng)插件Activity按鈕,發(fā)現(xiàn)啟動(dòng)的是插件PluginActivity郊供。

總結(jié)

Activity的插件化實(shí)現(xiàn)過(guò)程峡碉,實(shí)質(zhì)就是兩個(gè)字,那就是模仿驮审。通過(guò)對(duì)Activity啟動(dòng)流程的源碼分析鲫寄,了解系統(tǒng)啟動(dòng)Activity的整個(gè)過(guò)程,并且模仿系統(tǒng)的行為疯淫,找到其中的Hook點(diǎn)地来,從而最終實(shí)現(xiàn)Activity的插件化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末熙掺,一起剝皮案震驚了整個(gè)濱河市未斑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌币绩,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缆镣,死亡現(xiàn)場(chǎng)離奇詭異芽突,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)费就,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門诉瓦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人力细,你說(shuō)我怎么就攤上這事睬澡∩反希” “怎么了逝慧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)云稚。 經(jīng)常有香客問(wèn)我沈堡,道長(zhǎng),這世上最難降的妖魔是什么鲸拥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任刑赶,我火速辦了婚禮,結(jié)果婚禮上金踪,老公的妹妹穿的比我還像新娘谒所。我一直安慰自己沛申,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布尖淘。 她就那樣靜靜地躺著村生,像睡著了一般饼丘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卫病,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天典徘,我揣著相機(jī)與錄音,去河邊找鬼帜平。 笑死梅鹦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的齐唆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼抛腕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了担敌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤马昙,失蹤者是張志新(化名)和其女友劉穎行楞,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體子房,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡证杭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年解愤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乎莉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哼鬓,死狀恐怖肥橙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情存筏,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布予跌,位于F島的核電站券册,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏航邢。R本人自食惡果不足惜骄蝇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一九火、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勒极,春花似錦、人聲如沸辱匿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)媒楼。三九已至划址,卻和暖如春限府,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胁勺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工署穗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人封恰。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓褐啡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子许昨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355