Android 源码分析(二)handler 机制

深刻浅出 Handler

此次我本身不折不扣弄懂 handler 机制了,真的,不信我讲给你听。java

从哪里讲起呢,我特地去翻了一下 Handler 的类注释说明,然而好像并无 get 到我想讲的东西,粗略看一下类注释。数据结构

A Handler allows you to send and process {@link Message} and Runnable objects associated with a thread's {@link MessageQueue}. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.多线程

没看懂没事,反正我看了翻译也不想懂,咱们换个角度来理解 handler。并发

Handler 我相信你们开发中确定都用过。没用过的出门左拐~~less

通常咱们用 Handler 都是用来作线程切换,可能说到这里,有同窗会想起一句话“子线程不能修改 ui,主线程不能作耗时操做”,没错,handler 的使用场景大多都是在异步任务中须要修改 ui。然并卵,这个咱们都知道,可是并不能让我完全理解 handler 的机制。异步

好了,不扯犊子了,耽误你们的时间。async

先来看一个错误的示范。ide

new Thread(new Runnable() {
	@Override
	public void run() {
		Handler handler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
				Toast.makeText(MainActivity.this,"lalala",0).show();
			}
		};
		handler.sendEmptyMessage(0);
	}
}).start();
复制代码

根据你们的经验求解,以上代码可否运行经过?为何oop

思考一分钟再看答案。源码分析

好了,思考结束,我贴运行结果了。

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
//报错行是 Handler handler = new Handler(){
复制代码

Why?Why?Why?稍后我再给你们解释。

这时有经验的同窗会说,子线程在建立 Handler 以前,须要先调用Looper.prepare();

那么,咱们来看一下Looper$prepare 方法吧。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
	prepare(true);
}

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));
}
复制代码

这个方法很简单,若是sThreadLocal.get() != null则抛异常,否则就执行sThreadLocal.set(new Looper(quitAllowed));建立一个 Looper,而且赋值给sThreadLocal。

Looper 的构造方法很简单,就保存了当前线程对象、而后建立了一个MessageQueue 对象,MessageQueue咱们稍后再介绍。

可能有些同窗不知道 ThreadLocal(我以前也不知道),偷懒的同窗能够直接把这个类丢到百度上一搜就知道了,ThreadLocal 解决多线程程序的并发问题提供了一种新的思路。说的接地气一点,就是不一样的线程从 ThreadLocal 能取出本身独有的数据,泛型 T 则是ThreadLocal里面取出来的数据类型。就是线程1调用ThreadLocal.set存了个对象 a,线程2再调用 ThreadLocal.get 方法是取不到数据的,只有线程1调用ThreadLocal.get方法才能取到这个数据。

ThreadLocal 在多线程篇好像没有讲,可是不要紧,咱们有扎实的 java 基础,若是让咱们本身手动实现一个ThreadLocal,也不过就半个小时的事。个人实现思路:基于 HashMap 作实现,key 是线程 id,vaule 是线程对应的值,而后建立一个 MyThreadLocal 来管理这个HashMap便可。

好,扯远了。Looper.prepare()就是给当前线程建立了一个Looper对象,存在了静态变量sThreadLocal里面。

而后咱们再来看看 Handler 的构造方法,看看为何没调用Looper.prepare()的状况下直接new 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());
		}
	}

	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.myLooper()分析这个 mLooper为何会为 null。

public static @Nullable Looper myLooper() {
	return sThreadLocal.get();
}
复制代码

噢,不说了,你们都看得懂。到这里,咱们解决了刚刚那个 demo 为何会抛出异常的缘由。得出了一个结论

  • 在子线程中建立 Handler 的时候必须先调用 Looper.prepare()方法。

可是?这个结论有什么卵用?别急,接着往下看。

new Thread(new Runnable() {
	@Override
	public void run() {
		Looper.prepare();
		Handler handler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
				Toast.makeText(MainActivity.this,"lalala",0).show();
			}
		};
		handler.sendEmptyMessage(0);
	}
}).start();
复制代码

而后,咱们加上了Looper.prepare();,又执行了一遍代码,同窗们思考一下此次可否正常执行而且弹出 Toast。

333

22

1

好了,我来告诉你们执行结果,执行结果就是没有任何结果,不报错,也没有任何响应,debug 发现 handleMessage方法并无被回调。咱们只好去看handler.sendEmptyMessage(0);是否有将消息发出去。

经过阅读 Handler 的源码,咱们发现,Handler 不论是调用postDelayed、sendEmptyMessage、post 等各类方法,最终都会调用enqueueMessage方法,咱们来看看这个方法。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	msg.target = this;
	if (mAsynchronous) {
		msg.setAsynchronous(true);
	}
	return queue.enqueueMessage(msg, uptimeMillis);
}
复制代码

其中MessageQueue 是构造方法的时候new 的,Message 是根据传参建立的,uptimeMillis 则是一个消息处理时间戳,用于判断消息是当即处理仍是稍后处理。

看到这里,仍是没看到为何 handler 发了消息没有回调 handleMessage 方法。

那就接着看 queue.enqueueMessage 吧

enqueueMessage ,顾名思义,就是信息入栈嘛,根据单一职能原则,这里大概不会找到为何没有回调 handleMessage 的缘由,可是咱们仍是来看一下吧。

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) {
            // 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;
            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;
}
复制代码

这里是 Message 的入栈操做,也就是把 Message 存到 MessageQueue 里面,具体实现你们能够不用纠结细节,我给你们简单讲解一下:MessageQueue 里面维护的是一个双向链表,enqueueMessage 方法根据参数 when,决定 Message 查到链表的哪一个位置。简单的说MessageQueue 就是一个集合,维护 Handler 消息的专属集合(虽然没有继承集合接口,可是数据结构是链表呀)。

到了这里,仍是没找到为何 handleMessage 方法没被回调的缘由。 思考一下,不少同窗确定都知道 handler 是一个消息轮询机制,一条消息只有被处理的时候才会调用 handleMessage,而消息是保存在 Message 里面,Message 由MessageQueue 维护着,咱们要处理消息,必须从 MessageQueue 去取。刚刚咱们找到了MessageQueue 的添加信息的方法,那么确定有消息被处理的时候须要出栈的操做,据此,咱们在MessageQueue 里面找到了 next()方法,用于消息的出栈,那么只须要找到 next 在哪被调用就知道了。

因而,又是一番寻找。在 Looper.loop()方法里面找到了MessageQueue 的 next 方法调用。刚刚咱们在建立 Looper 的时候,构造方法就new 了MessageQueue对象。

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        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();

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

        final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (slowDispatchThresholdMs > 0) {
            final long time = end - start;
            if (time > slowDispatchThresholdMs) {
                Slog.w(TAG, "Dispatch took " + time + "ms on "
                        + Thread.currentThread().getName() + ", h=" +
                        msg.target + " cb=" + msg.callback + " msg=" + msg.what);
            }
        }

        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();
    }
}
复制代码

这个方法比较长,我给你们简单解释一下。 首先这是一个静态方法,经过静态方法 myLooper()获取当前线程的 Looper 对象,而后取出Looper 里面的 MessageQueue,而后就走了死循环,不断的取出 MessageQueue 里面的 Message 进行消费。不少同窗都知道 Android 的主线程就是一个死循环,这里不扯远了。

咱们能够找到 msg.target.dispatchMessage(msg);这样一行代码,咱们看一下 handler 的这个方法:

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
		handleCallback(msg);
	} else {
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) {
				return;
			}
		}
		handleMessage(msg);
	}
}
复制代码

这里就很简单了,根据状态,决定调用那个方法处理消息。咱们能够轻易判断出这里调用的就是handleMessage。而后咱们在思考一下,这个 handler 是谁,在那里建立的。

前面咱们在 handler 的enqueueMessage()方法里面msg.target = this;把 handler 自己赋值给了 Message,因此msg.target.dispatchMessage(msg) 实际上调用的就是handler.sendEmptyMessage(0);这个 handler 自己,因此这里也是没毛病的。

到这里,如今就只差 Looper.loop()方法没被调用了,那么咱们手动调用一下试试?

而后有了以下代码:

new Thread(new Runnable() {
	@Override
	public void run() {
		Looper.prepare();
		Looper.loop();
		Handler handler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
				Toast.makeText(MainActivity.this,"lalala",0).show();
			}
		};
		handler.sendEmptyMessage(0);
	}
}).start();
复制代码

然而,仍是不行。同窗们再思考一下缘由?

333

22

1

好了,不逗你们了,Looper.loop();开启了一个死循环,子线程执行到这行代码就在死循环,后面的代码就不会往下走了。把这行代码移到 handler.sendEmptyMessage(0)后面便可。

还没讲完

一直没用过标题,怕大家看着累,我加个标题吧。 上面的这些讲解,咱们大概了解到了 handler 的工做机制。我给你们回顾一下。

1.调用 Looper.prepare();给当前线程建立一个 Looper,存在 Looper 的静态变量ThreadLocal里面。且这个方法在同一个线程只能调用一次,保证了一线程对应一个 Looper 。

2.Looper 的构造方法建立了MessageQueue 对象,因此Looper和 MessageQueue 也是一对一的关系。

3.Looper.loop()根据当前线程,获取到 Looper 对象,而后死循环MessageQueue的消息。

4.Handler 里面有个mLooper对象,默认赋值是 Looper.myLooper();

5.Handler 发生消息,只是将一个 Message 丢给 Handler 的成员变量mLooper里面的 MessageQueue 里面去。而后由Handler 里面的 mLooper 消费掉(前期是mLooper已经调用了loop 方法 开启死循环)。

大体就是酱紫吧。

再接着挖坑了,仍是刚刚那个例子。

new Thread(new Runnable() {
	@Override
	public void run() {
		Looper.prepare();
		Looper.loop();
		Handler handler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
				mBt.setText("asasA");
			}
		};
		handler.sendEmptyMessage(0);
	}
}).start();
复制代码

同窗们思考一下,此次代码可否正常执行。

333 22 1

好了,不给你们看运行错误日志了,看到这里,相信每次都认真思考过的同窗应该知道报错缘由了,没想出来也不要紧,咱们再来回顾一遍。

上面的分析中,主要牵涉到如下几个类。

Message

一个消息 bean。

MessageQueue

消息队列,能够当成是一个集合,可是数据结构是双向链表,只给 Looper 用,和 Looper 是一对一的关系。

Looper

线程能够没开启 Looper,可是最多只能开启一个,Looper 在构造方法里面建立一个 MessageQueue,在 loop()方法里面开启死循环不断从 MessageQueue 取Message,Message 消息在循环里面由Message 持有的 handler$handleMessage 方法处理。

Handler

在构造方法里面会绑定一个 Looper,默认绑定当前线程的 Looper,也能够指定一个 Looper 绑定。而后当 Handler 发送一个消息的时候,就把这个消息创封装成一个 Message,发送到绑定 Looper 的 MessageQueue 里面去,再被 Looper$loop 开启的死循环消费掉。

好像讲完了😰

上面的报错就是咱们熟悉的“子线程不能修改 ui”的错,是由 ViewRootImpl 检测抛出的异常,这个不属于handler 的内容,因此咱们在建立 Handler 的时候指定 handler 绑定主线程 Looper 便可。

好了,Handler 应该已经讲清楚了吧,有点像生产者消费者模型,哈哈哈哈哈~~

来,思考一下,谁是生产者,谁是消费者。

哦,对了,漏了几个知识点。

补充几个知识点

主线程Looper 问题

为何在主线程建立的 handler,能够在子线程handleMessage 修改 ui,而子线程却不能够呢? 这个问题在上面的分析过程已经讲过了,handler 的建立默认是绑定当前线程的 Looper,你在子线程建立 handler 的时候指定 handler 绑定 主线程的 Looper 便可,代码是

而后主线程的Looper 是在哪里建立的呢?

咱们都知道 Activity 的启动是从 ActivityThread 的 main 方法开始的(不知作别急,关注我,后面我会分析 Activity 的启动过程的),在 main 方法的结尾有这么几行代码。

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();
复制代码

好了,咱们的主线程要开启消息循环机制,也是须要调用 Looper.loop()的,在 ActivityThread 里面帮咱们作了而已。

为何主线程执行 looper 的死循环不会 ANR,而主线程作耗时操做,就会 ANR

先解释一下 ANR(Application not Response)应用无响应,单词应该没拼错。

刚刚咱们已经知道主线程的循环是在不断死循环去处理 MessageQueue 里面的消息,可是 MessageQueue 不只仅是咱们手动建立的 Handler 去往里面生产消息,更多的是各类系统的消息,好比说?UI 的刷新,应该也是在这里面处理的(我猜想,后期研究 View 源码的时候再验证哦,可是那传说中的16ms 刷新一次的屏幕,确定跟这个有关系)。因此,咱们在 Activity 主线程的某个方法里面作了耗时操做,会影响 MessageQueue 里面下一个 Message 的执行,若是下一个 Message 正好是刷新View。其实 CPU 执行效率很高,一秒钟能处理不少不少 message,好比说有100个,那么耗时操做1秒钟,就会致使后面100个message 的处理被滞后,这就形成了界面卡顿。

Activity 的 runOnUiThread 方法怎样切换线程的

这个就简单了,点进去看 Activity 对这个方法的实现。

final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}
复制代码

没什么好说的了,过!下一题

View 的 post 方法

public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}
复制代码

咳咳,这个,我暂时也解释很差,可是咱们能看到,若是 View 已经被显示到 Window 以后,会调用 handler 来处理这个 Runnable。没办法,我只好经过 debug 的方式,来跟大家证实这里也是把消息放到了主线程的 MessageQueue 里面去了。attachInfo.mHandler.mLooper 是main looper,对应的是 main 线程。

好了,Handler 消息机制的讲解及源码分析就到这里咯。

相关文章
相关标签/搜索