从源码分析Handler机制

一、发送消息

当发送一个消息,在handler里面最后会执行方法:

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

然后在MessageQueue里面走方法:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            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;
            // 这个when=SystemClock.uptimeMillis() + delayMillis
            // SystemClock.uptimeMillis()表示系统开机到现在的时间,这个在下面消息排序有用
            // if表示新消息可以作为头一个,可以唤醒事件队列了
            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 {// 在此之前有消息,需要把新消息插入队列
                // 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是按时间先后构成的优先级队列
                这是一个无限循环,其实就是对MessageQueue的轮询
                消息池MessageQueue中存的是一个一个Message
				p是message,所以p.next就是取下一个消息,上一条消息指向下一条消息
				*/
                for (;;) {
                    prev = p;
                    p = p.next;
                    // 如果新消息的发生时间早于当前消息,跳出for循环
                    // 所有MessageQueue是这样的
                    // m1->m2->m3->m4,假如p是m3,就是把新消息插入m2和m3之前
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 把新消息插入刚才循环到的消息p前
                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;
    }

二、取消息

在MessageQueue中有个next方法,处理消息时取下一个消息。

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        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) {
                    // 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;
                }

            // 。。。
        }
    }

取出消息给谁用呢?
Looper,looper里有一个loop方法:

    public static void loop() {
    	final Looper me = myLooper();
        if (me == null) {
        	// 如果当前线程没有初始化Looper会抛出异常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // ...

        me.mInLoop = true;
        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();

        boolean slowDeliveryDetected = false;

        for (;;) {
        // 这里又是一个死循环,不断从MessageQueue里面取消息,但是如果没有消息时,
        // queue.next()方法会block锁住,就是没有任何返回,程序不会往下走了。
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

			// ...
			try {
			// 取到消息之后,调用dispatchMessage,这个target在上面发送消息时候赋值的
			// 它是Handler,所以在handler中dispatchMessage()方法有调用了handleMessage
			//这个就是我们使用handler时要重写的方法。
                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);
                }
            }
            // ... 
        }
    }

这个方法是静态的,只要应用一启动它就会跑起来,这也是程序存活的条件,如果这个loop方法死掉了,那程序也就挂掉了,所以主线程的Looper不可以销毁,这一点从源码也可以找到:

// Looper里面有两个quit方法,他们调用了MessageQueue的quit方法
    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
// MessageQueue的quit方法进来就要检查是否可以退出,从异常信息可以看出,不允许在主线程调用。
    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
这样整个流程就清楚了
发送消息:Handler.sendMessage --> MessageQueue.enqueueMessage --> 进入消息队列
处理消息:Looper.loop --> MessageQueue.next --> Handler.dispatchMessage --> Handler.handleMessage

三、结合对源码的分析来思考几个问题

  1. 一个线程有几个handler?

    答:多个,例如我们可以在多个Activity中创建Handler

  2. 一个线程有几个looper?如何保证?

    答:一个,如何保证的?看源码

    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));
    }
创建Looper要走Looper的prepare方法,如果get()到了说明已经创建了,它会抛出异常,说明这个prepare方法只能被调用一次;如果没有创建,就new一个Looper,而且它是ThreadLocal修饰的,作用域只在当前线程。
ThreadLocal结构,
之前是Object[],奇数存key,偶数存value,{k1, v1, k2, v2 ...}
现在是Entry[]了,Entry extends WeakReference<ThreadLocal<?>>,它的key是持有当前线程的弱引用,value是Object类型。
  1. handler内存泄漏原因?为什么其他内部类没有这样的情况?

    先看源码:
    发消息的方法最终都会走到Handler的这个方法,不管是延迟消息还是即时消息

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
这里可以看出把当前Handler作为Message的target了,Handler又持有Activity的引用,所以当我们发送了一个延迟消息,时长不确定,假如消息没处理退出了activity,这时候就有一个引用链导致内存泄漏,MessageQueue->Message->Handler->Activity;一般的内部类,在activity销毁时基本也都销毁了,所以不会导致内存泄漏,当然,如果其他的内部类也有Handler这种机制的也会导致内存泄漏。
解决方案:1、Handler使用弱引用;2、activity的onDestroy中移除所有消息
  1. 为什么主线程可以new Handler()?子线程创建handler作何准备?

    其实在任何线程创建Handler都是以下3步。

Looper.prepare();
Handler handler = new Handler();
Looper.loop();

而且子线程创建Handler处理完消息还要调用Looper.quit,这个下面问题会说明。

为什么主线程没有prepare和loop?看源码,当应用启动,会执行一系列操作,其中包括ActivityThread中的main函数如下,我们主线程的任何代码都执行在节点1和节点2中间,任何,是任何。所以当我们在主线程创建handler,直接new就行了;在子线程创建handler需要3步。

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // 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();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

		// 节点1,重点就在这里,准备主线程的looper
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        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);
		// 节点2
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
其实这一点从最上面的loop方法第一行也能看出来,走loop方法首先会获取looper,如果为空会抛出异常,在子线程直接new Handler那就会奔溃了。
  1. 子线程中维护的Looper,消息队列无消息的时候如何处理的?作用是什么?

这里用一个例子说明,我们在子线程创建一个Handler,写3个log,然后用我们创建的handler发送一个消息,log1和log2打印出来,但是log3是不走的,为什么?因为没有消息时Looper.loop()方法是阻塞的,在上面源码分析中说过。

    private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler mHandler = new Handler() {

                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        super.handleMessage(msg);
                        Log.e("Handler", "log1 handleMessage");
                    }
                };
                Log.e("Handler", "log2 running...");
                Looper.loop();
                Log.e("Handler", "log3 handler end");
            }
        }).start();
    }

调用quit方法会进入MessageQueue.quit,它里面会唤醒线程,而且有一个mQuitting = true;
然后在MessageQueue.next中会返回一个空消息对象

    Message next() {
        // Process the quit message now that all pending messages have been handled.
        if (mQuitting) {
            dispose();
            return null;
        }
    }

然后在Looper.loop的for循环中就退出来了,继续我们的代码逻辑了

public static void loop() {
	// ...
	for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        // ...
   }
}

处理方案:子线程无消息时处理方案就是调用Looper.quitSafely,释放内存,释放线程

  1. 既然可以存在多个Handler往MessageQueue里发消息(Handler可能在不同线程),它内部是如何确保线程安全的?

    答:添加消息和取消息方法中都加锁了,synchronized,所以时间不一定准确

  2. 如何创建Message对象?

    答:Message.obtain,不能用new Message,这种设计成为享元设计模式。因为像Message这种会频繁创建销毁的对象有可能在使用完释放的时候还有其他地方引用导致释放不完全,如果不断new就会出现内存不断增加的情况,也叫内存抖动,如下:
    在这里插入图片描述
    为了避免这种情况出现,采用消息池的方式来解决。来看obtain的源码:

    public static Message obtain() {
    	// 这里要考虑线程安全,所以加锁
        synchronized (sPoolSync) {
            if (sPool != null) {
            	// 如果消息池有可用消息,那么把旧的message对象reset了,消息池size自减,并返回message对象。
            	// sPool是Message类型,它为什么是消息池呢?
            	// Message里面还有一个next,也是Message对象
            	// 类比String数组, 消息池的元素类型是Message,每一个元素又持有下一条消息的引用
            	// 而且这个消息池的大小是50.
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        // 如果没有可用消息,重新new一个。
        return new Message();
    }
  1. 使用Handler的postDelay后消息队列会有什么变化?

    答:如果MessageQueue里面没有消息,在MessageQueue的enqueueMessage添加一条消息,然后在next中会计算消息消息触发的时间,如果时间没到 又会休眠,等到了消息触发时间点会唤醒去执行消息;
    如果MessageQueue里面有消息,那么会在MessageQueue的enqueueMessage中按时间顺序添加到MessageQueue中,next方法中正常取消息。

  2. 主线程Looper.loop死循环为什么不会导致应用卡死(ANR)?

    答:Looper.loop()这个死循环是应用运行的必要条件,上面说过应用的一切执行逻辑都运行在loop()中,如果它不运行了应用就挂掉了;

先说下ANR:5秒内无法响应屏幕触摸事件或键盘输入事件;广播的onReceive()函数时10秒没有处理完成;前台服务20秒内,后台服务在200秒内没有执行完毕;ContentProvider的publish在10s内没进行完。所以大致上Loop死循环和ANR联系不大,问了个正确的废话,所以触发事件后,耗时操作还是要放在子线程处理,handler将数据通讯到主线程,进行相关处理。线程实质上是一段可运行的代码片,运行完之后,线程就会自动销毁。当然,我们肯定不希望主线程被over,所以整一个死循环让线程保活。
为什么没被卡死:在事件分发里面分析了,在获取消息的next()方法中,如果没有消息,会触发nativePollOnce方法进入线程休眠状态,释放CPU资源,MessageQueue中有个原生方法nativeWake方法,可以解除nativePollOnce的休眠状态,ok,咱们在这俩个方法的基础上来给出答案。当消息队列中消息为空时,触发MessageQueue中的nativePollOnce方法,线程休眠,释放CPU资源消息插入消息队列,会触发nativeWake唤醒方法,解除主线程的休眠状态当插入消息到消息队列中,为消息队列头结点的时候,会触发唤醒方法当插入消息到消息队列中,在头结点之后,链中位置的时候,不会触发唤醒方法综上:消息队列为空,会阻塞主线程,释放资源;消息队列为空,插入消息时候,会触发唤醒机制这套逻辑能保证主线程最大程度利用CPU资源,且能及时休眠自身,不会造成资源浪费本质上,主线程的运行,整体上都是以事件(Message)为驱动的。

官方给出的产生ANR一般有三种类型:
1:KeyDispatchTimeout(5 seconds) –主要类型
按键或触摸事件在特定时间内无响应
2:BroadcastTimeout(10 seconds)
BroadcastReceiver在特定时间内无法处理完成
3:ServiceTimeout(20 seconds) –小概率类型
Service在特定的时间内无法处理完成

就是说你在主线程中做了一件规定时间内没有反馈的事,系统应用程序就会给你抛出ANR。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值