Handler知識(shí)梳理

Handler無論是在代碼的使用中還是面試恨溜,應(yīng)用的頻率都非常高汽久,這就要求我們將Handler的使用及原理研究透徹指郁。下面根據(jù)博客以及相關(guān)的一些資料,根據(jù)自己對(duì)Handler的理解迫肖,寫下這篇文章用于記錄锅劝,并加深印象;如有錯(cuò)誤之處蟆湖,請(qǐng)諒解故爵。

看下面這個(gè)Handler簡(jiǎn)單使用的例子:

        private Handler mHandler = new Handler(){//新建Handler并處理消息
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1000:
                        Log.e("LHC", "Handler Msg.");
                        break;
                    default:
                        break;
                }
            }
        };

        new Thread(new Runnable() {//建立子線程,并在線程中發(fā)送消息
            @Override
            public void run() {
                mHandler.sendEmptyMessage(1000);
            }
        }).start();

這樣就完成了一個(gè)Handler的簡(jiǎn)單使用隅津。
下面我們來一步步分析诬垂,這個(gè)過程是怎么實(shí)現(xiàn)的。先來看Handler的創(chuàng)建伦仍,如下:

1    public Handler() {
2       this(null, false);
3  }

4    public Handler(Callback callback, boolean async) {
5        if (FIND_POTENTIAL_LEAKS) {
6            final Class<? extends Handler> klass = getClass();
7            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
8                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
9            }
10        }

11        mLooper = Looper.myLooper();
12       if (mLooper == null) {
13            throw new RuntimeException(
                  "Can't create handler inside thread that has not called Looper.prepare()");
14       }
15        mQueue = mLooper.mQueue;
16        mCallback = callback;
17        mAsynchronous = async;
18    }

創(chuàng)建過程其實(shí)執(zhí)行的是Handler(Callback callback, boolean async)方法结窘。來具體看這個(gè)方法。

  • 5~10行充蓝,執(zhí)行了一個(gè)if語句隧枫,目的是為了判斷新建的Handler對(duì)象是否是非靜態(tài)的,如果是就顯示警告谓苟;
  • 11行官脓,生成了一個(gè)新的Looper對(duì)象。
  • 12~14行涝焙,判斷生成的mLooper是否為空卑笨,為空是拋出異常,表示進(jìn)行創(chuàng)建Handler的線程仑撞,此線程還沒有調(diào)用Looper.prepare()進(jìn)行Looper初始化赤兴。
  • 15行,初始化MessageQueue對(duì)象mQueue派草。這個(gè)是將Looper中的mQueue賦值給它的搀缠。
  • 16行,初始化Callback接口對(duì)象mCallback近迁。
  • 17行,初始化是否異步標(biāo)記簸州。

第11行代碼鉴竭,生成了一個(gè)新的Looper對(duì)象,我們看看其內(nèi)部實(shí)現(xiàn)岸浑,代碼如下:

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

sThreadLocalThreadLocal<Looper>對(duì)象搏存,通過get方法就可獲取線程中對(duì)應(yīng)的Looper。如果返回值為null矢洲,就表示線程還沒有關(guān)聯(lián)Looper璧眠,也就是還沒有初始化一個(gè)Looper。初始化的方法如下:

1    public static void prepare() {
2        prepare(true);
3    }

4    private static void prepare(boolean quitAllowed) {
5        if (sThreadLocal.get() != null) {
6            throw new RuntimeException("Only one Looper may be created per thread");
7        }
8        sThreadLocal.set(new Looper(quitAllowed));
9    }

這就是Looper的兩個(gè)初始化方法,具體看下帶參數(shù)的方法:

  • 5~7行责静,在if中判斷獲取的Looper對(duì)象時(shí)否為空袁滥,不為空時(shí),拋出異常灾螃,表示一個(gè)線程只能初始化一個(gè)Looper题翻。
  • 8行,調(diào)用Looper的構(gòu)造方法生成一個(gè)對(duì)象腰鬼,并將其設(shè)置到sThreadLocal中嵌赠。

那么在構(gòu)造方法中有進(jìn)行了什么操作呢?看代碼:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在這個(gè)構(gòu)造方法中完成了MessageQueue的創(chuàng)建熄赡,及當(dāng)前線程的保存姜挺。這樣在調(diào)用new Handler()時(shí),通過代碼mQueue = mLooper.mQueue;將此時(shí)新建的mQueue賦值給了Handler中的mQueue了彼硫。
通過上面的解釋炊豪,我們也就明白了為什么在子線程中不能直接new Handler(),而是需要先進(jìn)行調(diào)用Looper.prepare()方法了乌助。那么在UI線程中為什么有可以了呢?這是因?yàn)樵?code>ActivityThread類中已經(jīng)初始化了溜在,具體下面在說。

Handler建立后他托,就是消息的發(fā)送了掖肋,如例子代碼:

        mHandler.sendEmptyMessage(1000);

我們進(jìn)入看看其具體的實(shí)現(xiàn),代碼如下:

    public final boolean sendEmptyMessage(int what) {
        return sendEmptyMessageDelayed(what, 0);
    }

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

sendEmptyMessageDelayed(int what, long delayMillis)方法中赏参,將我們傳入的what封裝成了一個(gè)Message信息志笼,然后在傳給方法sendMessageDelayed(msg, delayMillis)。我們繼續(xù)深入把篓,看代碼:

    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) {
        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(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

封裝的Message經(jīng)過傳遞到了Handler中的enqueueMessage(queue, msg, uptimeMillis)方法纫溃,在方法中給target進(jìn)行了賦值(這里的target就是Message中的Handler對(duì)象),并進(jìn)行了異步判斷韧掩,最終將Message傳遞給了方法MessageQueue類中的enqueueMessage(msg, uptimeMillis)紊浩。那么在這個(gè)方法中做了哪些處理呢?看代碼:

    boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            ......
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            ......
        }
        return true;
    }

這個(gè)方法就實(shí)現(xiàn)了將封裝好的Message消息疗锐,加入到了MessageQueue消息隊(duì)列當(dāng)中坊谁。MessageQueue是按照Message觸發(fā)時(shí)間的先后順序排列的,隊(duì)頭的消息是將要最早觸發(fā)的消息滑臊。當(dāng)有消息需要加入消息隊(duì)列時(shí)口芍,會(huì)從隊(duì)列頭開始遍歷,直到找到消息應(yīng)該插入的合適位置雇卷,以保證所有消息的時(shí)間順序.

那么我們是在哪里進(jìn)行取消息的呢鬓椭?是在ActivityThread之中颠猴,我們來看代碼:

    public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看到在ActivityThread中,我們調(diào)用了Looper.prepareMainLooper();進(jìn)行了初始化小染,生成了主線程Looper翘瓮,在通過調(diào)用Looper.loop();來進(jìn)行消息獲取。代碼如下:

    public static void loop() {
        ......
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        ......
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ......
        }
    }

在方法中用for生成了一個(gè)死循環(huán)氧映,在里面循環(huán)調(diào)用queue.next()獲取Message春畔,當(dāng)獲取的消息為空時(shí),就跳出死循環(huán)岛都;不為空時(shí)律姨,就通過代碼msg.target.dispatchMessage(msg);去分配消息。那么在next()中是如何不斷的獲取消息的臼疫,看代碼:

    Message next() {
        ......
        for (;;) {
            ......
            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());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
        ......
            }
        ......
        }
    }

這個(gè)方法中也是使用for生成了一個(gè)死循環(huán)择份,用于獲取MessageQueue隊(duì)列中的消息,消息獲取成功后退出死循環(huán)烫堤,等待下次的調(diào)用荣赶。這樣就完成了循環(huán)取消息的過程。

消息獲取到之后鸽斟,使用代碼msg.target.dispatchMessage(msg);來進(jìn)行消息分配處理拔创,看具體代碼:

 1   public void dispatchMessage(Message msg) {
 2       if (msg.callback != null) {
 3           handleCallback(msg);
 4       } else {
 5           if (mCallback != null) {
 6               if (mCallback.handleMessage(msg)) {
 7                   return;
 8               }
 9           }
10           handleMessage(msg);
11      }
12    }
  • 當(dāng)msg.callback不為空時(shí),調(diào)用的是靜態(tài)方法handleCallback(msg)富蓄;
  • 當(dāng)msg.callback為空時(shí)且mCallback不為空時(shí)剩燥,調(diào)用的是Callback接口中的handleMessage(msg);
  • 當(dāng)msg.callback為空時(shí)且mCallback為空時(shí),調(diào)用Handler中的自己的handleMessage(msg);

我們通過上面的分析立倍,可以得到這樣的結(jié)論:

  • 線程默認(rèn)是沒有Looper的灭红,如果需要使用Handler,就必須先創(chuàng)建Looper口注。
  • ActivityThread被初始化的是時(shí)候就創(chuàng)建了Looper变擒,并運(yùn)行了loop()方法,這也就是UI線程中能直接使用Handler的原因寝志。
  • Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng)娇斑,Looper在哪個(gè)線程創(chuàng)建,就跟哪個(gè)線程綁定材部,并且Handler是在他關(guān)聯(lián)的Looper對(duì)應(yīng)的線程中處理消息的悠菜。

我們調(diào)用post方法來實(shí)現(xiàn)更新UI的操作,代碼如下:

        postHandler = new Handler();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //在這里進(jìn)行耗時(shí)操作...
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                postHandler.post(new Runnable() {
                    @Override
                    public void run() {//在這里進(jìn)行UI更新
                        Log.e("LHC", "Handler Post...");
                    }
                });
            }
        });

在這里需要注意的是:post方法傳入的是Runnable败富,看著是新建了一個(gè)子線程,那么在子線程中怎么能進(jìn)行UI更新呢摩窃?首先兽叮,我們要明確一點(diǎn)芬骄,一個(gè)線程的啟動(dòng)需要調(diào)用start()方法,但是這里并沒有調(diào)用鹦聪。在JAVA中账阻,直接調(diào)用run()方法相當(dāng)于調(diào)用了一個(gè)普通方法,當(dāng)然了還是當(dāng)前線程執(zhí)行泽本。我們來看下post里面代碼的具體實(shí)現(xiàn):

    public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

我們可以看到在post方法之中淘太,將參數(shù)r傳遞給sendMessageDelayed方法,然后在方法getPostMessage(r)中规丽,將r賦值給了m.callback并封裝成了Message返回蒲牧。而sendMessageDelayed方法以后的處理流程在上面已經(jīng)講過了,這里就不在重復(fù)了赌莺。
我們?cè)趤砘剡^頭來看看上面提到的方法dispatchMessage(msg)冰抢,代碼中當(dāng)msg.callback不為空時(shí),調(diào)用的是靜態(tài)方法handleCallback(msg)艘狭。那么這個(gè)靜態(tài)方法中執(zhí)行的是什么呢挎扰?看代碼:

    private static void handleCallback(Message message) {
        message.callback.run();
    }

這里調(diào)用的是Messagecallback下的run()方法,而這個(gè)callback就是我們?cè)谡{(diào)用post(Runnable r)時(shí)的傳入的參數(shù)r巢音。

所以post方法中就不存在新建線程遵倦,方法post運(yùn)行在調(diào)用它的Handler所在的線程中,而這個(gè)Handler運(yùn)行在主線程中官撼,所以在主線程中更新UI肯定沒有問題了梧躺。
  • 那么系統(tǒng)為什么不允許在子線程訪問UI呢?摘自<Android 開發(fā)藝術(shù)與探索>

這是因?yàn)锳ndroid的UI線程不是線程安全的歧寺,如果在多線程中并發(fā)訪問可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)燥狰。

  • 那么系統(tǒng)為什么不對(duì)UI控件加上鎖機(jī)制呢?摘自<Android 開發(fā)藝術(shù)與探索>

缺點(diǎn)有兩個(gè):首先斜筐,加上鎖機(jī)制會(huì)讓UI訪問的邏輯變的復(fù)雜龙致;其次,鎖機(jī)制會(huì)降低UI訪問的效率顷链,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行目代。

在看下面的代碼:

        Handler h = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.e("LHC", "Handler.Callback Msg:"+ msg.what);
                return true;
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                h.sendEmptyMessage(1);
            }
        }).start();

這里使用的是構(gòu)造方法Handler(Callback callback)進(jìn)行生成Handler,具體實(shí)現(xiàn)代碼:

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

public Handler(Callback callback, boolean async) {
        ......

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

將傳入的Callback接口對(duì)象參數(shù)callback賦值給了Handler中的成員變量mCallback嗤练。在回過頭來看處理消息方法dispatchMessage(msg)榛了,代碼中當(dāng)msg.callback為空時(shí)且mCallback不為空時(shí),調(diào)用的是Callback接口中的handleMessage(msg)煞抬。
到這里就將分發(fā)消息方法中的情況都介紹清楚了霜大。

  • 為什么Looper.loop()死循環(huán)不會(huì)導(dǎo)致APP ANR?

ActivityThread的main方法主要作用就是做消息循環(huán)革答,一旦退出消息循環(huán)战坤,主線程運(yùn)行完畢曙强,那么你的應(yīng)用也就退出了。
Android是事件驅(qū)動(dòng)的途茫,在Loop.loop()中不斷接收事件碟嘴、處理事件,而Activity的生命周期都依靠于主線程的Loop.loop()來調(diào)度囊卜,所以可想而知它的存活周期和Activity也是一致的娜扇。當(dāng)沒有事件需要處理時(shí),主線程就會(huì)阻塞栅组;當(dāng)子線程往消息隊(duì)列發(fā)送消息雀瓢,并且往管道文件寫數(shù)據(jù)時(shí),主線程就被喚醒笑窜。
真正會(huì)卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時(shí)間過長致燥,會(huì)導(dǎo)致掉幀,甚至發(fā)生ANR排截,looper.loop本身不會(huì)導(dǎo)致應(yīng)用卡死嫌蚤。

上面我們基本講解了Handler的流程,下面我們整體上來總結(jié)一下:

Handler機(jī)制主要涉及了這四個(gè)類:Handler断傲,Lopper脱吱,MessageMessageQueue认罩。
  • 新建Handler箱蝠,通過Handler的post或者sendMessage方法發(fā)送消息,Handler通過sendMessageDelayed方法垦垂,將Message交給MessageQueue.
  • MessageQueue通過調(diào)用enqueueMessage(msg, uptimeMillis)方法宦搬,將Message以鏈表的方式加入隊(duì)列當(dāng)中.
  • Looper類中的loop()方法循環(huán)調(diào)用MessageQueue.next()方法獲取消息,并調(diào)用Handler的dispatchMessage(msg)方法處理消息.
  • 在dispatchMessage(msg)方法中劫拗,根據(jù)判斷msg.callback间校,mCallback是否為空來執(zhí)行相對(duì)應(yīng)的回調(diào),如果都為空就執(zhí)行Handler的自身handlerMessage(msg)回調(diào).

在使用Handler過程當(dāng)中页慷,我們還會(huì)遇到這種情況憔足,看代碼:

        Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
               //TODO 自己的代碼邏輯
            }
        };

這次這樣寫的時(shí)候,代碼new Handler()中的Handler報(bào)警告The following Handler class should be static or leaks might occur酒繁,警告的意思也就是說Handler類應(yīng)當(dāng)是靜態(tài)的滓彰,否則可能會(huì)造成內(nèi)存泄露。那么為什么會(huì)出現(xiàn)這個(gè)警告呢州袒?Handler會(huì)在一個(gè)后臺(tái)線程中處理耗時(shí)操作揭绑,處理完成后將結(jié)果發(fā)送給UI線程進(jìn)行保存或者顯示。那么在后臺(tái)線程執(zhí)行的過程中郎哭,如果Activity關(guān)閉了洗做,在正常情況下弓叛,Activity會(huì)在GC檢查時(shí)被回收掉,但是由于后臺(tái)線程還在執(zhí)行诚纸,它持有Handler的引用,而Handler持有Activity的引用陈惰,這樣造成Activity不會(huì)被回收掉畦徘,直到后臺(tái)線程執(zhí)行完成。這樣就造成了內(nèi)存泄露抬闯。那么我們?nèi)绾谓鉀Q呢井辆?看下面代碼:

    private static class MyHandler extends Handler{
        private WeakReference<Activity> weakReference;
        MyHandler(Activity activity){
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = weakReference.get();
            if (activity == null || activity.isFinishing()){
                return;
            }

           //TODO msg消息的處理
           ......
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        myHandler.removeCallbacksAndMessages(null);//Activity退出時(shí),將還未執(zhí)行的消息處理掉溶握。免得引發(fā)異常
    }

通過上面的這種寫法杯缺,就解決了內(nèi)存泄露的問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末睡榆,一起剝皮案震驚了整個(gè)濱河市萍肆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胀屿,老刑警劉巖塘揣,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宿崭,居然都是意外死亡亲铡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門葡兑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奖蔓,“玉大人,你說我怎么就攤上這事讹堤∵汉祝” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蜕劝,是天一觀的道長檀头。 經(jīng)常有香客問我,道長岖沛,這世上最難降的妖魔是什么暑始? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮婴削,結(jié)果婚禮上廊镜,老公的妹妹穿的比我還像新娘。我一直安慰自己唉俗,他們只是感情好嗤朴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布配椭。 她就那樣靜靜地躺著,像睡著了一般雹姊。 火紅的嫁衣襯著肌膚如雪股缸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天吱雏,我揣著相機(jī)與錄音敦姻,去河邊找鬼。 笑死歧杏,一個(gè)胖子當(dāng)著我的面吹牛镰惦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播犬绒,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼旺入,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了凯力?” 一聲冷哼從身側(cè)響起茵瘾,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沮协,沒想到半個(gè)月后龄捡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡慷暂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年聘殖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片行瑞。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奸腺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出血久,到底是詐尸還是另有隱情突照,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布氧吐,位于F島的核電站讹蘑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏筑舅。R本人自食惡果不足惜座慰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翠拣。 院中可真熱鬧版仔,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至然想,卻和暖如春莺奔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背又沾。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工弊仪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杖刷。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像驳癌,于是被迫代替她去往敵國和親滑燃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 【Android Handler 消息機(jī)制】 前言 在Android開發(fā)中颓鲜,我們都知道不能在主線程中執(zhí)行耗時(shí)的任務(wù)...
    Rtia閱讀 4,809評(píng)論 1 28
  • 前言 在Android開發(fā)的多線程應(yīng)用場(chǎng)景中表窘,Handler機(jī)制十分常用 今天,我將手把手帶你深入分析Handle...
    BrotherChen閱讀 469評(píng)論 0 0
  • Android消息處理機(jī)制估計(jì)都被寫爛了昂验,但是依然還是要寫一下,因?yàn)锳ndroid應(yīng)用程序是通過消息來驅(qū)動(dòng)的艾扮,An...
    一碼立程閱讀 4,454評(píng)論 4 36
  • 1. 前言 在之前的圖解Handler原理最后留下了幾個(gè)課后題既琴,如果還沒看過那篇文章的,建議先看那篇文章泡嘴,課后題如...
    唐江旭閱讀 5,932評(píng)論 5 45
  • Android中的消息機(jī)制甫恩,消息的發(fā)送和接收過程以及與線程之間的關(guān)系。雖然我們經(jīng)常使用這些基礎(chǔ)的東西酌予,但對(duì)于其內(nèi)部...
    Sunny君907閱讀 615評(píng)論 0 1