"一篇就夠"系列: Handler擴(kuò)展篇

前言

Handler系列文章共兩篇:

第一篇:"一篇就夠"系列: Handler消息機(jī)制完全解析

第二篇: "一篇就夠"系列: Handler擴(kuò)展篇

上一篇中擦酌,我們對(duì)Handler的主體部分進(jìn)行了講解先口,今天,我們就來學(xué)習(xí)一下Handler相關(guān)的一些擴(kuò)展知識(shí)这敬,講完這些擴(kuò)展知識(shí)后戳葵,在來回答之前列出來的一系列問題

同步屏障

通過上一篇的學(xué)習(xí)筋量,我們知道: Handler發(fā)送的Message會(huì)放入到MessageQueue中植袍,MessageQueue中維護(hù)了一個(gè)優(yōu)先級(jí)隊(duì)列筋遭,優(yōu)先級(jí)隊(duì)列的意思就是將存儲(chǔ)數(shù)據(jù)的單鏈表按照時(shí)間升序進(jìn)行排序形成的打颤,Looper則按照順序,每次從這個(gè)優(yōu)先級(jí)隊(duì)列中取出一個(gè)Message進(jìn)行分發(fā)漓滔,一個(gè)處理完就處理下一個(gè)编饺。

那么問題來了:我能不能讓我的一個(gè)Message被優(yōu)先處理?

可以响驴,使用同步屏障

這里透且,我心里又會(huì)有個(gè)疑問,什么是同步屏障踏施?怎么使用同步屏障石蔗?同步屏障有啥作用?帶著這些疑問???畅形,我們來分析下源碼

先看下MessageQueue的next方法养距,在上一篇中,我們省略了一部分代碼日熬,其中有一部分是這樣子的棍厌,僅貼出關(guān)鍵代碼

Message next() {
    //...
    for (;;) {
        synchronized (this) {
            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());
            }
    }
}

上述代碼:

1、判斷當(dāng)前msg不為空并且msg.target為空竖席,則進(jìn)入條件體里面

2耘纱、條件體里面有一行源碼注釋,翻譯過來就是: 被一個(gè)屏障給阻礙毕荐。在隊(duì)列中查找下一個(gè)異步消息

3束析、接下來就是一個(gè)循環(huán),遍歷找出一條異步消息憎亚,循環(huán)體里面就是鏈表相關(guān)的操作

這里大家是不是會(huì)有個(gè)疑問员寇?msg.target怎么可能會(huì)為空呢弄慰?之前發(fā)送消息的一系列方法不是都會(huì)給msg.target對(duì)象賦值嗎?

沒錯(cuò)蝶锋,我們?cè)诨仡櫼幌翲andler的enqueueMessage

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
     //將當(dāng)前Handler賦值給msg.target
     msg.target = this;
     msg.workSourceUid = ThreadLocalWorkSource.getUid();
     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     //調(diào)用MessageQueue的enqueueMessage方法
     return queue.enqueueMessage(msg, uptimeMillis);
}

我們知道Handler的postsend系列方法發(fā)送的消息陆爽,最終都會(huì)走到這個(gè)方法,msg.target都會(huì)被賦值扳缕,因此不可能為空慌闭。那msg.target啥時(shí)候會(huì)為空呢?我們推斷肯定是其他發(fā)送消息的方法使得msg.target為空躯舔,那我們就找一下驴剔,會(huì)發(fā)現(xiàn)MessageQueue的postSyncBarrier的方法中沒有給msg.target對(duì)象賦值:

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;
     }
 }

上述代碼就是往消息隊(duì)列中合適的位置插入target屬性為null的Message

因此我們是不是可以知道,Message的target屬性為空和非空是很不一樣的庸毫,這里就不賣關(guān)子了仔拟,直接給結(jié)論: target屬性為空的Message就是同步屏障衫樊,他是一種特殊的消息飒赃,并不會(huì)被消費(fèi),僅僅是作為一個(gè)標(biāo)識(shí)處于 MessageQueue 中科侈,當(dāng)MessageQueue的next方法遇到同步屏障的時(shí)候载佳,就會(huì)循環(huán)遍歷整個(gè)鏈表找到標(biāo)記為異步消息的Message,其他的消息會(huì)直接忽視臀栈,那么這樣異步消息就會(huì)提前被執(zhí)行了

現(xiàn)在我們現(xiàn)在就可以回答上面的問題了:target屬性為空的Message就是同步屏障蔫慧,同步屏障可以使得異步消息優(yōu)先被處理,通過MessageQueue的postSyncBarrier可以添加一個(gè)同步屏障

注意: 在異步消息處理完之后权薯,同步屏障并不會(huì)被移除姑躲,需要我們手動(dòng)移除,從上面的源碼我們也可以看出盟蚣,如果不移除同步屏障黍析,那么他會(huì)一直在那里,這樣同步消息就永遠(yuǎn)無法被執(zhí)行了屎开。

因此我們?cè)谑褂猛晖狡琳虾蟛妫枰謩?dòng)移除,代碼如下:

public void removeSyncBarrier(int token) {
     // Remove a sync barrier token from the queue.
     // If the queue is no longer stalled by a barrier then wake it.
     synchronized (this) {
         Message prev = null;
         Message p = mMessages;
         while (p != null && (p.target != null || p.arg1 != token)) {
             prev = p;
             p = p.next;
         }
         if (p == null) {
             throw new IllegalStateException("The specified message queue synchronization "
                     + " barrier token has not been posted or has already been removed.");
         }
         final boolean needWake;
         if (prev != null) {
             prev.next = p.next;
             needWake = false;
         } else {
             mMessages = p.next;
             needWake = mMessages == null || mMessages.target != null;
         }
         p.recycleUnchecked();

         // If the loop is quitting then it is already awake.
         // We can assume mPtr != 0 when mQuitting is false.
         if (needWake && !mQuitting) {
             nativeWake(mPtr);
         }
     }
 }

到這里我心里又有一個(gè)疑問了奄抽?怎么把一個(gè)消息變成異步消息呢蔼两?還是回到Handler的enqueueMessage方法:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
     msg.target = this;
     msg.workSourceUid = ThreadLocalWorkSource.getUid();
     //如果mAsynchronous,則將該消息設(shè)置為異步消息
     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     return queue.enqueueMessage(msg, uptimeMillis);
}

從上述代碼我是可以看到逞度,通過msg.setAsynchronous方法設(shè)置為true额划,可以把一個(gè)消息變成異步消息,但是前提得滿足mAsynchronous屬性為true档泽,mAsynchronous是Handler中的一個(gè)屬性俊戳,他會(huì)在這兩個(gè)構(gòu)造方法中被賦值:

@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    //...
    mAsynchronous = async;
}

public Handler(@Nullable Callback callback, boolean async) {
    //...
    mAsynchronous = async;
}

因此我們是不是可以得出結(jié)論彬祖,把一個(gè)消息設(shè)置為異步消息,有兩種方式:

1品抽、在Handler的構(gòu)造方法中储笑,傳入async為true,那么這個(gè)時(shí)候發(fā)送的Message就都是異步的的消息

2圆恤、給Message通過setAsynchronous 方法標(biāo)志為異步

但是突倍,上面兩個(gè)構(gòu)造方法對(duì)外是不可見的,我們調(diào)用不到盆昙,而且設(shè)置同步屏障的方法對(duì)外也是不可見的羽历,說明谷歌不想要我們?nèi)ナ褂盟K赃@里同步屏障也是作為一個(gè)了解淡喜,一般只有系統(tǒng)會(huì)去使用它秕磷,例如:在進(jìn)行UI繪制的時(shí)候,以下是ViewRootImpl中執(zhí)行UI繪制的方法使用到了同步屏障:

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

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

上述代碼在把繪制消息放入隊(duì)列之前炼团,先放入了一個(gè)同步屏障澎嚣,然后在發(fā)送異步繪制消息,從而使得界面繪制的消息會(huì)比其他消息優(yōu)先執(zhí)行瘟芝,避免了因?yàn)?MessageQueue 中消息太多導(dǎo)致繪制消息被阻塞導(dǎo)致畫面卡頓易桃,當(dāng)繪制完成后,就會(huì)將同步屏障移除锌俱。

IdleHandler

見名知意晤郑,idle是空閑的意思,那么IdleHandler就是空閑的Handler贸宏,有點(diǎn)這個(gè)意思造寝,實(shí)際上它是MessageQueue中有一個(gè)靜態(tài)接口

public static interface IdleHandler {
    boolean queueIdle();
}

可以看到它是一個(gè)單方法的接口,也可稱為函數(shù)型接口吭练,它的作用是:在UI線程處理完所有View事務(wù)后诫龙,回調(diào)一些額外的操作,且不會(huì)堵塞主進(jìn)程线脚;我們來實(shí)際操作一下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Handler().getLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                Log.d("print", "queueIdle: 空閑時(shí)做一些輕量級(jí)別");
                return false;
            }
        });
    }
}

//上面代碼會(huì)打印如下結(jié)果
queueIdle: 空閑時(shí)做一些輕量級(jí)別

接著進(jìn)行源碼分析赐稽,我們?cè)诳聪?code>addIdleHandler這個(gè)方法:

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

可以看到,被添加進(jìn)來的handler放到了mIdleHandlers浑侥,跟過去看下mIdleHandlers姊舵,會(huì)發(fā)現(xiàn)MessageQueue中定義了IdleHandler的集合和數(shù)組,并且有一些操作方法寓落,如下:

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private IdleHandler[] mPendingIdleHandlers;

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

最后在看下MessageQueue中的Next方法括丁,僅貼出關(guān)鍵代碼:

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    for (;;) {
        //...
        synchronized (this) {
             //...
             //當(dāng)前無消息,或還需要等待一段時(shí)間消息才能分發(fā)伶选,獲得IdleHandler的數(shù)量
             if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                 pendingIdleHandlerCount = mIdleHandlers.size();
             }
          
             if (pendingIdleHandlerCount <= 0) {
                 // No idle handlers to run.  Loop and wait some more.
                 //如果沒有idle handler需要執(zhí)行史飞,阻塞線程進(jìn)入下次循環(huán)
                 mBlocked = true;
                 continue;
             }
         //初始化mPendingIdleHandlers
             if (mPendingIdleHandlers == null) {
                 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
             }
             //把List轉(zhuǎn)化成數(shù)組類型
             mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
         }
      
        //循環(huán)遍歷所有的IdleHandler
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                //獲得idler.queueIdle的返回值
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            //keep即idler.queueIdle的返回值尖昏,如果為false表明只要執(zhí)行一次,并移除构资,否則不移除
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
         }
         // Reset the idle handler count to 0 so we do not run them again.
         //將pendingIdleHandlerCount置為0避免下次再次執(zhí)行
         pendingIdleHandlerCount = 0;
      
         // 當(dāng)在執(zhí)行IdleHandler的時(shí)候抽诉,可能有新的消息已經(jīng)進(jìn)來了
         // 所以這個(gè)時(shí)候不能阻塞,要回去循環(huán)一次看一下
         nextPollTimeoutMillis = 0;
    }
}

上述代碼解析:

1吐绵、當(dāng)調(diào)用next方法的時(shí)候迹淌,會(huì)將pendingIdleHandlerCount賦值為-1

2、判斷pendingIdleHandlerCount是否小于0并且MessageQueue 是否為空或者有延遲消息需要執(zhí)行己单,如果是則把存儲(chǔ)IdleHandler的list的長(zhǎng)度賦值給pendingIdleHandlerCount

3唉窃、判斷如果沒有IdleHandler需要執(zhí)行,阻塞線程進(jìn)入下次循環(huán)纹笼,如果有纹份,則初始化mPendingIdleHandlers,把list中的所有IdleHandler放到數(shù)組中廷痘。這一步是為了不讓在執(zhí)行IdleHandler的時(shí)候List被插入新的IdleHandler蔓涧,造成邏輯混亂

4、循環(huán)遍歷所有的IdleHandler并執(zhí)行牍疏,查看idler.queueIdle方法的返回值蠢笋,為false表明這個(gè)IdleHandler只需要執(zhí)行一次,并移除鳞陨,為true,則不移除

5瞻惋、將pendingIdleHandlerCount置為0避免下次再次執(zhí)行厦滤, 當(dāng)在執(zhí)行IdleHandler的時(shí)候,可能有新的消息已經(jīng)進(jìn)來了歼狼,所以這個(gè)時(shí)候不能阻塞掏导,要回去循環(huán)一次看一下

到這里同步屏障和IdleHandler都講完了,建議讀者配合完整的源碼在去仔細(xì)閱讀一次羽峰。

實(shí)際應(yīng)用: 可以在IdleHandler里面獲取View的寬高

主線程消息循環(huán)

在上一篇中我們講到趟咆,ActivityThread就是主線程,也可以說是UI線程梅屉,在主線程的main方法中創(chuàng)建了Looper值纱,并開啟了消息循環(huán):

public static void main(String[] args) {
  //...
  //創(chuàng)建Looper
  Looper.prepareMainLooper();
  
  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  //...
  //開啟循環(huán)讀取消息
  Looper.loop();
  //Looper如果因異常原因停止循環(huán)則拋異常
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

主線程的消息循環(huán)開始了以后,ActivityThread還需要有一個(gè)Handler來和消息隊(duì)列進(jìn)行交互坯汤,這個(gè)Handler就是ActivityThread.H虐唠,它內(nèi)部定義了很多的消息類型,例如四大組件的啟動(dòng)惰聂,Application的啟動(dòng)等等

class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        @UnsupportedAppUsage
        public static final int EXIT_APPLICATION        = 111;
        @UnsupportedAppUsage
        public static final int RECEIVER                = 113;
        @UnsupportedAppUsage
        public static final int CREATE_SERVICE          = 114;
        @UnsupportedAppUsage
        public static final int SERVICE_ARGS            = 115;
        @UnsupportedAppUsage
        public static final int STOP_SERVICE            = 116;

        public static final int CONFIGURATION_CHANGED   = 118;
        public static final int CLEAN_UP_CONTEXT        = 119;
        @UnsupportedAppUsage
        public static final int GC_WHEN_IDLE            = 120;
        @UnsupportedAppUsage
        public static final int BIND_SERVICE            = 121;
        @UnsupportedAppUsage
        public static final int UNBIND_SERVICE          = 122;
        public static final int DUMP_SERVICE            = 123;
        public static final int LOW_MEMORY              = 124;
        public static final int PROFILER_CONTROL        = 127;
        public static final int CREATE_BACKUP_AGENT     = 128;
        public static final int DESTROY_BACKUP_AGENT    = 129;
        public static final int SUICIDE                 = 130;
            //...
        public void handleMessage(Message msg) {
            //...
        }
}

關(guān)于ActivityThread.H的實(shí)際應(yīng)用疆偿,我們?cè)诳?a target="_blank">Activity的啟動(dòng)流程可能會(huì)有比較深入的理解咱筛,ActivityThread通過ApplicationThread和AMS進(jìn)行進(jìn)程間通信的方式完成ActivityThread的請(qǐng)求后,會(huì)回調(diào)ApplicationThread中的Binder方法杆故,然后ApplicationThread會(huì)向H發(fā)送消息迅箩,H收到消息后會(huì)將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行,即切換到主線程去執(zhí)行处铛,這個(gè)過程就是主線程的消息循環(huán)模型

妙用 Looper 機(jī)制

1沙热、我們可以通過LoopergetMainLooper方法獲取主線程Looper,從而可以判斷當(dāng)前線程是否是主線程

2罢缸、將 Runnable post 到主線程執(zhí)行

public final class MainThread {

    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }
}

子線程使用Handler及相關(guān)注意事項(xiàng)

我們通常使用Handler都是從子線程發(fā)送消息到主線程去處理篙贸,那么這里我們嘗試一下從主線程發(fā)送消息到子線程來處理,上代碼:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //創(chuàng)建線程實(shí)例并開啟
        MyThread myThread = new MyThread();
        myThread.start();
        //打開這段注釋就不會(huì)crash枫疆,且看下面分析
//      try {
//          Thread.sleep(500);
//      } catch (InterruptedException e) {
//          e.printStackTrace();
//      }
        //獲取Handler發(fā)送消息
        myThread.getHandler().sendEmptyMessage(0x001);
    }

    public static class MyThread extends Thread {
        private Handler mHandler;

        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(@NonNull Message msg) {
                    if(msg.what == 0x001){
                        Log.d("print", "handleMessage: ");
                    }
                }
            };
            Looper.loop();
        }

        public Handler getHandler(){
            return mHandler;
        }
    }
}

運(yùn)行一下上述代碼爵川,發(fā)現(xiàn)會(huì)Crash,如下圖:

image-20210223112129732

報(bào)了一個(gè)空指針異常息楔,原因就是多線程并發(fā)寝贡,當(dāng)主線程執(zhí)行到sendEnptyMessage時(shí),子線程的Handler還沒有創(chuàng)建值依。因此我們可以在獲取Handler的時(shí)候讓主線程休眠一下在執(zhí)行圃泡,應(yīng)用就不會(huì)Crash了,打開上面代碼的注釋即可

值得注意的是:我們自己創(chuàng)建的Looper在使用完畢后應(yīng)該調(diào)用quit方法來終止消息循環(huán)愿险,如果不退出的話颇蜡,那么該線程的Looper處理完所有的消息后,就會(huì)處于一個(gè)阻塞狀態(tài)辆亏,要知道線程是比較重量級(jí)的风秤,如果一直存在,肯定會(huì)對(duì)應(yīng)用性能造成一定的影響扮叨。而如果退出Looper缤弦,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper彻磁。

因此在子線程使用Handler碍沐,我們需要注意一下兩點(diǎn):

1、必須調(diào)用Looper.prepare()創(chuàng)建當(dāng)前線程的 Looper衷蜓,并調(diào)用Looper.loop()開啟消息循環(huán)

2累提、必須在使用結(jié)束后調(diào)用Looper的quit方法退出當(dāng)前線程

HandlerThread

上面講到主線程發(fā)送消息到子線程來處理,其實(shí)Android已經(jīng)給我們提供了一個(gè)這樣輕量級(jí)的異步類恍箭,那就是HandlerThread

HandlerThread的實(shí)現(xiàn)原理也比較簡(jiǎn)單:繼承Thread并對(duì)Looper進(jìn)行了封裝

具體源碼就不過多分析了刻恭,大家有興趣的可以去看一下,也就100多行代碼,這里主要講解一下使用:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1鳍贾,創(chuàng)建Handler實(shí)例
        HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
        //2鞍匾,啟動(dòng)線程
        mHandlerThread.start();
        //3,使用傳入Looper為參數(shù)的構(gòu)造方法創(chuàng)建Handler實(shí)例
        Handler mHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                Log.d("print", "當(dāng)前線程: " + Thread.currentThread().getName() + " handleMessage");
            }
        };
        //4骑科,使用Handler發(fā)送消息
        mHandler.sendEmptyMessage(0x001);
        //5橡淑,在合適的時(shí)機(jī)調(diào)用HandlerThread的quit方法,退出消息循環(huán)
    }
}

//上述代碼打印結(jié)果:
當(dāng)前線程: HandlerThread handleMessage

Handler HandlerThread Thread三者區(qū)別

Handler:在Android中負(fù)責(zé)發(fā)送和處理消息

HandlerThread:繼承自Thread咆爽,對(duì)Looper進(jìn)行了封裝梁棠,也就是說它在子線程維護(hù)了一個(gè)Looper,方便我們?cè)谧泳€程中去處理消息

Thread: cpu執(zhí)行的最小單位斗埂,即線程符糊,它在執(zhí)行完后就立馬結(jié)束了,并不能去處理消息呛凶。如果要處理男娄,需要配合Looper,Handler一起使用

子線程彈Toast

//1
new Thread(){
    @Override
    public void run() {
        Toast.makeText(MainActivity.this, "子線程彈Toast", Toast.LENGTH_SHORT).show();
    }
}.start();

//2
new Thread(){
    @Override
    public void run() {
        Looper.prepare();
        Toast.makeText(MainActivity.this, "子線程彈Toast", Toast.LENGTH_SHORT).show();
        Looper.loop();
    }
}.start();

上述1代碼運(yùn)行會(huì)奔潰漾稀,會(huì)報(bào)這么一個(gè)異常提示:"Can't toast on a thread that has not called Looper.prepare()"

原因就是Toast的實(shí)現(xiàn)也是依賴Handler模闲,而我們知道在子線程中創(chuàng)建Handler,需先創(chuàng)建Looper并開啟消息循環(huán)崭捍,這點(diǎn)在Toast中的源碼也有體現(xiàn)尸折,如下圖:

image-20210223131023498

因此我們?cè)谧泳€程創(chuàng)建Toast就需要使用上述2代碼的方式

子線程彈Dialog

new Thread(){
    @Override
    public void run() {
            Looper.prepare();
            new AlertDialog.Builder(MainActivity.this)
                  .setTitle("標(biāo)題")
                  .setMessage("子線程彈Dialog")
                  .setNegativeButton("取消",null)
                  .setPositiveButton("確定",null)
                  .show();
            Looper.loop();     
    }    
}.start();

和上面Toast差不多,這里貼出正確的代碼示例殷蛇,它的實(shí)現(xiàn)也是依賴Handler实夹,我們?cè)谒脑创a中可以看到:

private final Handler mHandler = new Handler();

他直接就new了一個(gè)Handler實(shí)例,我們知道晾咪,創(chuàng)建Handler收擦,需要先創(chuàng)建Looper并開啟消息循環(huán),主線程中已經(jīng)給我們創(chuàng)建并開啟消息循環(huán)谍倦,而子線程中并沒有,如果不創(chuàng)建那就會(huì)報(bào)這句經(jīng)典的異常提示:"Can't create handler inside thread that has not called Looper.prepare() "泪勒,因此在子線程中昼蛀,需要我們手動(dòng)去創(chuàng)建并開啟消息循環(huán)

到這里,Handler相關(guān)的擴(kuò)展知識(shí)就全部講完了圆存,我們會(huì)發(fā)現(xiàn)也有著很多使用的小技巧叼旋,比如 IdleHandler,判斷是否是主線程等等

由于 Handler 的特性沦辙,它在 Android 里的應(yīng)用非常廣泛夫植,比如: AsyncTask、HandlerThread、Messenger详民、IdleHandler 和 IntentService 等等延欠,下面我們來回答上一篇中列出來的一系列問題

問題

1、Handler有哪些作用?

答:

1沈跨、Handler能夠進(jìn)行線程之間的切換

2由捎、Handler能夠按照順序處理消息,避免并發(fā)

3饿凛、Handler能夠阻塞線程

4狞玛、Handler能夠發(fā)送并處理延遲消息

解析:

1、Handler能夠進(jìn)行線程之間的切換涧窒,是因?yàn)槭褂昧瞬煌€程的Looper處理消息

2心肪、Handler能夠按照順序處理消息,避免并發(fā)纠吴,是因?yàn)橄⒃谌腙?duì)的時(shí)候會(huì)按照時(shí)間升序?qū)Ξ?dāng)前鏈表進(jìn)行排序硬鞍,Looper讀取的時(shí)候,MessageQueue的next方法會(huì)循環(huán)加鎖呜象,同時(shí)配合阻塞喚醒機(jī)制

3膳凝、Handler能夠阻塞線程主要是基于Linux的epoll機(jī)制實(shí)現(xiàn)的

4、Handler能夠處理延遲消息恭陡,是因?yàn)镸essageQueue的next方法中會(huì)拿當(dāng)前消息時(shí)間和當(dāng)前時(shí)間做比較蹬音,如果是延遲消息,那么就會(huì)阻塞當(dāng)前線程休玩,等阻塞時(shí)間到著淆,在執(zhí)行該消息

2、為什么我們能在主線程直接使用Handler拴疤,而不需要?jiǎng)?chuàng)建Looper永部?

答:主線程已經(jīng)創(chuàng)建了Looper,并開啟了消息循環(huán)

3呐矾、如果想要在子線程創(chuàng)建Handler苔埋,需要做什么準(zhǔn)備?

答:需要先創(chuàng)建Looper蜒犯,并開啟消息循環(huán)

4组橄、一個(gè)線程有幾個(gè)Handler?

答:可以有任意多個(gè)

5罚随、一個(gè)線程有幾個(gè)Looper玉工?如何保證?

答:一個(gè)線程只有一個(gè)Looper淘菩,通過ThreadLocal來保證

6遵班、Handler發(fā)送消息的時(shí)候,時(shí)間為啥要取SystemClock.uptimeMillis() + delayMillis,可以把SystemClock.uptimeMillis() 換成System.currentTimeMillis()嗎狭郑?

答:不可以

SystemClock.uptimeMillis() 這個(gè)方法獲取的時(shí)間腹暖,是自系統(tǒng)開機(jī)到現(xiàn)在的一個(gè)毫秒數(shù),這個(gè)時(shí)間是個(gè)相對(duì)的

System.currentTimeMillis() 這個(gè)方法獲取的是自1970-01-01 00:00:00 到現(xiàn)在的一個(gè)毫秒數(shù)愿阐,這是一個(gè)和系統(tǒng)強(qiáng)關(guān)聯(lián)的時(shí)間微服,而且這個(gè)值可以做修改

1、使用System.currentTimeMillis()可能會(huì)導(dǎo)致延遲消息失效

2缨历、最終這個(gè)時(shí)間會(huì)被設(shè)置到Message的when屬性以蕴,而Message的when屬性只是需要一個(gè)時(shí)間差來表示消息的先后順序,使用一個(gè)相對(duì)時(shí)間就行了辛孵,沒必要使用一個(gè)絕對(duì)時(shí)間

7丛肮、為什么Looper死循環(huán),卻不會(huì)導(dǎo)致應(yīng)用卡死魄缚?

答:因?yàn)楫?dāng)Looper處理完所有消息的時(shí)候宝与,會(huì)調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài),當(dāng)有新的Message進(jìn)來的時(shí)候會(huì)打破阻塞繼續(xù)執(zhí)行冶匹。

應(yīng)用卡死即ANR: 全稱Applicationn Not Responding习劫,中文意思是應(yīng)用無響應(yīng),當(dāng)我發(fā)送一個(gè)消息到主線程嚼隘,Handler經(jīng)過一定時(shí)間沒有執(zhí)行完這條消息诽里,那么這個(gè)時(shí)候就會(huì)拋出ANR異常

Looper死循環(huán): 循環(huán)執(zhí)行各種事務(wù),Looper死循環(huán)說明線程還活著飞蛹,如果沒有Looper死循環(huán)谤狡,線程結(jié)束,應(yīng)用就退出了卧檐,當(dāng)Looper處理完所有消息的時(shí)候會(huì)調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài)墓懂,當(dāng)有新的Message進(jìn)來的時(shí)候會(huì)打破阻塞繼續(xù)執(zhí)行

8、Handler內(nèi)存泄露原因? 如何解決霉囚?

內(nèi)存泄漏的本質(zhì)是長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用捕仔,導(dǎo)致短生命周期的對(duì)象無法被回收,從而導(dǎo)致了內(nèi)存泄漏

下面我們就看個(gè)導(dǎo)致內(nèi)存泄漏的例子

public class MainActivity extends AppCompatActivity {

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
           //do something
        }
    };
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //發(fā)送一個(gè)延遲消息盈罐,10分鐘后在執(zhí)行
        mHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
    }
}

上述代碼:

1逻澳、我們通過匿名內(nèi)部類的方式創(chuàng)建了一個(gè)Handler的實(shí)例

2、在onCreate方法里面通過Handler實(shí)例發(fā)送了一個(gè)延遲10分鐘執(zhí)行的消息

我們發(fā)送的這個(gè)延遲10分鐘執(zhí)行的消息它是持有Handler的引用的暖呕,根據(jù)Java特性我們又知道,非靜態(tài)內(nèi)部類會(huì)持有外部類的引用苞氮,因此當(dāng)前Handler又持有Activity的引用湾揽,而Message又存在MessageQueue中,MessageQueue又在當(dāng)前線程中,因此會(huì)存在一個(gè)引用鏈關(guān)系:

當(dāng)前線程->MessageQueue->Message->Handler->Activity

因此當(dāng)我們退出Activity的時(shí)候库物,由于消息需要在10分鐘后在執(zhí)行霸旗,因此會(huì)一直持有Activity,從而導(dǎo)致了Activity的內(nèi)存泄漏

通過上面分析我們知道了內(nèi)存泄漏的原因就是持有了Activity的引用戚揭,那我們是不是會(huì)想诱告,切斷這條引用,那么如果我們需要用到Activity相關(guān)的屬性和方法采用弱引用的方式不就可以了么民晒?我們實(shí)際操作一下精居,把Handler寫成一個(gè)靜態(tài)內(nèi)部類

public class MainActivity extends AppCompatActivity {

    private final SafeHandler mSafeHandler = new SafeHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //發(fā)送一個(gè)延遲消息,10分鐘后在執(zhí)行
        mSafeHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
    }

    //靜態(tài)內(nèi)部類并持有Activity的弱引用
    private static class SafeHandler extends Handler{
      
        private final WeakReference<MainActivity> mWeakReference;
      
        public SafeHandler(MainActivity activity){
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity mMainActivity = mWeakReference.get();
            if(mMainActivity != null){
                //do something
            }
        }
    }
}

上述代碼

1潜必、把Handler定義成了一個(gè)靜態(tài)內(nèi)部類靴姿,并持有當(dāng)前Activity的弱引用,弱引用會(huì)在Java虛擬機(jī)發(fā)生gc的時(shí)候把對(duì)象給回收掉

經(jīng)過上述改造磁滚,我們解決了Activity的內(nèi)存泄漏佛吓,此時(shí)的引用鏈關(guān)系為:

當(dāng)前線程->MessageQueue->Message->Handler

我們會(huì)發(fā)現(xiàn)Message還是會(huì)持有Handler的引用,從而導(dǎo)致Handler也會(huì)內(nèi)存泄漏垂攘,所以我們應(yīng)該在Activity銷毀的時(shí)候维雇,在他的生命周期方法里,把MessageQueue中的Message都給移除掉晒他,因此最終就變成了這樣:

public class MainActivity extends AppCompatActivity {

    private final SafeHandler mSafeHandler = new SafeHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //發(fā)送一個(gè)延遲消息吱型,10分鐘后在執(zhí)行
        mSafeHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
    }
  
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSafeHandler.removeCallbacksAndMessages(null);
    }

    //靜態(tài)內(nèi)部類并持有Activity的弱引用
    private static class SafeHandler extends Handler{
      
        private final WeakReference<MainActivity> mWeakReference;
      
        public SafeHandler(MainActivity activity){
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            MainActivity mMainActivity = mWeakReference.get();
            if(mMainActivity != null){
                //do something
            }
        }
    }
}

因此當(dāng)Activity銷毀后,引用鏈關(guān)系為:

當(dāng)前線程->MessageQueue

而當(dāng)前線程和MessageQueue的生命周期和應(yīng)用生命周期是一樣長(zhǎng)的仪芒,因此也就不存在內(nèi)存泄漏了唁影,完美。

所以解決Handler內(nèi)存泄漏最好的方式就是:將Handler定義成靜態(tài)內(nèi)部類掂名,內(nèi)部持有Activity的弱引用据沈,并在Activity銷毀的時(shí)候移除所有消息

9、線程維護(hù)的Looper饺蔑,在消息隊(duì)列無消息時(shí)的處理方案是什么锌介?有什么用?

答:當(dāng)消息隊(duì)列無消息時(shí)猾警,Looper會(huì)阻塞當(dāng)前線程孔祸,釋放cpu資源,提高App性能

我們知道Looper的loop方法中有個(gè)死循環(huán)一直在讀取MessageQueue中的消息发皿,其實(shí)是調(diào)用了MessageQueue中的next方法崔慧,這個(gè)方法會(huì)在無消息時(shí),調(diào)用Linux的epoll機(jī)制穴墅,使得線程進(jìn)入阻塞狀態(tài)惶室,當(dāng)有新消息到來時(shí)温自,就會(huì)將它喚醒,next方法里會(huì)判斷當(dāng)前消息是否是延遲消息皇钞,如果是則阻塞線程悼泌,如果不是,則會(huì)返回這條消息并將其從優(yōu)先級(jí)隊(duì)列中給移除

10夹界、MessageQueue什么情況下會(huì)被喚醒馆里?

答:需要分情況

1、發(fā)送消息過來可柿,此時(shí)MessageQueue中無消息或者當(dāng)前發(fā)送過來的消息攜帶的when為0或者有延遲執(zhí)行的消息鸠踪,那么需要喚醒

2、當(dāng)遇到同步屏障且當(dāng)前發(fā)送過來的消息為異步消息趾痘,判斷該異步消息是否插入在所有異步消息的隊(duì)首慢哈,如果是則需要喚醒,如果不是永票,則不喚醒

11卵贱、線程什么情況下會(huì)被阻塞?

答:分情況

1侣集、當(dāng)MessageQueue中沒有消息的時(shí)候键俱,這個(gè)時(shí)候會(huì)無限阻塞,

2世分、當(dāng)前MessageQueue中全部是延遲消息编振,阻塞時(shí)間為(當(dāng)前延遲消息時(shí)間 - 當(dāng)前時(shí)間),如果這個(gè)阻塞時(shí)間超過來Integer類型的最大值臭埋,則取Integer類型的最大值

12踪央、我們可以使用多個(gè)Handler往消息隊(duì)列中添加數(shù)據(jù),那么可能存在發(fā)消息的Handler存在不同的線程瓢阴,那么Handler是如何保證MessageQueue并發(fā)訪問安全的呢畅蹂?

答:循環(huán)加鎖,配合阻塞喚醒機(jī)制

我們可以發(fā)現(xiàn)MessageQueue其實(shí)是“生產(chǎn)者-消費(fèi)者”模型荣恐,Handler不斷地放入消息液斜,Looper不斷地取出,這就涉及到死鎖問題叠穆。如果Looper拿到鎖少漆,但是隊(duì)列中沒有消息,就會(huì)一直等待硼被,而Handler需要把消息放進(jìn)去示损,鎖卻被Looper拿著無法入隊(duì),這就造成了死鎖嚷硫。Handler機(jī)制的解決方法是循環(huán)加鎖屎媳。在MessageQueue的next方法中:

Message next() {
   ...
    for (;;) {
  ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            ...
        }
    }
}

我們可以看到他的等待是在鎖外的夺溢,當(dāng)隊(duì)列中沒有消息的時(shí)候,他會(huì)先釋放鎖烛谊,再進(jìn)行等待,直到被喚醒嘉汰。這樣就不會(huì)造成死鎖問題了丹禀。

那在入隊(duì)的時(shí)候會(huì)不會(huì)因?yàn)殛?duì)列已經(jīng)滿了然后一邊在等待消息處理一邊拿著鎖呢?這一點(diǎn)不同的是MessageQueue的消息沒有上限鞋怀,或者說他的上限就是JVM給程序分配的內(nèi)存双泪,如果超出內(nèi)存會(huì)拋出異常,但一般情況下是不會(huì)的密似。

13焙矛、Handler是如何進(jìn)行線程切換的呢?

答:使用不同線程的Looper處理消息

我們通常處理消息是在Handler的handleMessage方法中残腌,那么這個(gè)方法是在哪里回調(diào)的呢村斟?看下面這段代碼

public static void loop() {
    //開啟死循環(huán)讀取消息
    for (;;) {
         // 調(diào)用Message對(duì)應(yīng)的Handler處理消息
         msg.target.dispatchMessage(msg);
    }
}

上述代碼中msg.target其實(shí)就是我們發(fā)送消息的Handler,因此他會(huì)回調(diào)Handler的dispatchMessage方法抛猫,而dispatchMessage這個(gè)方法我們?cè)谏弦黄兄攸c(diǎn)分析過蟆盹,其中有一部分邏輯就是會(huì)回調(diào)到Handler的handleMessage方法,我們還可以發(fā)現(xiàn)闺金,Handler的handleMessage方法所在的線程是由Looper的loop方法決定的逾滥。平時(shí)我們使用的時(shí)候,是從異步線程發(fā)送消息到 Handler败匹,而這個(gè) Handler 的 handleMessage() 方法是在主線程調(diào)用的寨昙,因?yàn)長(zhǎng)ooper是在主線程創(chuàng)建的,所以消息就從異步線程切換到了主線程掀亩。

14舔哪、我們?cè)谑褂肕essage的時(shí)候,應(yīng)該如何去創(chuàng)建它归榕?

答:Android 給 Message 設(shè)計(jì)了回收機(jī)制尸红,官方建議是通過Message.obtain方法來獲取,而不是直接new一個(gè)新的對(duì)象刹泄,所以我們?cè)谑褂玫臅r(shí)候應(yīng)盡量復(fù)用 Message 外里,減少內(nèi)存消耗,方式有二:

1特石、調(diào)用 Message 的一系列靜態(tài)重載方法 Message.obtain 獲取

2盅蝗、通過 Handler 的公有方法 handler.obtainMessage,實(shí)際上handler.obtainMessage內(nèi)部調(diào)用的也是Message.obtain的重載方法

15姆蘸、Handler里面藏著的CallBack能做什么墩莫?

答: 利用此CallBack攔截Handler的消息處理

在上一篇中我們分析到芙委,dispatchMessage方法的處理步驟:

1、首先狂秦,檢查Message的callback是否為null灌侣,不為null就通過handleCallBack來處理消息,Message的callback是一個(gè)Runnable對(duì)象裂问,實(shí)際上就是Handler的post系列方法所傳遞的Runnable參數(shù)

2侧啼、其次,檢查Handler里面藏著的CallBack是否為null堪簿,不為null就調(diào)用mCallback的handleMessage方法來處理消息痊乾,并判斷其返回值:為true,那么 Handler 的 handleMessage(msg) 方法就不會(huì)被調(diào)用了椭更;為false哪审,那么就意味著一個(gè)消息可以同時(shí)被 Callback 以及 Handler 處理

3虑瀑、最后湿滓,調(diào)用Handler的handleMessage方法來處理消息

通過上面分析我們知道Handler處理消息的順序是:Message的Callback > Handler的Callback > Handler的handleMessage方法

使用場(chǎng)景: Hook ActivityThread.mH , 在 ActivityThread 中有個(gè)成員變量 mH 缴川,它是個(gè) Handler茉稠,又是個(gè)極其重要的類,幾乎所有的插件化框架都使用了這個(gè)方法把夸。

16而线、Handler阻塞喚醒機(jī)制是怎么一回事?

答: Handler的阻塞喚醒機(jī)制是基于Linux的阻塞喚醒機(jī)制恋日。

這個(gè)機(jī)制也是類似于handler機(jī)制的模式膀篮。在本地創(chuàng)建一個(gè)文件描述符,然后需要等待的一方則監(jiān)聽這個(gè)文件描述符岂膳,喚醒的一方只需要修改這個(gè)文件誓竿,那么等待的一方就會(huì)收到文件從而打破喚醒。和Looper監(jiān)聽MessageQueue谈截,Handler添加message是比較類似的筷屡。具體的Linux層知識(shí)讀者可通過這篇文章詳細(xì)了解(傳送門

17、什么是Handler的同步屏障簸喂?

答: 同步屏障是一種使得異步消息可以被更快處理的機(jī)制

18毙死、能不能讓一個(gè)Message被加急處理?

答:可以喻鳄,添加加同步屏障扼倘,并發(fā)送異步消息

19、什么是IdleHandler除呵?

答: IdleHandler是MessageQueue中一個(gè)靜態(tài)函數(shù)型接口再菊,它在主線程執(zhí)行完所有的View事務(wù)后爪喘,回調(diào)一些額外的操作,且不會(huì)阻塞主線程

總結(jié)

Handler消息機(jī)制在Android系統(tǒng)源碼中進(jìn)行了大量的使用纠拔,可以說是涉及了Android的方方面面秉剑,比如我們四大組件的啟動(dòng),Application的創(chuàng)建等等绿语,學(xué)好Handler相關(guān)的知識(shí)秃症,可以幫助我們更好的去閱讀Android源碼,而且Handler在我們?nèi)粘i_發(fā)中直接或間接的會(huì)被用到吕粹。同時(shí)通過對(duì)Handler源碼的學(xué)習(xí),讓我感受到了代碼設(shè)計(jì)的背后岗仑,蘊(yùn)藏著工程師大量的智慧匹耕,心里直呼666,哈哈荠雕。

到了這里稳其,關(guān)于Handler相關(guān)的知識(shí)就都講完了,如果你還有什么問題炸卑,評(píng)論區(qū)告訴我吧既鞠。

參考和推薦

Android全面解析之Handler機(jī)制(終篇):常見問題匯總

Handler 都沒搞懂,拿什么去跳槽案俏摹嘱蛋?

換個(gè)姿勢(shì),帶著問題看Handler

全文到此五续,原創(chuàng)不易洒敏,歡迎點(diǎn)贊,收藏疙驾,評(píng)論和轉(zhuǎn)發(fā)凶伙,你的認(rèn)可是我創(chuàng)作的動(dòng)力

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市它碎,隨后出現(xiàn)的幾起案子函荣,更是在濱河造成了極大的恐慌,老刑警劉巖扳肛,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傻挂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡敞峭,警方通過查閱死者的電腦和手機(jī)踊谋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旋讹,“玉大人殖蚕,你說我怎么就攤上這事轿衔。” “怎么了睦疫?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵害驹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蛤育,道長(zhǎng)宛官,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任瓦糕,我火速辦了婚禮底洗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘咕娄。我一直安慰自己亥揖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布圣勒。 她就那樣靜靜地躺著费变,像睡著了一般。 火紅的嫁衣襯著肌膚如雪圣贸。 梳的紋絲不亂的頭發(fā)上挚歧,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音吁峻,去河邊找鬼滑负。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锡搜,可吹牛的內(nèi)容都是我干的橙困。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了乒裆?” 一聲冷哼從身側(cè)響起碴裙,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡槽华,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趟妥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猫态。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亲雪,到底是詐尸還是另有隱情勇凭,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布义辕,位于F島的核電站虾标,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏灌砖。R本人自食惡果不足惜璧函,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望基显。 院中可真熱鬧蘸吓,春花似錦、人聲如沸撩幽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)摸航。三九已至,卻和暖如春舅桩,著一層夾襖步出監(jiān)牢的瞬間酱虎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工擂涛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留读串,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓撒妈,卻偏偏與公主長(zhǎng)得像恢暖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狰右,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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