Handler源码学习

Handler我们经常用,一般是用在子线程给主线程发消息,通知主线程做更新UI的操作,但是现在假如说,让你在主线程给子线程发消息呢?

public class MainActivity extends Activity {

private MyLinearLayout mParent;
private Handler subHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    init();
    Button button = findViewById(R.id.my_button);
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            subHandler.sendEmptyMessage(0);
        }
    });
}

private void init() {
    new Thread("子线程哈哈") {
        @Override
        public void run() {
            //创建looper对象,并与当前线程绑定
            Looper.prepare();
            subHandler = new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    Toast.makeText(MainActivity.this, "当前线程:" + Thread.currentThread()
                            .getName() , Toast.LENGTH_SHORT).show();
                    return true;
                }
            });
            //轮询Looper里面的消息队列
            Looper.loop();
        }
    }.start();
}

上面就是使用Handler在主线程给子线程发消息的demo,点击按钮后,主线程发消息给子线程,子线程收到消息后,Toast的结果是:当前线程:子线程哈哈. 但是如果我把Looper.prepare();去掉,就会报错:java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

对比在主线程创建handler,在子线程给主线程发消息的使用方式,唯一的区别就是加了2行代码:

    Looper.prepare();
    ........
    Looper.loop();

那为什么,在主线程中创建handler,我们直接new就可以,但是在子线程不行呢?Hanlder的工作原理是什么?源码是最好的老师,所以下面学习一下Handler的源码

学习Handler的源码之前,首先回顾一下一般情况下Handler的基本使用步骤:

1, 在主线程直接创建一个Handler对象,重写或实现handlerMessage(),在handlerMessage() 里面写收到消息后的处理逻辑
2, 在子线程中需要通知主线程的地方,调用handler.sendMessage()

只有2步,使用起来很简单. 我们就顺着这个使用步骤去看Handler的源码,看它到底怎么工作的,怎么就能够让2个不同的线程之间能够通信

Handler的构造方法:
public Handler() {
    this(null, false);
}


public Handler(Callback callback) {
    this(callback, false);
}


public Handler(Looper looper) {
    this(looper, null, false);
}


public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}


public Handler(boolean async) {
    this(null, async);
}

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
  //从当前线程的ThreadLocalMap里面查找有没有对应的looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;      //将looper的Queue赋值handler的成员mQueue 
    mCallback = callback;
    mAsynchronous = async;
}


public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Looper.myLooper()

Handler有7个重载的构造方法,我们直接看倒数第二种构造方式,可以看到调用了mLooper = Looper.myLooper();

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

sThreadLocalLooper类的一个静态成员,它是一个ThreadLocal

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

所以通过上面的代码可以看到,Looper.myLooper()就是取出当前线程的ThreadLocalMap里面存储的key为Looper的静态成员sThreadLocal所对应的looper对象.(有关ThreadLoca知识,请前往深入理解 Java 之 ThreadLocal 工作原理

如果当前线程(Handler在哪个线程创建就代表哪个线程)的ThreadLocalMap里面没有Looper对象,就会报错提示:不能在没有调用Looper.prepare()的线程里创建Handler对象,如果有Looper,就将LoopermQueue赋值给Handler的成员mQueue

Looper.prepare()

前面提到,如果要创建Handler对象,必须要有Looper,如果没有Looper,会报错提示要调用Looper.prepare()才可以创建,接下来就看一下Looper.prepare()

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

先从当前线程的ThreadLocalMap里面去取出key为Looper类的静态成员sThreadLocal对应的looper对象,如果已经有looper,就报错:每一个线程只能有一个looper对象. 如果没有looper,就新建一个,并存入当前线程的ThreadLocalMap,存入的key是Looper类的sThreadLocal. 看到这里,我们可能会想平时我们用的时候并没有去调用Looper.prepare(),其实是系统已经帮我们做了:

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

    Looper.prepareMainLooper();

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

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

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

在应用程序的入口ActivityThreadmain()方法里面(ActivityThread不是一个线程,只是一个普通类),调用 了Looper.prepareMainLooper();

 public static void prepareMainLooper() {
    //新建一个looper对象,并存进ThreadLocalMap
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //给主线程对应的looper赋值
        sMainLooper = myLooper();
    }
}

Looper也有一个获取主线程对应的looper的方法getMainLooper()

 public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

再看一下Looper的构造方法:

private Looper(boolean quitAllowed) {
    //Looper对象创建的时候,会创建一个 MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //Looper对应的线程
    mThread = Thread.currentThread();
}
Handler初始化过程总结

从前面Handler的创建过程源码可以得出:

1,创建Handler对象之前,必须要先创建Looper,Looper与线程对应,一个线程只能有一个Looper
2,创建Handler对象之前,会先检查Handler对象创建时所在的线程是否已经有一个对应的Looper,检查是通过调用Looper.myLooper()方法,内部原理是通过每个线程内部的ThreadLocalMap查找key为Looper的静态成员ThreadLocal对应的Looper对象是否为null
3,如果Handler对象创建时所在的线程没有对应的Looper,那么会抛异常,在主线程创建除外。所以在主线程以外的线程创建Handler之前要先调用Looper.prepare()创建一个Looper,该方法内部同时会把Looper的静态成员ThreadLocal作为key,把Looper对象做为value值,存进Handler对象创建时所在的线程的ThreadLocalMap,Looper就与当前线程对应了
4,Looper对象创建的时候,会创建一个MessageQueue,该MessageQueue会在构造Handler对象的时候,赋值给Handler的成员mQueue,Looper对象本身也会赋值给Handler的成员mLooper

消息的发送

有了Handler对象后,就可以发送消息了,看一下Handler发送消息的方法:

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

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

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}


public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    //第二个参数传递的是从系统开机到当前时间的毫秒数+延迟时间
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

//第二个参数uptimeMillis表示消息发送的时间
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    //Handler的成员mQueue就是构造Handler对象时,Looper里面的MessageQueue
    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);
}

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
   //发送消息到消息队列的头部,因为第三个参数传的是0
    return enqueueMessage(queue, msg, 0);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //把Handler赋值给Message的target,后面从消息队列取出消息后就是交给这个target(Handler)处理
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

上面那么多发送消息的方法,除了sendMessageAtFrontOfQueue()以外,其余都是调用sendMessageAtTime(),这两个方法其实也是一样的,sendMessageAtTime()的第二个参数传0这两个方法就是一样了。但是这两个方法最终都调用了enqueueMessage(),而enqueueMessage()又调用了MessageQueueenqueueMessage(),也就是把消息插入到消息队列

消息入队
 boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {    //如果消息队列退出了,还入队就报错
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();    //消息入队后,标记为在被使用
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
           //p为null(代表MessageQueue没有消息) 或者消息的发送时间是队列中最早的
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;  //当阻塞时需要唤醒
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //将消息按时间顺序插入到MessageQueue
            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;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
Looper.loop();

消息插入到消息队列了,肯定要取出来才能用,从消息队列取消息的方法就是开头demo中的Looper.loop();

 public static void loop() {
  //取出当前线程对应的looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //取出looper里面的消息队列
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //死循环
    for (;;) {
        Message msg = queue.next(); // 从消息队列取出消息,会阻塞
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        .......
        try {
            //分发消息,把消息交给msg.target也就是Handler处理
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        ........

        msg.recycleUnchecked();    //将不再使用的消息放入消息池
    }
}
消息出队

取消息的时候,调用了消息队列的消息出队方法next()

//MessageQueue #next()
 Message next() {
    final long ptr = mPtr;    
    if (ptr == 0) {    //当消息循环已经退出,则直接返回
        return null;
    }
    int pendingIdleHandlerCount = -1;  // 循环迭代的首次为-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);
        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) {
               //如果消息没有 target,那它就是一个屏障,需要一直往后遍历找到第一个异步的消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) { //如果这个消息还没到处理时间,就设置个时间过段时间再处理
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                  // 正常消息的处理
                    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 {
                //没有消息
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // We only ever reach this code block during the first iteration.
        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 {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}
Handler.dispatchMessage()

Looper.loop()里面从消息取出消息后,调用了msg.target.dispatchMessage(msg);

  public void dispatchMessage(Message msg) {
    if (msg.callback != null) {      //
        handleCallback(msg);
    } else {
        if (mCallback != null) {    //构造Handler的时候传入的Callback
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);   //构造Handler时候,Callback如果传null,就走这里,它是空实现
    }
}

一般情况下,我们使用Handler都是使用 Handler handler=new Handler()这种构造方式,通过前面Handler的构造方法源码可以知道,这种构造方式的mCallback传的是null(不是Message的callback),所以当handler取出消息要处理的时候,会回调handleMessage(msg);而它是空实现,所以我们需要覆写handleMessage()

那么什么时候会调用if条件语句里面的handleCallback(msg)呢,其实Handler不仅只有sendMessageXXX发送消息方法,还有postXXX发送消息的方法

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

public final boolean postAtTime(Runnable r, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

public final boolean postAtFrontOfQueue(Runnable r)
{
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}

有5个postXXX发送消息的方法,但还是调用了sendXXX方法,只是传递参数的过程中调用了一个getPostMessage()

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

看到这里就明白了,只有调用postXXX发送消息的方法,才会走Handler.dispatchMessage()的if语句里面的回调,其实我们常用更新UI的方式:Activity.runOnUiThread(Runnable), View.post(Runnable);和View.postDelayed(Runnable action, long delayMillis)都是调用Handler的post方法发送消息

 //Activity#runOnUiThread
 public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);  //这个Handler是Activity的成员变量
    } else {
        action.run();
    }
 }

 //View#post
 public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
 }
关于Message的其他知识

1,Message的成员what表示的是消息的类型(用于标识干什么用的)
2,获取一个Message对象最好的方式不是直接new,而是使用Message.obtain(),或者Handler.obtainMessage()这种方式会复用消息池中的消息,而不是新建

最后再总结一下Handler机制:

1,每一个Handler创建的时候必须要有一个Looper(要么传入,要么通过当前线程获取),每一个Looper对应一个线程和MessageQueue,一个线程可以创建多个Handler,但只能创建一个Looper
2,如果Handler创建时所在的线程和Looper创建时所在的线程是同一个线程,Handler对象创建的时候会通过当前线程的ThreadLocal的内部类ThreadLocalMap检查当前线程是否已经有一个Looper,如果是在主线程创建的Handler,Looper的创建和轮询消息队列的操作,主线程已经在ActivityThread已经帮我们做了,如果不是在主线程创建的Handler,需要我们自己手动调用Looper.prepare()来创建Looper和调用Looper.loop()轮询消息队列,如果我们不手动调用,会报错!特别注意:Handler的创建线程可以和Looper的创建线程不是同一个线程
3,Handler对象创建的时候,会把Looper持有的MessageQueue赋值给Handler的MessageQueue,同时也会把Looper赋值给Handler的Looper,Handler发消息其实是把消息发送到Looper的MessageQueue,也就是说Handler持有的Looper在哪个线程创建的(Looper.prepare()Looper.loop()一般都会在一个线程),消息也就发给哪个线程
4,发送消息的过程中,Handler会把自身设置为当前消息的Target(Handler.enqueueMessage()里面设置),这样即使在一个线程创建了多个Handler,接收的Handler就永远是那个发送的Handler,而不会是别的Handler,别且这多个Handler共用一个Looper和MessageQueue

感谢
https://blog.csdn.net/yanbober/article/details/45936145

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

推荐阅读更多精彩内容

  • 【Android Handler 消息机制】 前言 在Android开发中,我们都知道不能在主线程中执行耗时的任务...
    Rtia阅读 4,806评论 1 28
  • 一、提出问题 面试时常被问到的问题: 简述 Android 消息机制 Android 中 Handler,Loop...
    Marker_Sky阅读 2,250评论 0 18
  • 前言 在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将手把手带你深入分析Handle...
    BrotherChen阅读 469评论 0 0
  • 1. 前言 在之前的图解Handler原理最后留下了几个课后题,如果还没看过那篇文章的,建议先看那篇文章,课后题如...
    唐江旭阅读 5,924评论 5 45
  • 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息...
    cxm11阅读 6,416评论 2 39