Android插件化之Activity篇

Android客戶端的業(yè)務(wù)越來(lái)越多并思,客戶端代碼量也越來(lái)越顯得臃腫庐氮,一般都采用組件化,將應(yīng)用進(jìn)行多個(gè)模塊開(kāi)發(fā)宋彼,但是同樣不會(huì)讓apk瘦起來(lái)弄砍,采用插件化則可以進(jìn)行熱拔插的方式進(jìn)行功能模塊使用起來(lái),現(xiàn)在就為你講解如何啟動(dòng)一個(gè)插件的Activity输涕。

首先我們得了解ClassLoader,Android在API中給出可動(dòng)態(tài)加載的有:DexClassLoader 和 PathClassLoader棚品。
DexClassLoader:可加載jar寄雀、apk和dex写隶,可以從SD卡中加載(本文使用這種方式)
PathClassLoader:只能加載已經(jīng)安裝搭配Android系統(tǒng)中的apk文件

我們先假設(shè)插件MuPlug.apk糙箍,是我們的一個(gè)插件apk,存放到/sdcard/目錄下。

首先在需要加載插件之間合并Dex文件到BaseDexClassLoader.dexElements中(我們的代碼都放到了這里)碴卧。

/**
 * Dex代碼注入類
 * Created by Hickey on 2017/6/4 on MuDynamicLoadingHost.
 */
public class DexInject {

    public static void inject(DexClassLoader dexClassLoader) {
        /** 拿到本應(yīng)用的PathClassLoader */
        PathClassLoader pathClassLoader = (PathClassLoader) AppContext.getAppContext().getClassLoader();
        try {
            /** 獲取宿主和插件pathList */
            Object mainObj = getPathList(pathClassLoader);
            Object plugObj = getPathList(dexClassLoader);
            /** 獲取組合之后的dexElements */
            Object dexElements = combineArray(getDexElements(mainObj), getDexElements(plugObj));
            /** 重新設(shè)置字段值 */
            setField(mainObj, mainObj.getClass(), dexElements, "dexElements");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    /**
     * 設(shè)置對(duì)象參數(shù)值
     *
     * @param dexPathList      此類對(duì)象
     * @param cls              此類類名
     * @param dexElementsValus 字段值
     * @param field            字段名稱
     * @throws NoSuchMethodException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static void setField(Object dexPathList, Class<?> cls, Object dexElementsValus, String field) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
        Field set = cls.getDeclaredField(field);
        set.setAccessible(true);
        set.set(dexPathList, dexElementsValus);
    }

    /**
     * 重新組合數(shù)組
     *
     * @param main
     * @param plug
     * @return
     */
    private static Object combineArray(Object main, Object plug) {
        /** 獲取原數(shù)組類型 */
        Class<?> loadClass = main.getClass().getComponentType();
        /** 獲取宿主DexElements的長(zhǎng)度 */
        int mainLen = Array.getLength(main);

        MuL.e("Host dex length:"+mainLen);

        /** 現(xiàn)在的長(zhǎng)度 */
        int curLen = Array.getLength(plug) + mainLen;

        Object result = Array.newInstance(loadClass, curLen);
        for (int i = 0; i < curLen; ++i) {
            if (i < mainLen) {
                Array.set(result, i, Array.get(main, i));
            } else {
                Array.set(result, i, Array.get(plug, i - mainLen));
            }
        }
        MuL.e("After adding the plugin Dex,length:" + curLen);
        return result;
    }

    /**
     * 反射獲取到DexPathList對(duì)象
     *
     * @param pathClassLoader 類加載
     * @return
     * @throws ClassNotFoundException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getPathList(Object pathClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        return getField(pathClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    /**
     * 獲取某個(gè)類的全局變量
     *
     * @param classLoader 對(duì)象
     * @param cls         類
     * @param field       字段名稱
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getField(Object classLoader, Class<?> cls, String field) throws NoSuchFieldException, IllegalAccessException {
        Field mField = cls.getDeclaredField(field);
        mField.setAccessible(true);
        return mField.get(classLoader);
    }

    /**
     * 獲取dexElements
     *
     * @param mPathList
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getDexElements(Object mPathList) throws NoSuchFieldException, IllegalAccessException {
        return getField(mPathList, mPathList.getClass(), "dexElements");
    }
}

我們知道啟動(dòng)Activity一般都需要在清單文件中聲明才可以正常使用弱卡,否則就出現(xiàn)找不到Activity的異常。

在這里我們需要代理 ActivityManagerNative中的IActivityManager對(duì)象

    /**
     * Retrieve the system's default/global activity manager.
     */
    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;
        }
    };
public static void onProxyActivityManagerNative(){
        try {
            Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
            /** 獲取gDefault的值 */
            Field gDefault = activityManagerNativeClass.getDeclaredField("gDefault");
            gDefault.setAccessible(true);
            Object objSingleton = gDefault.get(null);

            /** 獲取Singleton對(duì)象 */
            Class<?> clsSingleton = Class.forName("android.util.Singleton");

            /** 獲取Singleton T 對(duì)象 */
            Field field = clsSingleton.getDeclaredField("mInstance");
            field.setAccessible(true);
            Object objIActivityManager  = field.get(objSingleton);

            Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{
                    iActivityManagerInterface
            }, new IActivityManagerHandler(objIActivityManager));

            field.set(objSingleton, proxy);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

這個(gè)IActivityManagerHandler:

/**
 * Created by Hickey on 2017/6/4 on MuDynamicLoadingHost.
 */
public class IActivityManagerHandler implements InvocationHandler {

    public static final String EXTRA_INTENT = "EXTRA_INTENT";

    private Object objIActivityManager;

    public IActivityManagerHandler(Object objIActivityManager) {
        this.objIActivityManager = objIActivityManager;
    }

    /**
     * 代理某些ActivityManager的某些方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //當(dāng)接收到應(yīng)用使用startActivity方法的時(shí)候
        if ("startActivity".equals(method.getName())){
            Pair<Integer,Intent> mPair = onFoundFirstIntentOfArgs(args);
            /** Create proxy component name */
            String pkgName = AppContext.getAppContext().getPackageName();
            String clzName = ProxyActivity.class.getName();
            ComponentName mComponentName = new ComponentName(pkgName,clzName);

            Intent pIntent = new Intent();
            pIntent.setComponent(mComponentName);
            /** Will save the real intention object */
            pIntent.putExtra(EXTRA_INTENT,mPair.second);

            /** Replace intention */
            args[mPair.first] = pIntent;
        }
        return method.invoke(objIActivityManager, args);
    }

    /**
     * 獲取對(duì)象和參數(shù)下標(biāo)
     * @param args
     * @return
     */
    private Pair<Integer, Intent> onFoundFirstIntentOfArgs(Object... args) {
        int index = 0;
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Intent) {
                index = i;
                break;
            }
        }
        return Pair.create(index, (Intent) args[index]);
    }
}

當(dāng)IActivityManager的startActivity方法被執(zhí)行的時(shí)候

android.app.IActivityManager ;

public interface IActivityManager extends IInterface {
    public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
            ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
}

我們替換掉intent參數(shù)對(duì)象住册,換成我們的ProxyActivity,從而繞過(guò)AMS的檢測(cè)婶博。那我們什么時(shí)候換回來(lái)呢,我們繼續(xù)hook...

我們應(yīng)用都是被ActivityThread的控制來(lái)調(diào)度的,通過(guò)內(nèi)部類H來(lái)進(jìn)行分發(fā)消息的

final H mH = new H();

private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY          = 101;
        public static final int PAUSE_ACTIVITY_FINISHING= 102;
        public static final int STOP_ACTIVITY_SHOW      = 103;
        public static final int STOP_ACTIVITY_HIDE      = 104;
        public static final int SHOW_WINDOW             = 105;
        .....
}

所以我們需要?jiǎng)?chuàng)建自己的Handler.CallBack對(duì)象來(lái)處理這些消息

public static void onProxyActivityThreadmH(){
        try {
            Class<?> cls = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = cls.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            /** 執(zhí)行方法得到ActivityThread對(duì)象 */
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            /** 由于ActivityThread一個(gè)進(jìn)程只有一個(gè),我們獲取這個(gè)對(duì)象的mH */
            Field mHField = cls.getDeclaredField("mH");
            mHField.setAccessible(true);
            /**得到H這個(gè)Handler*/
            Handler mH = (Handler) mHField.get(currentActivityThread);

            Field mCallBackField = Handler.class.getDeclaredField("mCallback");
            mCallBackField.setAccessible(true);
            mCallBackField.set(mH, new ActivityThreadHanderCallBack(mH));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
public class ActivityThreadHanderCallBack implements Handler.Callback{

    private Handler mH;

    public static final int LAUNCH_ACTIVITY = 100;

    public ActivityThreadHanderCallBack(Handler mH) {
        this.mH = mH;
    }

    @Override
    public boolean handleMessage(Message message) {
        switch (message.what) {
            case LAUNCH_ACTIVITY:
                launcherActivity(message);
                break;
        }
        mH.handleMessage(message);
        return true;
    }

    private void launcherActivity(Message message) {
        Object obj = message.obj;//ActivityClientRecord
        try {
            //ActivityClientRecord取出里面的Intent對(duì)象
            Field intentField = obj.getClass().getDeclaredField("intent");
            intentField.setAccessible(true);
            Intent proxyInent = (Intent) intentField.get(obj);
            //得到真實(shí)要啟動(dòng)的Activity的Inetnt
            Intent realIntent = proxyInent.getParcelableExtra(IActivityManagerHandler.EXTRA_INTENT);
            proxyInent.setComponent(realIntent.getComponent());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
static final class ActivityClientRecord {
        IBinder token;
        int ident;
        Intent intent;//這個(gè)實(shí)際就是我們的ProxyActivity對(duì)象對(duì)應(yīng)Intent
        String referrer;
        IVoiceInteractor voiceInteractor;
        Bundle state;
        PersistableBundle persistentState;
        Activity activity;
        Window window;
        Activity parent;
        String embeddedID;
        Activity.NonConfigurationInstances lastNonConfigurationInstances;
        boolean paused;
        boolean stopped;
        boolean hideForNow;
        Configuration newConfig;
        Configuration createdConfig;
        Configuration overrideConfig;
        // Used for consolidating configs before sending on to Activity.
        private Configuration tmpConfig = new Configuration();
        ActivityClientRecord nextIdle;

        ProfilerInfo profilerInfo;
        .......  
}

這樣我們就繞過(guò)了AMS去驗(yàn)證清單文件是否注冊(cè)的問(wèn)題了荧飞。

我們這樣就大功告成了凡人?沒(méi)有運(yùn)行項(xiàng)目:

 Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.android.mudl/com.android.mudl.plug.PlugMainActivity}
            at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:284)
            at android.support.v7.app.AppCompatDelegateImplV7.onCreate(AppCompatDelegateImplV7.java:152)
            at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:46)
            at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:73)
            at com.android.mudl.plug.PlugMainActivity.onCreate(PlugMainActivity.java:14)
            at android.app.Activity.performCreate(Activity.java:6910)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1123)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2746)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2864) 
            at android.app.ActivityThread.-wrap12(ActivityThread.java) 
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1567) 
            at com.hickey.mudl.ActivityThreadHanderCallBack.handleMessage(ActivityThreadHanderCallBack.java:31) 
            at android.os.Handler.dispatchMessage(Handler.java:101) 
            at android.os.Looper.loop(Looper.java:156) 
            at android.app.ActivityThread.main(ActivityThread.java:6524) 
            at java.lang.reflect.Method.invoke(Native Method) 
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:941) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:831) 

從錯(cuò)誤日志看到,我們實(shí)際已經(jīng)偷梁換柱成功叹阔,可是在使用AppCompatActivity時(shí)挠轴,它又去向PackageManger去檢測(cè)父類Activity,沒(méi)找到条获。那怎么辦,我們繼續(xù)hook蒋歌!

//同樣的從ActivityThread入手帅掘,找到sPackageManager,代理它
static IPackageManager sPackageManager;

//具體反射代碼
public static void onHookIPackageManager() {
        try {
            // 兼容AppCompatActivity報(bào)錯(cuò)問(wèn)題
            Class<?> forName = Class.forName("android.app.ActivityThread");
            Field field = forName.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object activityThread = field.get(null);
            Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
            Object iPackageManager = getPackageManager.invoke(activityThread);

            PackageManagerHandler handler = new PackageManagerHandler(iPackageManager);
            Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class<?>[]{iPackageManagerIntercept}, handler);

            // 獲取 sPackageManager 屬性
            Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
            iPackageManagerField.setAccessible(true);
            iPackageManagerField.set(activityThread, proxy);
        }catch (Exception e){
            MuL.e("onHookIPackageManager:"+e.toString());
        }

這里是找到上面驗(yàn)證失敗的方法getActivityInfo堂油,將里面的ComponentName對(duì)象換成ProxyActivity的修档。

public static class PackageManagerHandler implements InvocationHandler {
        public Object iPackageManager;
        public PackageManagerHandler(Object iPackageManager) {
            this.iPackageManager = iPackageManager;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("getActivityInfo".equals(method.getName())){
                   for (int i=0;i<args.length;i++){
                       if (args[i] instanceof ComponentName){
                           ComponentName componentName = new ComponentName(AppContext.getAppContext().getPackageName(), ProxyActivity.class.getName());
                           args[i] = componentName;
                       }
                   }
            }
            return method.invoke(iPackageManager,args);
        }
    }

你會(huì)發(fā)現(xiàn)插件中的使用布局怎么是宿主的布局:

宿主的界面截圖
實(shí)際的布局截圖

資源加載又成了一個(gè)問(wèn)題
找到ActivityThread

final ArrayMap<String, WeakReference<LoadedApk>> mPackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();

替換掉LoadedApk中的mResDir參數(shù):變成我們插件的路徑:

public static void switchToPlugResources(String resPath) {
        try {
            String packageName = AppContext.getAppContext().getPackageName();
            //獲取LoadedApk的Class
            Class<?> loadApkCls = Class.forName("android.app.LoadedApk");
            //獲取ActivityThread的Class
            Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");

            //獲取ActivityThread對(duì)象
            Method currentActivityThreadMethod = activityThreadCls.getMethod("currentActivityThread");
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            //反射獲取mPackages中的LoadedApk
            Field filed = activityThreadCls.getDeclaredField("mPackages");
            filed.setAccessible(true);
            Map mPackages = (Map) filed.get(currentActivityThread);
            WeakReference wr = (WeakReference) mPackages.get(packageName);

            Field filed2 = loadApkCls.getDeclaredField("mResDir");
            filed2.setAccessible(true);
            filed2.set(wr.get(), resPath);
        }catch (Exception e){
            MuL.e("changeResDir:"+e.toString());
        }
    }

這樣就成功啟動(dòng)插件中的Activity且支持AppCompatActivity.

由于當(dāng)前應(yīng)用的資源路徑變換了,我們需要在適當(dāng)?shù)氖菍①Y源路徑變回來(lái)府框。

我們通過(guò)如上方式重新將資源路徑變換回來(lái),所有方法的調(diào)用順序如下

public class InitRunable implements Runnable {
        @Override
        public void run() {

            MuL.e("Step1:Merge plugins and host Dex.");
            String cacheDir = MainActivity.this.getCacheDir().getAbsolutePath();
            String apkPath = Environment.getExternalStorageDirectory() + File.separator + "MuPlug.apk";
            DexClassLoader dexClassLoader = new DexClassLoader(apkPath, cacheDir, cacheDir, getClassLoader());
            DexInject.inject(dexClassLoader);

            MuL.e("Step2:Agent IActivityManager Object.");
            ActivityManagerHook.onProxyActivityManagerNative();

            MuL.e("Step3:Agent ActivityThread mH object.");
            ActivityThreadHandlerHook.onProxyActivityThreadmH();


            /*MuL.e("Step4:Get ActivityThread sInstrumentation Object.");
            ActivityThreadHandlerHook.onProxyActivityInstrumentation(MainActivity.this);*/

            /** Switch to the plug-in resource directory */
            MuL.e("Step4:Switch to the plug-in resource directory");
            LoadApkResDir.switchToPlugResources(apkPath);

            IPackageManagerHook.onHookIPackageManager();

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    compatButton.setEnabled(true);
                }
            });
        }
    }

//將資源路徑變成我們的apk路徑
LoadApkResDir.switchToPlugResources(getApplicationInfo().sourceDir);

筆記本沒(méi)有電量了吱窝,有點(diǎn)不詳細(xì),請(qǐng)見(jiàn)諒迫靖!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末院峡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子系宜,更是在濱河造成了極大的恐慌照激,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盹牧,死亡現(xiàn)場(chǎng)離奇詭異俩垃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)汰寓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)口柳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人有滑,你說(shuō)我怎么就攤上這事跃闹。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵辣卒,是天一觀的道長(zhǎng)掷贾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)荣茫,這世上最難降的妖魔是什么想帅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮啡莉,結(jié)果婚禮上港准,老公的妹妹穿的比我還像新娘。我一直安慰自己咧欣,他們只是感情好浅缸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著魄咕,像睡著了一般衩椒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哮兰,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天毛萌,我揣著相機(jī)與錄音,去河邊找鬼喝滞。 笑死阁将,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的右遭。 我是一名探鬼主播做盅,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窘哈!你這毒婦竟也來(lái)了吹榴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤滚婉,失蹤者是張志新(化名)和其女友劉穎腊尚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體满哪,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婿斥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哨鸭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片民宿。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖像鸡,靈堂內(nèi)的尸體忽然破棺而出活鹰,到底是詐尸還是另有隱情哈恰,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布志群,位于F島的核電站着绷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锌云。R本人自食惡果不足惜荠医,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桑涎。 院中可真熱鬧彬向,春花似錦、人聲如沸攻冷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)等曼。三九已至里烦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禁谦,已是汗流浹背胁黑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枷畏,地道東北人别厘。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓虱饿,卻偏偏與公主長(zhǎng)得像拥诡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子氮发,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

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