Android Handler机制详解

前言

Handler是Android的消息机制,他能够很轻松的在线程间传递数据。由于Android开发规范的限制,我们不能在主线程执行耗时操作(如网络,IO操作等),不能在子线程更新UI,所以Handler大部分用来在耗时操作与更新UI之间切换。这让很多人误以为Handler就是用来更新UI的,其实这只是它的一小部分应用。

开始

我相信大多数人对Handler的用法已经烂熟于心了,这篇文章不会去探讨Handler的使用,而是着重从源码上分析Handler的运行机制。

想要了解Handler的运行机制,我们需要了解 MessageQueueMessageLooper 这几个类。

  • MessageQueue 的意思就是消息队列,它存储了我们需要用来处理的消息Message
  • Message是消息类,内部存在一个Bundle对象和几个public字段存储数据,MessageQueue作为一个消息队列不能自己处理消息,所以需要用到Looper
  • Looper是一个循环装置,他负责从不断从MessageQueue里取出Message,然后回调给HandlerhandleMessage来执行具体操作。
  • Handler在这里面充当的角色更像是一个辅助类,它让我们不用关系MessageQueueLooper的具体细节,只需要关系如何发送消息和回调的处理就行了。

上面讲了几个关键类在Handler运行机制中的职责,相对大家对Handler机制有个粗略的了解。

我相信各位看官在阅读这篇文章前都是带着问题的,我们将通过问题来解答大家的疑惑。

分析

Looper

在分析Looper之前,我们还需要知道ThreadLocal这个类,如果对ThreadLocal还不太了解,可以去看我的另一篇文章《ThreadLocal详解》

Looper是如何创建?

Handler执行的线程和它持有的Looper有关。每个Thread都可以创建唯一的Looper对象。

 //为当前线程创建Looper对象的方法。
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
    //使用ThreadLocal来存储当前线程的Looper对象,这保证了每个线程有且仅有一个Looper对象。
    //这里做了非空判断,所以在同一个线程prepare方法是不允许被调用两次的
    //第一次创建好的Looper对象不会被覆盖,它是唯一的。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    

那么主线程的Looper对象是怎么创建的呢?

public static void prepareMainLooper() {
//其实主线程创建Looper和其他线程没有区别,也是调用prepare()。
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //但是Looper用sMainLooper这个静态变量将主线程的Looper对象存储了起来
            //可以通过getMainLooper()获取,存储MainLooper其实非常有作用,下面会讲到。
            sMainLooper = myLooper();
        }
    }

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
Looper是如何从MessageQueue取出消息并分发的?

Looper分发消息的主要逻辑在loop方法里


 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        //保证当前线程必须有Looper对象,如果没有则抛出异常,调用Looper.loop()之前应该先调用Looper.prepare().
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //Looper需要不断从MessageQueue中取出消息,所以它持有MessageQueue对象
        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 (;;) {
           //这里开始执行死循环,queue通过调用next方法来取出下一个消息。
           //很多人很疑惑死循环不会相当耗费性能吗,如果没有那么多消息怎么办?
           //其实当没有消息的时候,next方法会阻塞在这里,不会往下执行了,性能问题不存在。
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                //这里满足了死循环跳出的条件,即取出的消息为null
                //没有消息next不是会阻塞吗,怎么会返回null呢?
                //其实只有MessageQueue停止的时候(调用quit方法),才会返回null
                //MessageQueue停止后,调用next返回null,且不再接受新消息,下面还有详细介绍。
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            //这里的msg.target是Handler对象,分发消息到Handler去执行。
            //有人问主线程可以创建这么多Handler,怎么保证这个Handler发送的消息不会跑到其它Handler去执行呢?
            //那是因为在发送Message时,他会绑定发送的Handler,在此处分发消息时,也只会回调发送该条消息的Handler。
            //那么分发消息具体在哪个线程执行呢?
            //我觉得这个不该问,那当然是当前方法在哪个线程调用就在哪个线程执行啦。
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

        //这里对Message对象进行回收,会清空所有之前Message设置的数据。
        //正是因为Message有回收机制,我们在创建消息的时候应该优先选择Message.obtain(). 
        //如果发送的消息足够多,Message缓存的Message对象不够了,obtain内部会调用new Message()创建一个新的对象。
            msg.recycleUnchecked();
        }
    }
Looper 分发的消息在哪个线程执行?

先给大家展示一段Looper文档上的示例代码

class LooperThread extends Thread {
   public Handler mHandler;
  
   public void run() {
        Looper.prepare(); //创建LooperThread的Looper对象
  
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
              //处理发送过来的消息
            }
        };
    
        Looper.loop(); //开始循环消息队列
   }
}

上面这段代码相信很多人都写过,这是一段在子线程创建Handler的案例,其中handleMessage所执行的线程为LooperThread,因为Looper.loop()执行在LooperThreadrun方法里。可以在其他线程通过mHandler发送消息到LooperThread

如果不调用Looper.prepare()直接new Handler()会怎么样呢?

我们可以查看Handler的源码看看无参构造是如何运行的

public Handler() {
//调用两参构造
    this(null, false);
}
    
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());
        }
    }
//获取当前线程的Looper,如果不创建Looper会抛出异常。
//主线程我也没看到有调用Looper.prepare()啊,怎么在主线程不会抛异常呢?这个看下一个问题。
    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;
}
主线程的Looper对象在哪里创建的?

从上一个问题可以看出如果不调用Looper.prepare()直接new Handler()就会抛出异常`

throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");`

那么主线程的Looper在哪里创建的呢?首先它是创建了的,因为Looper.getMainLooper() != null,其实MainLooper创建的时间比我们想象的早,它在ActivityThread类里面,ActivityThreadAndroid的启动类,main方法就在里面(如果有人问你Android有没有main方法,你应该知道怎么回答了吧),而MainLooper就是在main方法里面创建的。

上代码:

//android.app.ActivityThread
public final class ActivityThread {
    ...
    public static void main(String[] args) {
         
        SamplingProfilerIntegration.start();
    
        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);
    
        Environment.initForCurrentUser();
    
        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
    
        Security.addProvider(new AndroidKeyStoreProvider());
    
        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
    
        Process.setArgV0("<pre-initialized>");
    
        //注意这里,这里创建了主线程的Looper
        Looper.prepareMainLooper();
    
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
    
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
    
        AsyncTask.init();
    
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        //开启消息循环
        Looper.loop();
    
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
}
MainLooper可以用来做什么
判断当前线程是否为主线程

因为Looper是在某一线程唯一的,那么可以在么做。如果

 public static boolean isMainThread() {
        //如果当前线程的Looper和MainLooper是同一个对象,那么可以认为当前线程是主线程
        return Looper.myLooper() == Looper.getMainLooper() ;
}

但是也有人说下面这样也可以

public static boolean isMainThread() {
    //这个方法其实是不准确的,线程的名称是可以随便更改的。
    return Thread.currentThread().getName().equals("main");
}

所以用Looper来判断主线程是很好的做法

创建运行在主线程的Handler

Handler除了有无参构造,还有一个可以传入Looper的构造。通过指定Looper,可以在任意地方创建运行在主线程的Handler

    class WorkThread extends Thread{
        private Handler mHandler;

        @Override
        public void run() {
            super.run();
            mHandler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    //运行在主线程
                }
            };
            mHandler.sendEmptyMessage(0);
        }
    }
Looper的quit方法和quitSafely方法有什么区别

下面是Looper两个方法的源码

public void quit() {
    mQueue.quit(false);
}
public void quitSafely() {
    mQueue.quit(true);
}

可以看出实际上是调用的MessageQueuequit方法
下面是MessageQueue的源码

//android.os.MessageQueue
void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
            //如果调用的是quitSafely运行removeAllFutureMessagesLocked,否则removeAllMessagesLocked。
            if (safe) {
            //该方法只会清空MessageQueue消息池中所有的延迟消息,
            //并将消息池中所有的非延迟消息派发出去让Handler去处理,
            //quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
                removeAllFutureMessagesLocked();
            } else {
            //该方法的作用是把MessageQueue消息池中所有的消息全部清空,
            //无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

无论是调用了quit方法还是quitSafely方法,MessageQueue将不再接收新的Message,此时消息循环就结束,MessageQueuednext方法将返回null,结束loop()的死循环.这时候再通过Handler调用sendMessagepost等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。

Message

Message.obtain()和new Message()如何选择

Message提供了obtain等多个重载的方法来创建Message对象,那么这种方式和直接new该如何选择。下面看看obtain的代码。

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(); //只有当从对象池里取不出Message才去new
}

void recycleUnchecked() {
        //清除所有使用过的痕迹
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

    //回收到对象池
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

从上面代码可以看出,通过obtain方法是从对象池取,而new是创建了一个新的对象。我们应该使用obtain来创建Message对象,每次使用完后都会自动进行回收,节省内存。

......

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

推荐阅读更多精彩内容