android 详解Handler内部源码机制
handler是我们android开发一定会用到的,如果说你没用到,那你还说是做android开发的,谁都不信,上帝也不会相信的,但是如果只停留在使用上,而不去分析内部实现,如果你去面试问你这个都不会,估计不太好吧,至少在面试官的影响中肯定是减分的.
我们知道Handler里面会涉及到几个类,都说轮询也就是Looper,还有什么消息队列,就是MessageQueue,那么他们之间是什么关系呢? 那就去Handler源码中神游下.
首先从Handler构造函数入手,
/** * Default constructor associates this handler with the {@link Looper} for the * current thread. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ public Handler() { this(null, false); }
我们调用一个无参的构造函数,但是它是去调用二个参数的构造函数,这不是主要的,主要的是它上面的注释,意思是通过一个默认的构造函数创建一个Handler对象,而且是在当前线程,也就是说你在哪个线程中new Handler,还有就是如果这个线程中没有与之关联的Looper对象,那这个handler无法接受到消息,
那么在UI线程中创建了一个Handler,但是并没有创建Looper啊,有的,只是系统帮我们做了,在activity的启动的时候通过ActivityThread中的入口main()方法中,
public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); 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()); // 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.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"); }
Looper.prepareMainLooper();通过这个代码,就会去创建Looper对象了,点击进去,看到底干了什么事,
/** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
看这个注释就知道了,在当前线程中创建一个Looper,它是应用启动的时候就创建了,所以你永远不需要调用这个方法.
它的第一行代码就是prepare(false),查下这个方法:
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)); }
你会发现这个方法是一个private修饰的,内部使用的,它是创建了一个Looper对象然后保存在ThreadLocal中去,ThreadLocal它的作用是可以在每个线程中存储数据的安全,比如你在A线程中发送了数据,B线程中发送了数据,怎么保证线程的数据不乱,就是通过ThreadLocal实现的,
现在回到Handler的构造函数中,如果是参数的构造函数,会调用带二个参数的构造函数,
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()方法获取Looper对象,
/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
看注释是返回与当前线程关联的Looper对象,如果调用线程与一个Looper没有关联,则为null.
mQueue = mLooper.mQueue;再看下这个是返回一个MessageQueue对象,就是消息队列,mQueue是在哪赋值的,
final MessageQueue mQueue;这是在Looper类中定义的成员变量,赋值是在构造函数中的,
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
从这可以看的出来,一个Looper对象对应一个MessageQueue,MessageQueue是一个链表的结构,那么我们在子线程中发送消息(Message),消息就入队列了,
/** * Pushes a message onto the end of the message queue after all pending messages * before the current time. It will be received in {@link #handleMessage}, * in the thread attached to this handler. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
看这个注释,最终会调到这个方法:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 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); }
enqueueMessage()方法就是把Message放入到队列中,
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
msg.targt = this,这是把Handler赋值给了Message.target,用来判断这个消息是从哪个Handler发送过来的,
queue.enqueueMessage(msg, uptimeMillis)这个就是真正把消息放入到哦队列中的,既然有消息入队列,我们都知道Looper是消息轮询的,轮询是Looper中的,
/** * 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) { 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 traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } 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(); } }
但是我们好像从来没有调用过这个方法啊,对吧,它是怎么去轮询这个MessageQueue队列中的消息呢?这是在ActivityThread中的main()方法倒数第二行代码被调用了,
if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop();
这就是为啥我们自己不手动调用loop()方法,如果有消息会怎么办,loop()方法中有一个重要的代码
try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } }
之前说了targete就是handler,调用dispatchMessage分发Message,
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
如果msg.callback==null,就调用handleMessage()处理这消息,那么这个消息处理是在子线程中还是主线程中呢,你这么想handler是异步消息处理机制,是更新UI的,如果是子线程中,能更新UI么,所以说这个消息处理一定是在主线程中,
现在花个图理解下:
其实这就是典型的多线程的生产者消费者模式,在子线程中生产(Message),在主线程中消费(Message).
现在几个常见的问题,也是面试常见的问题举例分析下:
1:我们都知道Handler的创建是在主线程,那么在子线程中能创建么,写个,
new Thread(){ @Override public void run() { super.run(); Handler handler = new Handler(); } }.start();
运行起来后发现报错了,
它说不能创建handler对象在子线程中,因为没有调用perpare()方法,之前我们也分析了这个方法,再把这个方法看下:
public static void prepare() { prepare(true); }
调用带参数的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)); }
这个是创建Looper对象的,所以说prepare()方法是创建Looper对象的,如果你创建Handler对象,只顾着发送消息,但是没有处理这个消息,那么队列中的消息是不是堆满了,对内存消息挺大,所以它就报错了,那解决的方法就是调用prepare()方法
new Thread(){ @Override public void run() { super.run(); Looper.prepare(); Handler handler = new Handler(); } }.start();
2:handler可以在主线程中发送消息么.那是可以的,只是如果在主线程中发送消息,处理消息也在主线程中,那干嘛还用Handler.
3:能在子线程中更新UI么? 可以的
new Thread(){ @Override public void run() { super.run(); Looper.prepare(); Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Toast.makeText(getApplicationContext(), "在子线程中更新UI", Toast.LENGTH_LONG).show(); } }; handler.sendEmptyMessage(1); Looper.loop(); } }.start();
有二个必须条件:
1:必须调用Looper.prepare()方法创建Looper对象
2:必须调用Looper.loop()去消息队列中轮询消息,否则你发送消息,没有Looper处理消息.
4:消息回收
在Looper.loop()轮询的时候,方法最后一行代码:
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();
最后一行代码可以看到msg调用了recycleUnchecked(),进入到这个方法
/** * Recycles a Message that may be in-use. * Used internally by the MessageQueue and Looper when disposing of queued Messages. */ void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. 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++; } } }
经历一系列的赋null操作,消息池中最大的消息数是50,
private static final int MAX_POOL_SIZE = 50;
5:很多框架的回调接口怎么让他在主线程中执行;
Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { //回调接口 } });