Android---探究Handler消息机制

在开发Android应用程序的过程中,如果耗时的操作放在Activity的主线程(UI线程)中执行的话,超过五秒钟的时候,就会出现传说中的ANR(Application Not Responsding),即应用无响应。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现ANR。

所以在进行一些耗时操作的时候,我们应该放在子线程中去完成的,完成之后如果我们要更新主线程中的UI视图的话,就要用到Handler了,因为Android系统是禁止在子线程中去更新UI视图的

总之一句话,Handler是用来进行线程间通信的!

先来看看主线程和子线程之间通信的时候Handler怎么用吧:

在主线程中,使用handler很简单,new一个Handler对象实现其handleMessage方法,在 handleMessage 中提供收到消息后相应的处理方法即可。(接收消息,并且更新UI)

private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case testMessageWhat:
                    LogUtils.d("主线程的handler收到了子线程的消息:" + msg.obj);
                    //此时下面可以做更新UI的处理了
                    break;
                default:
                    break;
            }
        }
    };
    
	private final int testMessageWhat = 1;

    private void sendMessageInThread(){
        new Thread(() -> {
            Message message = mHandler.obtainMessage();
            message.what = testMessageWhat;
            message.obj = "我是子线程里的message";
            mHandler.sendMessage(message);
        }).start();
    }

然后调用sendMessageInThread方法运行起来的时候就可以看到日志有打印:
在这里插入图片描述
我们来看看Message有哪些属性:
Message.what:在Hander中用来标识是哪一个子线程发送过来的数据,从而可以对其进行一定规则的处理。
Message.arg1/Message.arg2:arg1和arg2是用来存放整型数据的。
Message.obj:obj可以是Object类型的任意对象。

前面说了,Handler是用来进行线程间通信的,上面只是演示了主线程和子线程之间进行通信,我们还可以在两个子线程之间用Handler来通信。

那根据上面的用法,我们定义一个全局变量Handler,在子线程中去new一个Handler的对象接收消息并处理,然后在另一个子线程里用这个handler去发送消息就行了嘛。我们来试试看能不能行:

	private Handler mHandler;

    private final int testMessageWhat = 1;

    private void handleMessageThread() {
        new Thread(() -> {
            mHandler = new Handler() {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case testMessageWhat:
                            LogUtils.d("handleMessage中收到消息:" + msg.obj);
                            break;
                        default:
                            break;
                    }
                }
            };
        }).start();
    }

    private void sendMessageThread() {
        new Thread(() -> {
            Message message = mHandler.obtainMessage();
            message.what = testMessageWhat;
            message.obj = "我是sendMessageThread中的消息";
            mHandler.sendMessage(message);
        }).start();
    }
	
	@Override
    public void initView() {
        handleMessageThread();
        sendMessageThread();
    }

哦豁,跑起来之后你会发现崩掉了:
在这里插入图片描述
它说我们创建的handler之前没有调用Looper.prepare(); 哦豁,那我就加上嘛:

new Thread(() -> {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case testMessageWhat:
                            LogUtils.d("handleMessage中收到消息:" + msg.obj);
                            break;
                        default:
                            break;
                    }
                }
            };
        }).start();

听系统的果然没错,加了之后就不报错了!等等,但是我也没有收到消息啊,没看到有日志打印啊喂!
好吧其实这里还要调用一个方法:Looper.loop();,调用之后运行就可以看到日志打印了

好现在到了关键时刻了,我们来看一下为什么,刚刚new Handler的时候会报错,用工具查看一下源码发现:
在这里插入图片描述
在Handler的构造方法中,发现当mLooper为空的时候,才会抛出这个异常,而mLooper又是由Looper.myLooper()方法拿到的,我们点进Looper.myLooper()方法看它干了什么
在这里插入图片描述
可以看到,myLooper这个方法就返回了一个ThreadLocal,说明get方法返回的是null。就凭我看到sThreadLocal.get()这个方法,我就猜到一定会有set方法,那具体在哪儿呢,我们刚刚调用了Looper.prepare()之后就没有报错了,那答案肯定藏在prepare这个方法里面,看下源码:
在这里插入图片描述
果然,在prepare方面里面,我们看到了ThreadLocal的set方法,传入了一个Looper对象,我们看看Looper的构造方法里做了什么:
在这里插入图片描述
到这个地方为止,我们已经成功的引出了Handler机制的五个关键对象了:Handler、Looper、MessageQueue、Message、ThreadLocal。说实话,当我第一次看到这几个关键对象时,我的脑海中就犹如柯南每次破案的时候闪过的那一道白光一样闪了一下:
在这里插入图片描述
喏就是这样。如果你英语还不错的话,你可以直接翻译这几个单词的意思:

Handler:处理者。 Looper:循环器。 MessageQueue:消息队列。Message:消息

看到这几个词的时候,我当时脑海一下子就把它们串连起来了,即:循环器(Looper)不停的从消息队列(MessageQueue)中拿出消息(Message)给处理者(Handler)去处理。这就是Handler的核心思想。

这其中,ThreadLocal 是一个线程内存储数据的类,当不同的线程去访问同一个 ThreadLocal 对象时,获得的值都是不一样的,这样就可以保证,不管是在UI线程还是子线程中,我们拿到的looper对象都是唯一的,即:在Android体系中,每个线程有且只有一个Looper 对象。

那现在似乎有了新的问题了,为什么我们在Activity中全局变量的地方直接去new Handler的时候,没有调用Looper.prepare()方法呢?

你如果写过JAVA程序的话会知道,每个JAVA程序都有一个main方法作为入口,那你想没想过在Android里面程序的入口在哪儿?好吧其实是在ActivityThread里面,每个APP启动的时候都会调用ActivityThread的main方法,我们去翻下源码看一下:在这里插入图片描述
看到我红框里面圈出来的地方是不是一目了然了,原来在APP启初始化的时候,系统会帮我们去调用Looper.prepareMainLooper()方法,顾名思义,是初始化一个MainLooper给我们主线程去使用的,此后,每当我们在子线程想要new Handler对象的时候,都要手动的去初始化一个Looper。

弄清楚Handler的大体流程之后,我们来看下Handler是怎么发送消息的,在源码里搜一下send关键字:
在这里插入图片描述
你会发现有很多send方法,用来发送各种各样的消息,但是如果你追踪到最后的话会发现,所有的send方法,最终都会走到sendMessageAtTime方法,我们看下:
在这里插入图片描述
可以看到返回的是一个enqueueMessage方法,我们看看这个方法干了什么:
在这里插入图片描述
就是在其内部调用了MessageQueue的enqueueMessage方法:
在这里插入图片描述
从源码中可以看到Message最终被存入的地方是MessageQueue中的上一个(prev)的Message.next上,这样就跟链表一样组成了一个链式的队列。同时也通过when属性保证了消息队列的时序性。

好,到这里为止,Handler发送消息算是看了个大概,知道是通过handler放到了MessageQueue里面去排队等着被取出来了,那这个Message怎么被取出来的呢?

还记不记得刚刚在子线程创建handler的时候报错,然后我们加了Looper.prepare方法去初始化looper之后就不报错了,但是也收不到消息,在我们加了Looper.loop方法之后就可以收到消息了?那我敢肯定,消息一定是在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();
        if (me == null) {
        	//这个异常可以看出来在没有调用Looper.prepare方法初始化looper之前是不能调用loop方法的
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //拿到了消息队列
        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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

		//开启死循环不断地从looper内的MessageQueue中取出Message
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
            	//开始分发消息了
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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);
            }

            msg.recycleUnchecked();
        }
    }

从源码中可以看到,在开启死循环不停的从消息队列中拿消息之后,只要有Message对象,就会通过Message的target调用dispatchMessage去分发消息,我们来看下dispatchMessage的源码:
在这里插入图片描述
谷歌的工程师其实已经给出详细注释了:在这里处理系统消息.
我们自己是可以通过源码观察到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法,比如我们的runOnUiThread(Runnable)
如果没有设置callback的话,就会走到我们熟悉的 handleMessage方法中,即我们在new Handler的时候重写的那个方法,由我们自己去决定怎么处理这个消息
在这里插入图片描述
这就是Handler消息机制的大体流程,那在我们日常使用Handler的时候,有几个要注意的地方:
1.内存泄漏:我们说Handler是处理耗时操作的回调的,那在触发handleMessage方法的时候,Activity假如已经死掉了呢?

private void handleMessageThread() {
        new Thread(() -> {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case testMessageWhat:
                            LogUtils.d("handleMessage中收到消息:" + msg.obj);
                            //其实,这里隐含了Activity的引用,其实全称应该是MainActiivty.this.click();
                            click()
                            break;
                        default:
                            break;
                    }
                }
            };
            Looper.loop();
        }).start();
    }

我们看在handleMessage方法中,我们调用了MainActivity的click方法,其实这里面包含的有Activity的引用,那这个时候假设在子线程的耗时操作中MainActivity已经死掉了呢?你这个时候再去调用click方法,不就造成内存泄漏了!

解决办法:百度上有很多,一般是使用静态内部类+弱引用的方式

2.Message优化:我们在创建Message对象时,一般是通过Handler.obtainMessage()来获取Message对象的,它的内部是调用的是Message.obtain()方法。我们来看下Message.obtain方法:
在这里插入图片描述
在Message类中有一个static Message变量sPool,这个sPool是用来缓存Message对象的,如果sPool不为空则会返回当前sPool,也就是返回Message。把sPool(Message)赋给m对象,然后把m的下一个对象指向了sPool,之后把m清空,作为一个新的空消息返回。如果sPool为空,则没有缓存的Message,则需要创建一个新的Message(new Message)
总结:
obtainmessage()是从消息池中拿来一个msg 不需要另开辟空间new
new需要重新申请,效率低,obtianmessage可以循环利用;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值