Android Handler详解(附面试题)

Handler模型.png

可以将Handler模型理解为:生产者—消费者 模型。
该模型中,生产者在子线程中生产Message,调用Handler对象的sendMessage()等方法,将Message加入到MessageQueue中;Looper.loop()死循环从MessageQueue中取出Message,然后调用Handler对象的handleMessage()方法在主线程中消耗掉Message。

1.源码分析

想要弄清楚Handler,得先理解Thread和ThreadLocal

1.1 Thread和ThreadLocal

public class Thread implements Runnable {
    ...
    ThreadLocalMap threadLocals = null;
    ...
}

从Thread源码中得知:

  1. 每一个Thread都有一个ThreadLocalMap类型的成员变量:threadLocals
  2. ThreadLocalMap定义在ThreadLocal中
public class ThreadLocal<T> {

    public ThreadLocal() {
    }
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    static class ThreadLocalMap {
        ...
    }
}

从ThreadLocal源码中得知:

  1. ThreadLocal并不是一个Thread,只不过Thread使用了ThreadLocal中定义的ThreadLocalMap。
  2. 由createMap方法可知,ThreadLocal负责创建当前线程对应Thread对象的ThreadLocalMap
  3. getMap方法获取的是当前线程对应Thread对象的threadLocals属性
  4. set方法是将某一个对象添加到当前线程对应Thread对象的threadLocals中,get方法同理。

测试ThreadLocal:

public class MyTest {
    /**
     * 多个线程可以共用一个ThreadLocal
     */
    private static ThreadLocal<Person> mThreadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        new MyThread("张三").start();
        new MyThread("李四").start();
    }

    static class MyThread extends Thread {
        private Person mPerson;
        public MyThread(String name) {
            mPerson = new Person(name);
        }
        @Override
        public void run() {
            super.run();
            // ThreadLocal的set、get方法操作的是当前线程对应Thread对象的threadLocals属性
            mThreadLocal.set(mPerson);
            Person person = mThreadLocal.get();
            System.out.println(Thread.currentThread() + "----" + person.getName());
        }
    }

    static class Person {
        private String mName;
        public Person(String name) {
            mName = name;
        }
        public String getName() {
            return mName;
        }
        public void setName(String name) {
            this.mName = name;
        }
    }
}

测试结果:

Thread[Thread-0,5,main]----张三
Thread[Thread-1,5,main]----李四

ThreadLocal总结:
ThreadLocal提供了线程局部变量,每个线程都可以通过set和get方法来对这个局部变量进行操作,且不会和其他线程的局部变量冲突,实现了线程的数据隔离。

1.2 Looper、MessageQueue、Message、Handler

Looper和Handler都持有MessageQueue的引用,那么是谁创建的MessageQueue?
查看Looper的源码,从Looper的构造函数可知,MessageQueue是由Looper创建的。

public final class Lopper {
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    @UnsupportedAppUsage
    final MessageQueue mQueue;
    final Thread mThread;
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    public static void prepare() {
        prepare(true);
    }

    @Deprecated
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    public static void loop() {
        final Looper me = myLooper();
        ...
        me.mInLoop = true;
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {
            ...
            Message msg = queue.next(); // might block
            ...
            try {
                msg.target.dispatchMessage(msg);
            }
        }
    }
    public void quit() {
        mQueue.quit(false);
    }
}

从Looper源码中得知:

  1. Looper的构造函数私有化,不能通过new创建Looper对象,需要使用Looper.prepare()来创建Looper对象
  2. sThreadLocal.set(new Looper(quitAllowed));得知可以在当前线程的其他地方使用sThreadLocal.get()获取Looper对象
  3. loop()方法里面写了一个死循环,不断的调用MessageQueue的next()方法获取Message对象,并调用Handler的dispatchMessage(msg)方法(msg.target指的就是Handler对象)
public class Handler {
    // Handler的构造函数有很多个,这里只展示其中的两个
    
    public Handler(@Nullable Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    // Handler的子类必须实现handleMessage方法
    public void handleMessage(@NonNull Message msg) {
    }
    
    public void dispatchMessage(@NonNull Message msg) {
        ...
        handleMessage(msg);
        ...
    }
    
    /**
     * sendMessage/sendEmptyMessage/sendEmptyMessageDelayed/sendMessageDelayed/...
     * 如上所有的发送消息方法最终都执行的是sendMessageAtTime
     */
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

}

从Handler源码可知:

  1. 我们在使用Handler的时候,一般是new一个Handler,然后重写handleMessage方法,该方法是由dispatchMessage来调用,而dispatchMessage是在Looper对象的loop()方法中的死循环中执行。
  2. sendMessage、sendEmptyMessage、sendMessageDelayed等发送消息的方法,实际上调用的是MessageQueue对象的enqueueMessage(msg, uptimeMillis)方法,将Message对象放入MessageQueue中
public final class MessageQueue { {
    // True if the message queue can be quit.
    @UnsupportedAppUsage
    private final boolean mQuitAllowed;
    
    @UnsupportedAppUsage
    Message mMessages;
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
    
    @UnsupportedAppUsage
    Message next() {
        // Message是一个单链表结构,有一个属性next指向了后一个Message
    }
    
    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            // 时间参数when表示Message的优先级
            // 1.根据时间参数when来判断当前Message(假设为curr_msg)要插入到哪一个Message(假设为pre_msg)的后面
            // 2.如果pre_msg的next不为空,则用一个临时变量记录:temp_msg = pre_msg.next
            // 3.将pre_msg.next = curr_msg,然后将curr_msg.next = temp_msg
        }
    }
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        ...
    }
}

从MessageQueue的源码可知:
mQuitAllowed表示MessageQueue是否可以销毁。
我们在创建Looper时,一般调用的是Looper.prepare(),该方法最终调用的是Looper的构造方法,会将mQuitAllowed设置为true,表示可以销毁。

public final class ActivityThread extends ClientTransactionHandler {
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        Looper.loop();
        ...
    }
}

从ActivityThread源码可知:
启动一个app时,会在app的主线程创建一个Looper,而此时调用的是Looper.prepareMainLooper(),会将mQuitAllowed设置为false,表示不可以销毁。

public final class Message implements Parcelable {
    @UnsupportedAppUsage
    /*package*/ Handler target;
    
    @UnsupportedAppUsage
    /*package*/ Message next;

    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    
    public void setTarget(Handler target) {
        this.target = target;
    }
    
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    public static Message obtain(Message orig) {
        Message m = obtain();
        m.what = orig.what;
        m.arg1 = orig.arg1;
        m.arg2 = orig.arg2;
        m.obj = orig.obj;
        m.replyTo = orig.replyTo;
        m.sendingUid = orig.sendingUid;
        m.workSourceUid = orig.workSourceUid;
        if (orig.data != null) {
            m.data = new Bundle(orig.data);
        }
        m.target = orig.target;
        m.callback = orig.callback;

        return m;
    }
}

从Message源码可知:

  1. 成员变量target是一个Handler对象
  2. 可以通过Message.obtain()来创建Message对象,这种方式可以复用已经创建过的Message,从而避免频繁的创建、销毁Message,达到优化内存和性能的目的。

1.3 Toast

不知道大家有没有在子线程中使用过Toast,如果有,那么你可能碰到过在子线程中直接调用Toast的makeText()方法会报错Can't toast on a thread that has not called Looper.prepare()

public class Toast {
    
    public Toast(Context context) {
        this(context, null);
    }

    public Toast(@NonNull Context context, @Nullable Looper looper) {
        ...
        looper = getLooper(looper);
        ...
    }
    
    private Looper getLooper(@Nullable Looper looper) {
        if (looper != null) {
            return looper;
        }
        return checkNotNull(Looper.myLooper(),
                "Can't toast on a thread that has not called Looper.prepare()");
    }
    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        return makeText(context, null, text, duration);
    }

    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
            Toast result = new Toast(context, looper);
            result.mText = text;
            result.mDuration = duration;
            return result;
        } else {
            Toast result = new Toast(context, looper);
            View v = ToastPresenter.getTextToastView(context, text);
            result.mNextView = v;
            result.mDuration = duration;

            return result;
        }
    }
}

从Toast源码可知:

  1. makeText()会调用Toast的构造函数,构造函数中调用getLooper(),此时looper为空,会执行checkNotNull(Looper.myLooper(), "...")方法,通过方法名可以直接得出该方法用于判空,为空则抛出异常。
  2. 从Looper的源码可知,Looper.myLooper()获取的是当前线程的Looper对象,而此时子线程的Looper对象为空,导致了异常。

子线程中使用Toast的方法:
第一步,调用Looper.prepare()给子线程创建一个Looper对象。
第二步,调用Toast.makeText(...).show()向子线程的MessageQueue插入Message
第三步,调用Looper.loop(),从MessageQueue中取出Message

1.4 ThreadHandler


1.5 同步屏障

同步Message和异步Message的区别:

  1. 同步(sync)就是一个一个的来,处理完一个再处理下一个;异步(async)就是可以同时处理多个,不需要等待。
  2. 同步Message,就是按照顺序执行的Message,默认情况下,我们通过Handler对象的sendMessage()方法发送的Message就是同步Message。
  3. 异步Message,类似于屏幕点击事件的Message,就是异步Message。

什么是同步屏障?
字面意思理解,就是设置了一道屏障,让同步Message不可以通过,从而优先处理异步Message

那么同步屏障如何设置?我们来看ViewRootImpl的源码:

public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    ...
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }
    ...
}

从ViewRootImpl的源码可知:

  1. scheduleTraversals()中调用了MessageQueue对象的postSyncBarrier()方法,设置了同步屏障,设置同步屏障的代码如下:
public final class MessageQueue {
    @UnsupportedAppUsage
    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }
}

如上代码可知,设置同步屏障就是在MessageQueue队列中新增了一个Message对象,并将该Message对象插入到队列的最前面。当我们调用Handler对象的sendMessage()方法时,会将Message对象的target属性设置为Handler对象,而此处postSyncBarrier()方法没有设置Message对象的target,说明target=null

  1. scheduleTraversals()中调用了mChoreographer.postCallback(),追踪源码可知调用的是Handler对象的sendMessageAtTime()发送Message,但是在发送Message之前执行了调用了Message对象的setAsynchronous(true),将Message设置为了异步消息
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
  1. unscheduleTraversals()中调用了MessageQueue对象的removeSyncBarrier()方法,移除了同步屏障
  2. unscheduleTraversals()中调用了mChoreographer.removeCallbacks(),追踪源码可知调用的是Handler对象的removeMessages(),就是将Message从MessageQueue中移除。

再来看MessageQueue的next()方法:

    @UnsupportedAppUsage
    Message next() {
        ...
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
            }
        ...
    }

next()方法中的if语句中,判断了Message对象的target属性是否为空。如果为空,则说明MessageQueue队列中有需要优先处理的异步Message,此时执行do-while循环,去查找队列中的异步Message,找到异步Message之后返回给Looper。判断一个Message是否为异步Message:msg.isAsynchronous()

同步屏障总结:

  1. 要使用异步Message,需要发送两个Message。一个Message的target=null,用于告诉MessageQueue,当前队列中有异步消息;另一个Message才是真正的消息载体,并且要设置setAsynchronous(true),用于标记Message的异步属性。
  2. Looper的死循环中,会调用MessageQueue的next()方法从消息队列中获取消息,而 MessageQueue的next()方法会优先返回异步消息。

2.Handler常见面试题

2.1 一个线程有几个Handler?

答:一个线程可以有多个Handler,需要时,通过new Handler()的方式创建Handler

2.2 一个线程有几个Looper,如何保证?

答:一个线程只有一个Looper,从Looper的源码中可知,Looper的构造私有化了,需要通过Looper.prepare()创建Looper,创建的Looper会使用ThreadLocal存起来,再次调用Looper.prepare()会检查ThreadLocal中是否有Looper,如果有则抛出异常。

2.3 Handler内存泄漏的原因?

答:假设在Activity中使用匿名内部类的方式创建了一个Handler:

public class MainActivity extends Activity {

    private TextView mTextView;

    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 等价于:MainActivity.this.mTextView.setText("hello")
            mTextView.setText("hello");
            if (msg.what == 100) {
                // do something...
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Thread(){
            @Override
            public void run() {
                super.run();
                Message message = new Message();
                message.what = 100;
                // 延迟一小时发送
                mHandler.sendMessageDelayed(message, 1000 * 60 * 60);
            }
        }.start();
    }
}

结论:匿名内部类默认会持有外部类对象的引用,这也是为什么能直接调用mTextView.setTex()的原因

  1. 从Message的源码可知,Message有一个Handler类型的属性target,说明Message持有一个Handler
  2. 从上述结论可知Handler持有MainActivity对象
  3. 如果Message得不到释放,则Handler也得不到释放,那么MainActivity也得不到释放

上述代码中,将Message设置为延迟一小时后再发送,如果一小时内需要finish掉MainActivity跳转到其他Activity,则此时会发生内存泄漏,因为GC无法回收MainActivity。
可以使用静态内部类 + 弱引用的方式解决:

public class MainActivity extends Activity {

    private TextView mTextView;

    Handler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new MyHandler(this);
        new Thread(){
            @Override
            public void run() {
                super.run();
                Message message = new Message();
                message.what = 100;
                // 延迟一小时发送
                mHandler.sendMessageDelayed(message, 1000 * 60 * 60);
            }
        }.start();
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> mMainReference;
        public MyHandler(MainActivity mainActivity) {
            mMainReference = new WeakReference<MainActivity>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity mainActivity = mMainReference.get();
            mainActivity.mTextView.setText("hello");
            if (msg.what == 100) {
                // do something...
            }
        }
    }
}

2.4 为何主线程中可以new Handler,在子线程中new Handler要做什么准备?

答:先调用Looper.prepare(),再调用Looper.loop()

2.5 子线程中维护的Looper,当MessageQueue中无消息时,该如何处理?

答:调用Looper对象的quit()方法,最终调用的是MessageQueue的quit()方法

2.6 不同线程的Handler往MessageQueue中添加Message,Handler内部如何保证线程安全?

答:

  1. 从Looper的源码可知,MessageQueue对象在Looper的构造函数中创建。即每一个线程对应一个MessageQueue对象
  2. 从MessageQueue的enqueueMessage(...)方法可知,该方法中的synchronized(this)持有的是this对象锁,当前MessageQueue对象其他使用synchronized(this)的地方都会等待

以上两点,保证了线程安全。

2.7 使用Message时应该如何创建它?

答:从Message源码可知,应该使用Message.obtain()来创建Message对象

2.8 为什么主线程不会因为 Looper.loop() 里的死循环卡死?

答:

  1. ActivityThread是应用程序的入口,该类中的main函数是整个Java程序的入口,main函数的主要作用就是做消息循环,一旦main函数执行完毕,循环结束,那么应用也就可以退出了。
  2. ActivityThread的main函数中调用了Looper.loop()开启了一个死循环,loop()方法中调用了MessageQueue的next()方法,该方法是一个阻塞方法(这里涉及到Linux中的pipe,不做赘述),如果MessageQueue中没有Message,则会等待。
  3. loop()的阻塞,是指MessageQueue中没有Message,此时释放CPU执行权,等待唤醒。
  4. ANR:Application Not Responding。导致ANR的情况比如主线程中访问网络、操作数据库等耗时操作,此时也会往MessageQueue中发送Message,然后loop()循环中获取到了Message,但是在处理Message时耗时过长,导致短时间内无法响应屏幕点击事件等操作(屏幕被点击也会发送Message到MessageQueue中),然后就出现了ANR。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容