Android主线程到底是什么

转自: https://blog.csdn.net/u011631275/article/details/47337385

很多做Android应用层开发的同学都知道UI主线程这个说法,且很多初学者在它身上吃了不少亏,常见的错误如“在主线程中做耗时的操作,比如下载大文件”。尽管我们已经非常熟悉这个所谓的“主线程”了,但是恐怕并没有多少人对它有一个清晰的认识。
作为一个Android应用层的开发人员,如果你想开发出性能更优,体验更好的app,那么了解它是必须的。所有的ui效果,事件处理都是在主线程执行的,我们怎么能不了解它呢?接下来开始我们的代码之旅吧。
在阅读下文之前,我希望大家能准备一份android系统源代码,我的是4.0的,然后是一个阅读源码的工具,我用的是source insight。

(一)、简单来说说主线程是如何启动的

我们的故事从哪里开始说起呢?首先考虑这么个问题:主线程是什么时候会启动?我们都知道当我们点击手机应用列表的图标后,应用就会启动,所以这里必然会有主线程的启动。手机的桌面、应用列表等其实也是一个app,这个应用叫做Launcher。
在系统源码目录下,有一个packages目录,这个目录下都是android系统app的源代码,Launcher的源码就在里面。

我们找到路径android4.0\packages\apps\Launcher2\src\com\android\launcher2\Launcher.java文件,如果你的系统源码和我版本不一致,那可能会有不一样的路径。一般来说packages下或其子目录下一定有个应用叫Launcher2的应用,有个Launcher.java的类,如果你是用source insight阅读源码的,那很方便就能根据类名搜索到。

Launcher.java即应用列表的Activity,其中有一个onClick函数(我省略掉非重要代码),为点击图标的点击事件,我们来看看点击图标时,程序做了啥?

public void onClick(View v) {
               .....

        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {

            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
            boolean success = startActivitySafely(intent, tag);

           ....
        } else if (tag instanceof FolderInfo) {
             ....
        } else if (v == mAllAppsButton) {
           .....
        }
    }

boolean startActivitySafely(Intent intent, Object tag) {
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //注意这里的标志  是新的task
        try {
            startActivity(intent);
            return true;
        } catch (ActivityNotFoundException e) {
          ....
        } catch (SecurityException e) {
         ......
        }
        return false;
    }

原来Launcher内部启动应用也是通过startActivity。
我们先明确这么几个概念,这几个概念不了解的同学不需要去深究,而我也不可能去细说,因为光这两个机制足够写一本书。我们这里的重点是ui主线程。
android系统框架层有一个服务叫ActivityManagerService,它运行在系统的某个进程中。
android系统通过binder机制实现进程间通信,应用开发中常见的aidl就是对binder的一种封装。我们只要知道进程之间能通过这种机制远程调用其它进程的函数并传递数据。
我们继续,Launcher的startActivity运行在Launcher这个应用的进程中,它会通过binder机制告诉ActivityManagerSertvice我需要创建一个新的Activity了。ActivityManagerService中会根据intent中的flag Intent.FLAG_ACTIVITY_NEW_TASK判断出新的Activity是运行在新的进程中,于是会创建一个新的进程(新的进程最终是通过linux的fork函数拷贝zygote进程来实现的,学过linux系统开发的应该会很熟悉,这又是另一个故事了,我们这里不详细分析)。新的进程会加载ActivityThread.java类,并执行它的main函数, 主角终于出现了,自此,主线程从main函数开始执行了。
好了,知道了主线程是如何启动的,接下去才是这篇的重点。

(二)、主线程中到底做了什么

public static void main(String[] args) {
    ....

        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
      public static void prepareMainLooper() {
        prepare();
        setMainLooper(myLooper());
        myLooper().mQueue.mQuitAllowed = false;
    }

    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

  private Looper() {
        mQueue = new MessageQueue();
         ....
    }

Looper、MessageQueue有没有很熟悉,简单来说,android中的每个线程具备这样一种能力:创建自己独有的Looper对象,和一个独有的MessageQueue,MessageQueue是一个消息队列,里面存放有很多消息。Looper对象会通过loop()函数不断循环从MessageQueue中取出消息并处理,而当MessageQueue中没有消息时,loop()函数将阻塞在那,直到有消息为止。那么消息是谁塞进MessageQueue的呢,塞消息的方法有很多,但是android为我们封装了Handler来简化这种机制。

来看代码理解
prepare()中sThreadLocal为线程局部变量,即只当前线程可访问,如果当前线程还没有这个局部变量,就创建新的looper对象,我们来看Looper的构造函数,它内部创建了一个MessageQueue对象mQueue。

myLooper()获取前面创建的Looper对象

 public static Looper myLooper() {
        return sThreadLocal.get();
    }

setMainLooper将创建的Looper对象设置为主Looper。

ActivityThread thread = new ActivityThread(); 
thread.attach(false); 

下面是attach函数的关键代码 , ActivityManagerNative为ActivityManagerService的本地调用类,也就是说我们可以通过
ActivityManagerNative远程调用ActivityManagerService进程中的方法,以下代码其实就是远程调用ActivityManagerService的attachApplication(mAppThread)方法。

private void attach(boolean system) {
        sThreadLocal.set(this);
        mSystemThread = system;
        if (!system) {
           .....
            IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread); //注意里面传的参数
            } catch (RemoteException ex) {
                // Ignore
            }
        } else {
           ......
        }       
     ......
    }

见代码android4.0\frameworks\base\services\java\com\android\server\am\ActivityManagerService.java的attachApplication(IApplicationThread thread)函数

  public final void attachApplication(IApplicationThread thread) {
        synchronized (this) {
           ...
            attachApplicationLocked(thread, callingPid);
           ...
        }
    }

下面这段代码很长,但是我们只需要关心我们需要关注的代码,也就是会和主线程交互的部分,

private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
          ....
            thread.bindApplication(processName, appInfo, providers,
                    app.instrumentationClass, profileFile, profileFd, profileAutoStop,
                    app.instrumentationArguments, app.instrumentationWatcher, testMode,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    mConfiguration, app.compat, getCommonServicesLocked(),
                    mCoreSettingsObserver.getCoreSettingsLocked());

            .....
                    if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
                        didSomething = true;
                    }

         ....
    }

attachApplicationLocked中调用了IApplicationThread的bindApplication函数,IApplicationThread是什么?我们回到attach(boolean system)函数里,mgr.attachApplication(mAppThread);
final ApplicationThread mAppThread = new ApplicationThread();
其实IApplicationThread就是ApplicationThread的本地远程调用类,即这里在ActivityManagerService的进程中调用bindApplication函数,会通过binder机制最终调用ApplicationThread中的bindApplication函数。ApplicationThread就定义在ActivityThread.java类中,

public final void bindApplication(String processName,
                ApplicationInfo appInfo, List<ProviderInfo> providers,
                ComponentName instrumentationName, String profileFile,
                ParcelFileDescriptor profileFd, boolean autoStopProfiler,
                Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
                int debugMode, boolean isRestrictedBackupMode, boolean persistent,
                Configuration config, CompatibilityInfo compatInfo,
                Map<String, IBinder> services, Bundle coreSettings) {
            if (services != null) {
                // Setup the service cache in the ServiceManager
                ServiceManager.initServiceCache(services);
            }
            setCoreSettings(coreSettings);
            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.debugMode = debugMode;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfileFile = profileFile;
            data.initProfileFd = profileFd;
            data.initAutoStopProfiler = false;
//这句是最关键的代码
            queueOrSendMessage(H.BIND_APPLICATION, data);

}

 private void queueOrSendMessage(int what, Object obj) {
        queueOrSendMessage(what, obj, 0, 0);
    }

 private void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
        synchronized (this) {
            if (DEBUG_MESSAGES) Slog.v(
                TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
                + ": " + arg1 + " / " + obj);
            Message msg = Message.obtain();
            msg.what = what;
            msg.obj = obj;
            msg.arg1 = arg1;
            msg.arg2 = arg2;
            mH.sendMessage(msg);
        }
    }

看到这里是不是很熟悉,我们应用开发中最常用的Handler就是这么使用的,那么mH是不是个Handler呢

final H mH = new H();

private class H extends Handler { 
…. 
} 

果然如我们上面猜测的,它确实是个Handler,我们再来看看sendMessage内部实现

  public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

 public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

   public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
//注意这句  这里将Message的target变量赋值为当前Handler对象
            msg.target = this;
//这里就是向消息队列中添加一个Message
            sent = queue.enqueueMessage(msg, uptimeMillis);
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }

即bindApplication中通过Handler的sendMessage向当前线程的MessageQueue塞进了一个消息,这个消息的what值为H.BIND_APPLICATION。

好了,回到我们的ActivityThread的main函数中。 thread.attach(false);的分析到此为止,我们总结一下分析到现在,main函数中完成了哪些工作
1.Looper.prepareMainLooper();创建了一个Looper对象和一个MessageQueue对象
2.thread.attach(false);向MessageQueue队列中加入了一个Message,这个Message的what为H.BIND_APPLICATION

接着来看 Looper.loop();依然是留下最关键的代码

public static void loop() {
        Looper me = myLooper();
          ...
        MessageQueue queue = me.mQueue;

      ...

        while (true) {
            Message msg = queue.next(); // might block
              ....
                msg.target.dispatchMessage(msg);
               ....

                msg.recycle();
            }
        }

获取Looper对象,再从Looper对象中拿到MessageQueue对象,调用它的next()函数拿队列中下一个消息,然后调用Message对象的target成员的dispatchMessage函数,target其实就是一个Handler对象,回到上面的Handler的sendMessageAtTime函数中,有一句 msg.target = this;所以这里在取出消息后,调用的就是这个消息对应的Handler的dispatchMessage函数。
我们这里处理的第一个消息就是前面thread.attach(false);调用后向messageQueue中添加的消息,所以主线程在loop()函数中会调用H的dispatchMessage函数,H继承了Handler,但是并没有重写dispatchMessage函数,所以调用的还是Handler本身的dispatchMessage函数。

 public void dispatchMessage(Message msg) {

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

这里处理消息有三种方式,首先判断消息对象中的callback是不是空,不为空的话直接执行callback的run函数。为空的话在判断当前Handler有没有回调接口mCallback是不是为空,不为空就回调mCallback的handleMessage(msg)。最后才会调用Handler自身的 handleMessage(msg);
一般应用开发中最常用的处理方式就是最后一种。接下来我们就看H类的handleMessage(msg);

private class H extends Handler {
      ....
public static final int BIND_APPLICATION        = 110;


        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);
            switch (msg.what) {
                ...
                case BIND_APPLICATION:
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    break;
              ...
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
        }
      ....}

之前向消息队列发消息时 what值为H.BIND_APPLICATION,所以我们直接找到H.BIND_APPLICATION,取出msg中app相关的信息,然后调用handleBindApplication(data);

private void handleBindApplication(AppBindData data) {
      ......
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
      ...

        try {
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
        ....
        }
  }

handleBindApplication中调用makeApplication函数,该函数中根据app信息中Application类的路径加载并创建对象,然后调用callApplicationOnCreate函数。callApplicationOnCreate中调用Application对象的onCreate函数。原来我们在应用开发中常用的Application的onCreate就是这里调用的呀。

 public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {

        ....
        Application app = null;
        String appClass = mApplicationInfo.className;


            java.lang.ClassLoader cl = getClassLoader();
            ContextImpl appContext = new ContextImpl();
            appContext.init(this, null, mActivityThread);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
           ...

        return app;
    }

 public void callApplicationOnCreate(Application app) {
        app.onCreate();
    }

到这里我们第一个消息处理就完成了,但是loop()函数却不会退出,注意它是一个死循环,处理完一个紧接着取下一个消息。那些我们熟悉的Activity的onCreate onResume等回调函数都是在loop()中处理消息时执行的,关于Activity的创建我们在后面详细分析。

接着上面说,现在我们的主线程已经处理了第一个Message,在这个处理中执行了Application的onCreate函数,紧接着就是等待第二个消息了。
那么第二个消息是什么时候发送的呢?

private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid) {
          ....
            thread.bindApplication(processName, appInfo, providers, 
                    app.instrumentationClass, profileFile, profileFd, profileAutoStop,
                    app.instrumentationArguments, app.instrumentationWatcher, testMode,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    mConfiguration, app.compat, getCommonServicesLocked(),
                    mCoreSettingsObserver.getCoreSettingsLocked());

            .....
                    if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
                        didSomething = true;
                    }

         ....
    }

我们回到上一篇的attachApplicationLocked函数,在这个函数中,不仅调用thread.的bindApplication函数(这个函数的作用上一节已经说过了)。还调用了mMainStack.realStartActivityLocked(hr, app, true, true)

 final boolean realStartActivityLocked(ActivityRecord r,
            ProcessRecord app, boolean andResume, boolean checkConfig)
            throws RemoteException {
     ....
            app.thread.scheduleLaunchActivity(new Intent(r.intent), r,
                    System.identityHashCode(r), r.info, mService.mConfiguration,
                    r.compat, r.icicle, results, newIntents, !andResume,
                    mService.isNextTransitionForward(), profileFile, profileFd,
                    profileAutoStop);

    ...
}

realStartActivityLocked中再次远程调用ApplicationThread的scheduleLaunchActivity函数

 public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
                Bundle state, List<ResultInfo> pendingResults,
                List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
                String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
            ActivityClientRecord r = new ActivityClientRecord();
            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;
            r.startsNotResumed = notResumed;
            r.isForward = isForward;
            r.profileFile = profileName;
            r.profileFd = profileFd;
            r.autoStopProfiler = autoStopProfiler;
            updatePendingConfiguration(curConfig);

            //和上一篇一样  通过这个函数向MessageQueue中添加一个消息,what值为LAUNCH_ACTIVITY
            queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
        }

看到了吗,就是通过 queueOrSendMessage添加消息的,这个函数上一节已经有过说明。

loop()中取出该消息,分发到mH中处理该消息

public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                } break;
              ......
            }
         }

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      ....
        Activity a = performLaunchActivity(r, customIntent);
   ....
}
 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       .....
        Activity activity = null;
        try {

//获取类加载器,创建activity对象
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
          ....
        } catch (Exception e) {
           ....
        }
        try {
        //获取当前Application对象,因为前面已经创建过了,所以这里直接返回
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        ...
            if (activity != null) {
                ContextImpl appContext = new ContextImpl();
                appContext.init(r.packageInfo, r.token, this);
                appContext.setOuterContext(activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
             ...
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);
               ...
                activity.mCalled = false;
                mInstrumentation.callActivityOnCreate(activity, r.state);
              ...
                r.activity = activity;
                r.stopped = true;
                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                if (!r.activity.mFinished) {
                    if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
                }
                if (!r.activity.mFinished) {
                    activity.mCalled = false;
                    mInstrumentation.callActivityOnPostCreate(activity, r.state);
                ...
                }
            }
            r.paused = true;
            mActivities.put(r.token, r);
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }
        return activity;
}    

performLaunchActivity函数中主要做了这么几件事
1.创建Activity对象。2.初始化activity对象的上下文
3.为当前activity创建窗口。 4.分别调用activity的onCreate onResume等函数,完成应用程序希望在特定时刻完成的操作。

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config) {
        attachBaseContext(context);
        mFragments.attachActivity(this);

        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

      ....
    }

PolicyManager.makeNewWindow(this);
创建显示的窗口对象, mWindow.setCallback(this);
为窗口对象设置回调接口,Activity实现了该接口。

回到performLaunchActivity函数中,mInstrumentation.callActivityOnCreate(activity, r.state);

  public void callActivityOnCreate(Activity activity, Bundle icicle) {
       ....      
        activity.performCreate(icicle);

       ....
    }

 final void performCreate(Bundle icicle) {
        onCreate(icicle);
        mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
                com.android.internal.R.styleable.Window_windowNoDisplay, false);
        mFragments.dispatchActivityCreated();
    }

callActivityOnCreate中通过Activity的performCreate函数调用了Activity的onCreate和fragment的onCreate。

 if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }

首次打开activity, r.activity.mFinished==false,所有执行activity.performStart();内部调用了activity的onStart()和fragment的onStart函数。

mInstrumentation.callActivityOnPostCreate(activity, r.state);
调用Activity的onPostCreate()函数

回到 handleLaunchActivity函数中, handleResumeActivity(r.token, false, r.isForward);它内部调用Activity的onResume函数,并绘制了界面。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容