插件化之啟動沒有注冊的Activity

啟動沒有在AndroidManifest中注冊的Activity是安卓插件化中一個很重要的知識點,只有這樣你才能把Activity中分離出來,放到插件中.

啟動沒有在AndroidManifest中注冊的Activity,會涉及到Activity啟動流程弄喘、反射透绩、動態(tài)代理的知識,我覺得就算不學插件化,掌握這些知識也是很有用的.

Activity的啟動流程

為了達到啟動沒有在AndroidManifest中注冊的Activity的目的,我們先來分析下Activity的啟動流程,看看有沒有什么突破口.

這部分的知識我在《從源碼看Activity生命周期》這篇博客里面其實也有講過,這里只做大概的講解,然后做一些補充,感興趣的同學可以將兩篇博客結合起來看看.

拋出ActivityNotFoundException的原因

如果使用startActivity去啟動一個沒有在AndroidManifest中注冊的Activity,正常情況下是會拋出ActivityNotFoundException的,那這個異常是怎么拋出來的呢?

我們知道調(diào)用Activity.startActivity方法,實際上最后是調(diào)用了Instrumentation.execStartActivity:

public class Instrumentation {
  ...

  public ActivityResult execStartActivity(
                  Context who, IBinder contextThread, IBinder token, Activity target,
                  Intent intent, int requestCode, Bundle options) {
      ...
      int result = ActivityManagerNative.getDefault()
                         .startActivity(whoThread, who.getBasePackageName(), intent,
                                      intent.resolveTypeIfNeeded(who.getContentResolver()),
                                      token, target != null ? target.mEmbeddedID : null,
                                      requestCode, 0, null, null, options);
      checkStartActivityResult(result, intent);
      ...
  }

  ...

  public static void checkStartActivityResult(int res, Object intent) {
        ...
        switch (res) {
              case ActivityManager.START_INTENT_NOT_RESOLVED:
              case ActivityManager.START_CLASS_NOT_FOUND:
                  if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                      throw new ActivityNotFoundException(
                              "Unable to find explicit activity class "
                              + ((Intent)intent).getComponent().toShortString()
                              + "; have you declared this activity in your AndroidManifest.xml?");
                  throw new ActivityNotFoundException(
                          "No Activity found to handle " + intent);
              ...
        }
        ...
    }

    ...
}

可以看到Instrumentation又是通過ActivityManagerNative.getDefault()拿到一個IActivityManager去調(diào)用其startActivity來啟動Activity的.

這個IActivityManager內(nèi)部實際是通過Binder機制將處理轉(zhuǎn)發(fā)給ActivityManagerService:

public abstract class ActivityManagerNative extends Binder implements IActivityManager
    ...

    static public IActivityManager getDefault() {
       return gDefault.get();
    }

    ...

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            //實際上是用Binder機制與AMS進行交互
            IBinder b = ServiceManager.getService("activity");
            IActivityManager am = asInterface(b);
            return am;
        }
    };

    ...
}

所以可以看到通過ActivityManagerService去startActivity之后會有個返回值.

ActivityManagerService內(nèi)部會使用PackageManagerService查詢這個Activity是否在AndroidManifest中注冊.如果沒有,就會返回START_CLASS_NOT_FOUND或者START_INTENT_NOT_RESOLVED,這個時候Instrumentation就會拋出ActivityNotFoundException.

所以ActivityNotFoundException就是這樣被拋出的.

Activity是怎樣被創(chuàng)建的

我們都知道兩個不同的進程直接是不能直接訪問內(nèi)存的,所以處于應用進程的Activity肯定還是應用進程去創(chuàng)建,而不是被AMS創(chuàng)建的.

這塊的代碼在ActivityThread中實現(xiàn):

public final class ActivityThread {
    ...
    final H mH = new H();

    ...
    @Override
    public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
            CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
            int procState, Bundle state, PersistableBundle persistentState,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
            boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
        ...
        sendMessage(H.LAUNCH_ACTIVITY, r);
    }

    ...
    private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        ...

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ...
            }
            ...
    }
    ...
}

AMS會調(diào)用ActivityThread的scheduleLaunchActivity,在這個方法中會使用一個Hander同步到主線程中再去創(chuàng)建Activity.

Activity啟動的原理圖

1.png

怎樣欺騙ActivityManagerService

從上面的Activity啟動的原理圖可以看到大概的流程是:

應用將要啟動的Activity告訴AMS->AMS檢查Activity是否注冊->AMS讓ActivityThread去創(chuàng)建Activity.

那是不是可以這樣呢?

  1. 新建一個StubActivity并且在AndroidManifest中注冊
  2. 將想要啟動的Activity換成StubActivity,而將真正想要啟動的Activity保存到Extra中
  3. 騙過AMS
  4. 在ActivityThread中拿出真正想要創(chuàng)建的Activity換回來去創(chuàng)建

修改后的原理如下:

2.png

將要啟動的Activity替換成StubActivity

第一步是將要啟動的Activity替換成StubActivity,我們回顧下上一節(jié)看到的ActivityManagerNative代碼:

public abstract class ActivityManagerNative extends Binder implements IActivityManager
    ...

    static public IActivityManager getDefault() {
       return gDefault.get();
    }

    ...

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            //實際上是用Binder機制與AMS進行交互
            IBinder b = ServiceManager.getService("activity");
            IActivityManager am = asInterface(b);
            return am;
        }
    };

    ...
}

可以看到這個gDefault其實是個靜態(tài)的私有成員變量.

那我們是不是可以通過反射,將它替換成我們寫的Singleton<IActivityManager>,然后保存好原來的gDefault,在替換的代碼里面先將要啟動的Activity替換成StubActivity,然后再將Intent傳給原來的gDefault?

大概的做法如下:


class MyActivityManager implements IActivityManager {
    private IActivityManager mOrigin;

    public MyActivityManager(IActivityManager origin) {
        mOrigin = origin;
    }
    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 {
        // TODO 將要啟動的activity替換成StubActivity

        return mOrigin. startActivity(caller, callingPackage, intent,
            resolvedType, resultTo, resultWho, requestCode, flags,
            profilerInfo, options);
    }
    ...
}

Class c = Class.forName("android.app.ActivityManagerNative");
final Field field =  c.getDeclaredField("gDefault");
field.setAccessible(true);

Singleton<IActivityManager> proxy = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        return new MyActivityManager(field.get(null));
    }
};

field.set(null, proxy);

但是這個做法問題很大,首先我們要將IActivityManager的所有方法都實現(xiàn)一遍轉(zhuǎn)發(fā)給mOrigin匠璧。而且最大的問題是IActivityManager和Singleton被隱藏了,我們在應用層是找不到定義的!

那怎么辦呢?別急,我們先來看看Singleton的實現(xiàn):

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

其實最終的IActivityManager是保存在mInstance這個變量里面的,我們只需要替換這個變量就好,于是就繞過了Singleton沒有定義的問題岭埠。但是還有這個IActivityManager的定義問題擺在我們面前。

怎么辦呢?答案就是我們可以用動態(tài)代理的方法去創(chuàng)建IActivityManager靠闭。關于動態(tài)代理我之前寫過一篇博客 《Java自定義注解和動態(tài)代理》 ,大家感興趣的話可以去看看。這里就直接把代碼貼上了:

// 獲取gDefault
Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);

// 獲取mIntance
Class singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object mInstance = mInstanceField.get(gDefault);

// 替換mIntance
Object proxy = Proxy.newProxyInstance(
        mInstance.getClass().getClassLoader(),
        new Class[]{Class.forName("android.app.IActivityManager")},
        new IActivityManagerHandler(mInstance));
mInstanceField.set(gDefault, proxy);


public static class IActivityManagerHandler implements InvocationHandler {
    private Object mOrigin;

    IActivityManagerHandler(Object origin) {
        mOrigin = origin;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startActivity".equals(method.getName())) {
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            Intent raw = (Intent) args[index];

            Intent intent = new Intent();
            intent.setClassName(raw.getComponent().getPackageName(), StubActivity.class.getName());
            intent.putExtra("RawIntent", raw);
            args[index] = intent;
        }
        return method.invoke(mOrigin, args);
    }
}

上面的代碼的功能就是創(chuàng)建一個IActivityManager的代理,代理startActivity方法,將啟動的Activity的Intent換成啟動StubActivity的Intent,并且將原來的Intent保存起來放到RawIntent這個Extra里坎炼。

然后用它去替換ActivityManagerNative.gDefault的mInstance成員變量愧膀。

將StubActivity替換會要啟動的Activity

在上面我們已經(jīng)將要啟動的Activity替換成了已經(jīng)注冊了的StubActivity,這樣在AMS檢查的時候就能在AndroidManifest查到,不會報ActivityNotFoundException了.

然后AMS會讓ActivityThread去創(chuàng)建Activity,這個時候就要將StubActivity替換會真正要啟動的Activity了.

再回顧下這部分的代碼:

public final class ActivityThread {
    ...
    final H mH = new H();

    ...
    @Override
    public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
            CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
            int procState, Bundle state, PersistableBundle persistentState,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
            boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
        ...
        sendMessage(H.LAUNCH_ACTIVITY, r);
    }

    ...
    private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        ...

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ...
            }
            ...
    }
    ...
}

ActivityThread的scheduleLaunchActivity方法會被調(diào)到,然后會向mH發(fā)送LAUNCH_ACTIVITY消息.

所以關鍵點就是將這個mH變量替換成我們的代理對象,將Intent替換回之前保存的RawIntent.

但是這里有個問題,H是個內(nèi)部類,我們是沒有辦法用動態(tài)代理的方式創(chuàng)建內(nèi)部類的,也就是說我們沒有辦法替換掉mH這個對象.

于是只好繼續(xù)挖一挖Handler內(nèi)部有沒有機會了,其實在Handler.dispatchMessage里面是會先判斷mCallback是不是有賦值的,如果有就會將消息交給它去處理.

public class Handler {
    ...
    final Callback mCallback;
    ...
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    ...
}

所以我們可以從這個mCallback入手,將mH的mCallback設置成我們的代理對象:

// 獲取ActivityThread實例
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field threadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
threadField.setAccessible(true);
Object sCurrentActivityThread = threadField.get(null);

// 獲取mH變量
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(sCurrentActivityThread);

// 設置mCallback變量
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
Handler.Callback callback = new Handler.Callback() {
   @Override
   public boolean handleMessage(Message msg) {
       if (msg.what == 100) {
           try {
               Field intentField = msg.obj.getClass().getDeclaredField("intent");
               intentField.setAccessible(true);
               Intent intent = (Intent) intentField.get(msg.obj);
               Intent raw = intent.getParcelableExtra("RawIntent");
               intent.setComponent(raw.getComponent());
           } catch (Exception e) {
               Log.e("hook", "get intent err", e);
           }

       }
       return false;
   }
};
mCallbackField.set(mH, callback);

ActivityThread的實例保存在sCurrentActivityThread這個靜態(tài)成員變量里,代碼我就不貼了,然后我們在mCallback這里將要啟動的Activity設置回來.

處理Android 8.0的情況

上面的代碼運行在8.0的系統(tǒng)上會崩潰,原因是8.0對Activity的啟動這塊做了些改動,不再使用ActivityManagerNative.getDefault()了,改成了ActivityManager.getService():

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
    ...
    int result = ActivityManager.getService()
        .startActivity(whoThread, who.getBasePackageName(), intent,
                intent.resolveTypeIfNeeded(who.getContentResolver()),
                token, target != null ? target.mEmbeddedID : null,
                requestCode, 0, null, options);
    checkStartActivityResult(result, intent);
    ...
}

ActivityManager其實和ActivityManagerNative很像:

public class ActivityManager {
    ...
    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;
              }
          };
    ...
}

所以我們類似的去替換IActivityManagerSingleton就好了:

// 獲取IActivityManagerSingleton
Class activityManagerClass = Class.forName("android.app.ActivityManager");
Field singletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object gDefault = singletonField.get(null);

// 獲取mIntance
Class singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object mInstance = mInstanceField.get(gDefault);

// 替換mIntance
Object proxy = Proxy.newProxyInstance(
        mInstance.getClass().getClassLoader(),
        new Class[]{Class.forName("android.app.IActivityManager")},
        new IActivityManagerHandler(mInstance));
mInstanceField.set(gDefault, proxy);

處理AppCompatActivity的情況

到目前為止,我們已經(jīng)可以正常啟動沒有注冊的Activity了,但是其實還有一個BUG:如果啟動的是沒有注冊的AppCompatActivity就會崩潰。

10-25 19:32:30.867  8754  8754 E AndroidRuntime: Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{me.linjw.plugindemo/me.linjw.plugindemo.HideActivity}
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:285)
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:158)
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:58)
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at com.cvte.tv.speech.TestActivity.onCreate(TestActivity.java:14)
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at android.app.Activity.performCreate(Activity.java:6664)
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
10-25 19:32:30.867  8754  8754 E AndroidRuntime:        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)

網(wǎng)上很多講啟動未注冊的Activity的文章要不就沒有講這個,要不就沒有詳細講如何處理,直接一筆帶過了.這里我手把手帶大家解BUG.

遇到問題先不要慌,先看看打印找到崩潰的代碼在哪:

@Nullable
public static String getParentActivityName(Activity sourceActivity) {
    try {
        return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
    } catch (NameNotFoundException e) {
        // Component name of supplied activity does not exist...?
        throw new IllegalArgumentException(e);
    }
}

@Nullable
public static String getParentActivityName(Context context, ComponentName componentName)
        throws NameNotFoundException {
    PackageManager pm = context.getPackageManager();
    ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
    String parentActivity = IMPL.getParentActivityName(context, info);
    return parentActivity;
}

很明顯是PackageManager.getActivityInfo在AndroidManifest里面找不到Activity拋出了NameNotFoundException.

所以我們看看有沒有辦法替換一下這個Context.getPackageManager()拿到的PackageManager:

class ContextImpl extends Context {
    ...
    @Override
    public PackageManager getPackageManager() {
        if (mPackageManager != null) {
            return mPackageManager;
        }

        IPackageManager pm = ActivityThread.getPackageManager();
        if (pm != null) {
            // Doesn't matter if we make more than one instance.
            return (mPackageManager = new ApplicationPackageManager(this, pm));
        }

        return null;
    }
    ...
}

ContextImpl會從ActivityThread.getPackageManager獲取IPackageManager,讓我們繼續(xù)挖:

public final class ActivityThread {
    ...
    static volatile IPackageManager sPackageManager;
    ...
    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
            return sPackageManager;
        }
        IBinder b = ServiceManager.getService("package");
        //Slog.v("PackageManager", "default service binder = " + b);
        sPackageManager = IPackageManager.Stub.asInterface(b);
        //Slog.v("PackageManager", "default service = " + sPackageManager);
        return sPackageManager;
    }
    ...
}

所以sPackageManager就是我們的突破點,讓我們來把它換掉:

try {
    //要先獲取一下,保證它初始化
    context.getPackageManager();

    Class activityThread = Class.forName("android.app.ActivityThread");
    Field pmField = activityThread.getDeclaredField("sPackageManager");
    pmField.setAccessible(true);
    final Object origin = pmField.get(null);
    Object handler = Proxy.newProxyInstance(activityThread.getClassLoader(),
            new Class[]{Class.forName("android.content.pm.IPackageManager")},
            new PackageManagerHandler(context, origin));
    pmField.set(null, handler);
} catch (Exception e) {
    Log.e("hook", "hook IPackageManager err", e);
}

static class PackageManagerHandler implements InvocationHandler {
        private Context mContext;
        private Object mOrigin;

        PackageManagerHandler(Context context, Object origin) {
            mContext = context;
            mOrigin = origin;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (!method.getName().equals("getActivityInfo")) {
                return method.invoke(mOrigin, args);
            }

            //如果沒有注冊,并不會拋出異常,而是會直接返回null
            Object ret = method.invoke(mOrigin, args);
            if (ret == null) {
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof ComponentName) {
                        ComponentName componentName = (ComponentName) args[i];
                        componentName.getClassName();
                        args[i] = new ComponentName(
                            mContext.getPackageName(),
                            StubActivity.class.getName()
                        );
                        return method.invoke(mOrigin, args);
                    }
                }
            }
            return ret;

        }
    }

在IPackageManager.getActivityInfo方法拋出異常的時候invoke會返回null,就代表這個Activity沒有注冊,我們直接將他換成StubActivity就好谣光。

大功告成!

完整Demo

完整Demo見我的Github

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末檩淋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子萄金,更是在濱河造成了極大的恐慌蟀悦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氧敢,死亡現(xiàn)場離奇詭異日戈,居然都是意外死亡,警方通過查閱死者的電腦和手機孙乖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門浙炼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人唯袄,你說我怎么就攤上這事弯屈。” “怎么了恋拷?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵资厉,是天一觀的道長。 經(jīng)常有香客問我梅掠,道長酌住,這世上最難降的妖魔是什么店归? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮酪我,結果婚禮上消痛,老公的妹妹穿的比我還像新娘。我一直安慰自己都哭,他們只是感情好秩伞,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著欺矫,像睡著了一般纱新。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上穆趴,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天脸爱,我揣著相機與錄音,去河邊找鬼未妹。 笑死簿废,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的络它。 我是一名探鬼主播族檬,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼化戳!你這毒婦竟也來了单料?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤点楼,失蹤者是張志新(化名)和其女友劉穎扫尖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盟步,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡藏斩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年躏结,在試婚紗的時候發(fā)現(xiàn)自己被綠了却盘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡媳拴,死狀恐怖黄橘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屈溉,我是刑警寧澤塞关,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站子巾,受9級特大地震影響帆赢,放射性物質(zhì)發(fā)生泄漏小压。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一椰于、第九天 我趴在偏房一處隱蔽的房頂上張望怠益。 院中可真熱鬧,春花似錦瘾婿、人聲如沸蜻牢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抢呆。三九已至,卻和暖如春笛谦,著一層夾襖步出監(jiān)牢的瞬間抱虐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工饥脑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梯码,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓好啰,卻偏偏與公主長得像轩娶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子框往,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

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