從啟動activity開始分析插件化

我想在最開始再廢一些周章砌庄,來把啟動的過程略微的闡述一下蔫缸,為的就是接下去描述插件化痛點的時候,你會更加的身臨其境肠槽。

activity的啟動過程如下:

  • Launcher是系統(tǒng)的apk乡小,點擊界面圖標會調用相應的Activity.startActivity阔加,或者是直接從ActivityA啟動ActivityB
  • contextImpl.startActivity()->Instrumentation.executeActivity()
  • ActivityManagerNative.getDefault().startActivity()->AMS.startActivity
    Instrumentation通過aidl進行跨進程通信,最終調用AMS的startActivity方法
  • ActivityStarter.startActivityMayWait()->startActivityLocked()->startActivityUnchecked()
  • ActivityStack.resumeFocusedStackTopActivityLocked()->resumeTopActivityUncheckedLocked()->resumeTopActivityInnerLocked()
  • ActivityStackSupervisor.startSpecificActivityLocked()->realStartActivityLocked()(如果應用還沒有啟動那么走mService.startProcessLocked方法)
  • app.thread.scheduleLaunchActivity()-> sendMessage(H.LAUNCH_ACTIVITY, r)
  • H.handleMessage()->handleLaunchActivity()->performLaunchActivity()

如果是初次啟動應用劲件,那么我們接著上面的mService.startProcessLocked去看掸哑,直接請求Zygote給啟動應用fork一個進程出來

  • startProcessLocked()(經過幾次重載跳轉)->
Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);

->Process.startViaZygote() 請求Zygote進程為其生成新進程

  • 在startViaZygote方法中設置完各種參數(shù)之后约急,最終調用zygoteSendArgsAndGetResult方法零远,向Zygote發(fā)送創(chuàng)建進程的請求。
    內部使用socket通信
zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote)
  • Zygote內部的不斷循環(huán)方法runSelectLoop會接受到這個請求
if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    boolean done = peers.get(i).runOnce();
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }

先進行包裝成ZygoteConnection對象厌蔽,并將socket的文件標識做保存牵辣。然后在下一次的循環(huán)中去執(zhí)行runOnce

  • 在runOnce方法中
            pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,parsedArgs.appDataDir);
通過這個方法去創(chuàng)建進程,實際上就是fork出一個新的虛擬機實例奴饮。
  • 如果fork成功了纬向,那么返回的pid==0
if (pid == 0) {
                // in child
                IoUtils.closeQuietly(serverPipeFd);
                serverPipeFd = null;
                handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);

                // should never get here, the child is expected to either
                // throw ZygoteInit.MethodAndArgsCaller or exec().
                return true;
            } else {
                // in parent...pid of < 0 means failure
                IoUtils.closeQuietly(childPipeFd);
                childPipeFd = null;
                return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
            }
 我們接著去執(zhí)行handleChildProc
  • 這里我們做的就是關閉socket通道,并且啟動新的進程
private void handleChildProc(Arguments parsedArgs,
            FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
            throws ZygoteInit.MethodAndArgsCaller {
        //關閉socket
        closeSocket();
        ZygoteInit.closeServerSocket();

        ...

        // End of the postFork event.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        if (parsedArgs.invokeWith != null) {
            WrapperInit.execApplication(parsedArgs.invokeWith,
                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
                    VMRuntime.getCurrentInstructionSet(),
                    pipeFd, parsedArgs.remainingArgs);
        } else {
            //基本走的都是這種啟動方式戴卜,通過參數(shù)逾条,去尋找目標類的main方法并執(zhí)行
            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                    parsedArgs.remainingArgs, null /* classLoader */);
        }
    }
  • zygoteInit
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
            throws ZygoteInit.MethodAndArgsCaller {
        ...
        //初始化
        commonInit();
        //native的初始化
        nativeZygoteInit();
        //application的初始化
        applicationInit(targetSdkVersion, argv, classLoader);
    }
主要的就是applicationInit方法,設置虛擬機的堆大小投剥,和虛擬機的sdkversion
 VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
      VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);

并且通過反射的方式去觸發(fā)目標類的main方法

 try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }

        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }
 最終觸發(fā)师脂,ActivityThread的main方法

對于activity啟動的總結

  • activityA啟動ActivityB

    由contextImp發(fā)起的startActivity跳轉到instrumentation的executeStartActivity然后交由ActivityStarter做啟動準備,分別又經過ActivityStack棧,觀察是否有棧頂Activity可以直接用來resume吃警,最終交由ActivityStackSupervisor來realStartActivityLocked糕篇,然后通過applicationThread的scheduleLaunchActivity方法發(fā)送消息給H的handler,經過處理執(zhí)行handleLaunchActivity到performLaunchActivity

  • Launcher點擊圖標啟動應用

    依然有Activity.startActivity啟動應用酌心,直到ActivityStackSupervisor的startSpecificActivityLocked通過啟動service的startProcessLocked拌消,這個是AMS的方法,通過Socket通道安券,請求Zygote系統(tǒng)進程為目標應用分配VM虛擬機墩崩。分配成功之后關閉通道,并且通過反射的方式執(zhí)行目標類的main方法侯勉,也就是ActivityThread的main方法泰鸡。


走進插件化

講在前面

我們前面廢了相當多的周章,來把啟動的過程詳細的闡述了一下(其實也沒有很詳細)壳鹤,為的就是接下去闡述痛點的時候盛龄,你會更加的身臨其境。

本文是基于android7.1的最新源碼分析芳誓,你會發(fā)現(xiàn)google在不斷的修改并強化activity的啟動過程余舶,它會想方設法的去阻止你進行插件化修改,因為插件化的方式從某些層面來講锹淌,并不利于android的生態(tài)發(fā)展匿值。所以你會發(fā)現(xiàn)這篇插件化講解中的啟動的源碼是這樣的,而到了那篇就變了個樣赂摆,實際以最新版本源碼為主挟憔。

首先,對于插件化烟号,你需要先了解它的模式

插件化干什么

比如說我們的主app相當?shù)木薮蟀硖罚锩婕闪舜罅康哪K,我們的app可能有300+mb汪拥,那么這個時候客戶可能就很不愿意去下載您的app达传。

我們需要把我們app的主干功能保存下來,放在主app當中迫筑,然后將其余的模塊功能做成插件的方式宪赶,在主app需要使用到的時候再去調用。

這樣我們可以做到

  • 并行開發(fā)脯燃,快速迭代
  • 模塊間解耦
  • 按需加載搂妻,節(jié)省內存
  • 動態(tài)升級,我們只需要更新插件就可以做到功能更新辕棚,而無需更新整個app

碰到的坎

那么很明顯了欲主,好處相當多追他,我們開始準備著手去做了,我們搞了兩個apk岛蚤,一個是主apk邑狸,一個是插件apk。我們琢磨著在主apk中去啟動插件apk中的activity涤妒。但是發(fā)現(xiàn)行不通单雾。報了一個平時開發(fā)中也會遇到的exception,當你沒有將你需要啟動的activity在manifest文件中注冊的時候就會出現(xiàn)的錯誤她紫。

Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {org.ding.testmulti/org.ding.testmulti.JustTest}; have you declared this activity in your AndroidManifest.xml?

怎么辦硅堆?我們先通過錯誤提示定位到出問題的所在地

at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1794)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1512)

前往execStartActivity方法,問題就出在下面這段代碼

int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
checkStartActivityResult(result, intent);

也就是說跨進程訪問的AMS的startActivity返回的result是錯誤的result贿讹,我們經過一番定位最終發(fā)現(xiàn)問題出在ActivityStarter的startActivityMayWait方法中渐逃,

ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);

返回的是空,

 ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) {
        try {
            return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType,
                    PackageManager.MATCH_DEFAULT_ONLY | flags
                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
        } catch (RemoteException e) {
        }
        return null;
    }

最終我們走的PMS的resolveIntent民褂,如果你看過我的應用安裝分析的話(Android app安裝過程分析(基于Nougat),你應該會了解茄菊,在應用安裝的過程中,PMS會將Manifest文件中的內容依次讀出赊堪,并保存在packageInfo當中面殖,以供使用

最終在PMS.java文件中找到resolveIntent方法

@Override
    public ResolveInfo resolveIntent(Intent intent, String resolvedType,
            int flags, int userId) {
        try {
            ...
            final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
                    flags, userId);
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

            final ResolveInfo bestChoice =
                    chooseBestActivity(intent, resolvedType, flags, query, userId);
            return bestChoice;
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }

也就是說queryIntentActivitiesInternal方法返回的列表為空,并沒有找到與intent匹配的activity哭廉。

當前流行的解決方案

當前失眠流行的插件化代理解決方案有很多脊僚,但是基本的原理無外乎如下兩類。分別是hookInstrumentation和hookActivityManagerNative遵绰,此處的hook我們可以把它理解成貍貓換太子辽幌。就是利用我們自己偽造的來騙過系統(tǒng),從而達到插件化的效果椿访。

hookInstrumention

市面上使用hookInstrumentation方法的是Small框架乌企,在它的ApkBundleLauncher類中的onCreate方法:


@Override
    public void onCreate(Application app) {
        super.onCreate(app);
        Object/*ActivityThread*/ thread;
        List<ProviderInfo> providers;
        Instrumentation base;
        ApkBundleLauncher.InstrumentationWrapper wrapper;
        Field f;
        // Get activity thread
        thread = ReflectAccelerator.getActivityThread(app);

        // Replace instrumentation
        try {
            //拿到當前thread的Instrumentation對象
            //我們主要大費周章的把Instrumentation這個對象取出來是為了對其進行保存,要注意赎离,只要是hook了對象逛犹,那么除非真的沒有必要
            //一般都需要對這個對象進行緩存,以防不時之需
            f = thread.getClass().getDeclaredField("mInstrumentation");
            f.setAccessible(true);
            base = (Instrumentation) f.get(thread);
            wrapper = new ApkBundleLauncher.InstrumentationWrapper(base);
            f.set(thread, wrapper);
        } catch (Exception e) {
            throw new RuntimeException("Failed to replace instrumentation for thread: " + thread);
        }

        ...
    }

我們在InstrumentationWrapper中看到它重寫了execStartActivity方法梁剔,來進行替換


         /** @Override V21+
         * Wrap activity from REAL to STUB */
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, android.os.Bundle options) {
            wrapIntent(intent);
            return ReflectAccelerator.execStartActivity(mBase,
                    who, contextThread, token, target, intent, requestCode, options);
        }

        /** @Override V20-
         * Wrap activity from REAL to STUB */
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode) {
            wrapIntent(intent);
            return ReflectAccelerator.execStartActivity(mBase,
                    who, contextThread, token, target, intent, requestCode);
        }

wrapIntent(intent)方法包裝intent,然后對activity進行替換舞蔽,將realActivity替換成我們提前在Manifest中注冊好的Activity荣病。
在Small的最新版本中,將Activity再替換回來是放在Hook的Handler.CallBack中的(之前是直接走的mInstrumentation的newActivity來進行Activity的復原)

// Inject message handler
        try {
            f = thread.getClass().getDeclaredField("mH");
            f.setAccessible(true);
            Handler ah = (Handler) f.get(thread);
            f = Handler.class.getDeclaredField("mCallback");
            f.setAccessible(true);
            f.set(ah, new ApkBundleLauncher.ActivityThreadHandlerCallback());
        } catch (Exception e) {
            throw new RuntimeException("Failed to replace message handler for thread: " + thread);
        }


 @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY:
                    //在handler收到LAUNCH_ACTIVITY的信號時進行進行替換
                    redirectActivity(msg);
                    break;

                case CREATE_SERVICE:
                    ensureServiceClassesLoadable(msg);
                    break;

                default:
                    break;
            }

            return false;
        }

HookActivityManagerNative

而DroidPlugin使用的是將ActivityManagerNative整個的Hook掉渗柿,具體我們見ProxyHool類

public abstract class ProxyHook extends Hook implements InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        try {
            if (!isEnable()) {
                return method.invoke(mOldObj, args);
            }
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(mOldObj, method, args);
            }
            return method.invoke(mOldObj, args);
        }

        ......
}

拿出具體的HookedMethodHandler个盆,然后調用其doHookInner方法

public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
        long b = System.currentTimeMillis();
        try {
            mUseFakedResult = false;
            mFakedResult = null;
            boolean suc = beforeInvoke(receiver, method, args);
            Object invokeResult = null;
            if (!suc) {
                invokeResult = method.invoke(receiver, args);
            }
            afterInvoke(receiver, method, args, invokeResult);
            if (mUseFakedResult) {
                return mFakedResult;
            } else {
                return invokeResult;
            }
        } finally {
            long time = System.currentTimeMillis() - b;
            if (time > 5) {
                Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time);
            }
        }
    }

通過AOP的方式分別在其方法前后進行hook
我們此處替換掉的ActivityManagerNative脖岛,就是在IActivityManagerHookHandle中的startActivity類然后我們看他的beforeInvoke方法

@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {

    RunningActivities.beforeStartActivity();
    boolean bRet;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
        bRet = doReplaceIntentForStartActivityAPILow(args);
    } else {
        bRet = doReplaceIntentForStartActivityAPIHigh(args);
    }
    if (!bRet) {
        setFakedResult(Activity.RESULT_CANCELED);
        return true;
    }

    return super.beforeInvoke(receiver, method, args);
}

doReplaceIntentForStartActivityAPIHigh這個方法也是將真Activity信息保存,然后用stubActivity來進行替換颊亮。

至于再換回來也是用的callBack柴梆,實際上DroidPlugin要比Small要更早的使用Hook callBack的方式
PluginCallbackHook.java

@Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        Object target = ActivityThreadCompat.currentActivityThread();
        Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass();

        /*替換ActivityThread.mH.mCallback,攔截組件調度消息*/
        Field mHField = FieldUtils.getField(ActivityThreadClass, "mH");
        Handler handler = (Handler) FieldUtils.readField(mHField, target);
        Field mCallbackField = FieldUtils.getField(Handler.class, "mCallback");
        //*這里讀取出舊的callback并處理*/
        Object mCallback = FieldUtils.readField(mCallbackField, handler);
        if (!PluginCallback.class.isInstance(mCallback)) {
            PluginCallback value = mCallback != null ? new PluginCallback(mHostContext, handler, (Handler.Callback) mCallback) : new PluginCallback(mHostContext, handler, null);
            value.setEnable(isEnable());
            mCallbacks.add(value);
            FieldUtils.writeField(mCallbackField, handler, value);
            Log.i(TAG, "PluginCallbackHook has installed");
        } else {
            Log.i(TAG, "PluginCallbackHook has installed,skip");
        }
    }

PluginCallback.java

private boolean handleLaunchActivity(Message msg) {
        try {
            Object obj = msg.obj;
            Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
            //ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true);
            stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
            Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
            // 這里多加一個isNotShortcutProxyActivity的判斷终惑,因為ShortcutProxyActivity的很特殊绍在,啟動它的時候,
            // 也會帶上一個EXTRA_TARGET_INTENT的數(shù)據(jù)雹有,就會導致這里誤以為是啟動插件Activity偿渡,所以這里要先做一個判斷。
            // 之前ShortcutProxyActivity錯誤復用了key霸奕,但是為了兼容溜宽,所以這里就先這么判斷吧。
            if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) {
                IPackageManagerHook.fixContextPackageManager(mHostContext);
                ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
                ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
                if (targetActivityInfo != null) {

                    if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
                        targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
                    }

                    ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0);
                    ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null;
                    if (stubActivityInfo != null) {
                        PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName);
                    }
                    PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
                    ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
                    setIntentClassLoader(targetIntent, pluginClassLoader);
                    setIntentClassLoader(stubIntent, pluginClassLoader);

                    ...

                    Log.i(TAG, "handleLaunchActivity OK");
                } else {
                    Log.e(TAG, "handleLaunchActivity oldInfo==null");
                }
            } else {
                Log.e(TAG, "handleLaunchActivity targetIntent==null");
            }
        } catch (Exception e) {
            Log.e(TAG, "handleLaunchActivity FAIL", e);
        }

        if (mCallback != null) {
            return mCallback.handleMessage(msg);
        } else {
            return false;
        }
    }

至此兩種最經典的插件化方式分析完畢质帅,之后我們再來看框架的具體使用心得

TBC适揉。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末煤惩,一起剝皮案震驚了整個濱河市涡扼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盟庞,老刑警劉巖吃沪,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異什猖,居然都是意外死亡票彪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門不狮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來降铸,“玉大人,你說我怎么就攤上這事摇零⊥频В” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵驻仅,是天一觀的道長谅畅。 經常有香客問我,道長噪服,這世上最難降的妖魔是什么毡泻? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮粘优,結果婚禮上仇味,老公的妹妹穿的比我還像新娘呻顽。我一直安慰自己,他們只是感情好丹墨,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布廊遍。 她就那樣靜靜地躺著,像睡著了一般贩挣。 火紅的嫁衣襯著肌膚如雪喉前。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天揽惹,我揣著相機與錄音被饿,去河邊找鬼。 笑死搪搏,一個胖子當著我的面吹牛狭握,可吹牛的內容都是我干的。 我是一名探鬼主播疯溺,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼论颅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤藏否,失蹤者是張志新(化名)和其女友劉穎扒磁,沒想到半個月后扬霜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盾鳞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞻离,死狀恐怖腾仅,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情套利,我是刑警寧澤推励,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站肉迫,受9級特大地震影響验辞,放射性物質發(fā)生泄漏。R本人自食惡果不足惜昂拂,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一受神、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧格侯,春花似錦鼻听、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至朝墩,卻和暖如春醉拓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背收苏。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工亿卤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鹿霸。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓排吴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親懦鼠。 傳聞我的和親對象是個殘疾皇子钻哩,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內容