Android 线程与消息 机制 15问15答
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 线程与消息 机制 15问15答相关的知识,希望对你有一定的参考价值。
1.handler,looper,messagequeue三者之间的关系以及各自的角色?
答:MessageQueue就是存储消息的载体,Looper就是无限循环查找这个载体里是否还有消息。Handler就是创建的时候 会使用looper来构建这个消息循环。
handler的主要功能就是 将一个任务切换到某个指定的线程中去执行。
2.为何android 无法在子线程更新ui?
答:都知道 更新ui 实际工作都是在viewrootimpl这个类里面去做的。他的源码里有下面这样一个函数:
1 void checkThread() { 2 if (mThread != Thread.currentThread()) { 3 throw new CalledFromWrongThreadException( 4 "Only the original thread that created a view hierarchy can touch its views."); 5 } 6 }
在源码里 就限定了 你这个子线程是不能访问ui的。只有主线程能访问ui。原因其实很简单,你要是允许子线程也操作ui,那就得给ui 加上锁机制,
锁机制就太影响效率了。所以为了效率 就控制开发者 只能在主线程上访问ui。
3.简单概述一下Handler的工作原理?
答:其实很简单,关系理顺了就可以了。首先要明白 handler要创建成功 必须有一个前提条件 就是创建handler的线程 必须有looper。不然就会报错。
handler使用方法 一般就2个 post 传一个runnable 或者send一个消息。注意的是post方法 最后其实也是调用的send方法。这个send方法
最终会调用messagequeue的enqueuemessage方法,就是把消息放入队列中。与此同时looper 是一个无限循环 会无限从这个消息队列里面
取消息,取出来以后 消息中的runnable 或者 handler的handlerMessage方法就会被调用了。这个地方有一个关键的地方就在于 我们的looper
是运行在 创建handler的线程中的。所以looper在处理消息的时候 会把这个消息也运行在 创建handler的所在线程中。想明白这点 就明白了
handler的运行机制了。
4.简单介绍下threadLocal?
答:这个东西 在looper activitythread 以及ams中 都有大量应用。其实作用就是在指定的线程中存储数据。当你希望有一个数据 在不同线程中 都能独立保存自己的独立值
互相不干扰互相不影响的时候 就可以使用threadlocal了。他的作用域仅限于线程。
给个例子
1 package com.example; 2 3 public class MyClass { 4 5 6 public static void main(String[] args) { 7 8 ThreadLocal<Integer> mIntegerThreadLocal = new ThreadLocal<Integer>(); 9 mIntegerThreadLocal.set(0); 10 //输出结果为main thread threadlocal==0 11 System.out.println("main thread threadlocal==" + mIntegerThreadLocal.get()); 12 13 new Thread("Thread 1") { 14 @Override 15 public void run() { 16 mIntegerThreadLocal.set(0); 17 mIntegerThreadLocal.set(mIntegerThreadLocal.get() + 2); 18 //输出结果为 Thread 1 threadlocal==2 19 System.out.println("Thread 1 threadlocal==" + mIntegerThreadLocal.get()); 20 } 21 }.start(); 22 23 new Thread("Thread 2") { 24 @Override 25 public void run() { 26 //这里就会报空指针错误了 因为mIntegerThreadLocal 在这个线程中没有初始化他的值所以是null pointer 27 mIntegerThreadLocal.set(mIntegerThreadLocal.get() + 2); 28 System.out.println("Thread 1 threadlocal==" + mIntegerThreadLocal.get()); 29 } 30 }.start(); 31 32 33 } 34 }
5.从源码的角度 阐述threadlocal的原理?
答:
1 //首先我们可以看到 这是一个泛型, 我们的分析 建立在java 1.8 的基础上 其他java版本这里实现都有各自的不同 有兴趣的可以自己自行分析 2 public class ThreadLocal<T> { 3 4 //然后我们看一下 set方法 5 public void set(T value) { 6 //先取出当前的thread 7 Thread t = Thread.currentThread(); 8 //然后从getMap方法里 取t 也就是当前这个thread的数据 , ThreadLocalMap是一个静态内部类 你可以把他看做 存储threadlocal数据的一个载体 9 ThreadLocalMap map = getMap(t); 10 //如果取出来是空 那就给他set一个值进去 也就是更新这个threadlocal的值 11 if (map != null) 12 map.set(this, value); 13 else 14 //否则就创建一个ThreadLocalMap的值 15 createMap(t, value); 16 } 17 18 //getMap就是取出这个线程的threadLocals 如果这个值为null 就说明这个线程还从来没有使用过threadlocal对象 19 ThreadLocalMap getMap(Thread t) { 20 return t.threadLocals; 21 } 22 23 24 25 //我们可以先跟踪createMap方法 也就是创建ThreadLocalMap的流程 26 void createMap(Thread t, T firstValue) { 27 //这里thread的threadLocals 的值 就是在这里被赋值了,也就是ThreadLocalMap对象 28 //可以明确 一个线程T,就有一个唯一的ThreadLocalMap对象,里面存储的就是 29 //这个线程里的th对象。也就是threadlocal 30 t.threadLocals = new ThreadLocalMap(this, firstValue); 31 } 32 33 //可以看到是用这个构造方法来构造的 34 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 35 //首先是创造了一个table数组 36 table = new Entry[INITIAL_CAPACITY]; 37 //然后根据一定的算法 计算出来 我们传进来的这个ThreadLocal 应该在table数组里的位置 也就是i 38 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 39 //然后把i位置上对应的值 赋值进去,有点类似于hashmap的流程 40 table[i] = new Entry(firstKey, firstValue); 41 size = 1; 42 setThreshold(INITIAL_CAPACITY); 43 //所以这里要明确的就是一个th的值 在table数组里 就对应着一个位置, 44 } 45 46 47 48 //简单浏览一下Entry的数据结构 49 static class Entry extends WeakReference<ThreadLocal<?>> { 50 /** The value associated with this ThreadLocal. */ 51 Object value; 52 53 Entry(ThreadLocal<?> k, Object v) { 54 super(k); 55 value = v; 56 } 57 } 58 59 60 61 62 //前面我们简单分析了 threadlocal 第一次被创建时的 存储过程 63 //然后看我们的set方法 也就是更新threadlocal的过程 64 //这里的逻辑很简单 当某个线程t 已经有了threadLocals的值以后 第二次再调用set方法 就会走到这里了。第一次调用set方法 可以看上面的createMap流程 65 //当第二次或者第n次调用set方法 以后 就会根据th的值 在table里遍历 找到这个th对应的值,因为一个线程t 可能有n个不同的th变量。 66 //这个函数就是根据你传进去的th变量 找到对应的位置 来更新他的值 67 private void set(ThreadLocal<?> key, Object value) { 68 69 // We don‘t use a fast path as with get() because it is at 70 // least as common to use set() to create new entries as 71 // it is to replace existing ones, in which case, a fast 72 // path would fail more often than not. 73 74 Entry[] tab = table; 75 int len = tab.length; 76 //通过我们传进去的theradlocal 取出table数组里的值 table[i] 77 int i = key.threadLocalHashCode & (len-1); 78 79 80 for (Entry e = tab[i]; 81 e != null; 82 e = tab[i = nextIndex(i, len)]) { 83 ThreadLocal<?> k = e.get(); 84 85 if (k == key) { 86 e.value = value; 87 return; 88 } 89 90 if (k == null) { 91 replaceStaleEntry(key, value, i); 92 return; 93 } 94 } 95 96 tab[i] = new Entry(key, value); 97 int sz = ++size; 98 if (!cleanSomeSlots(i, sz) && sz >= threshold) 99 rehash(); 100 } 101 102 103 //再来看一下 threadlocal的get方法 104 public T get() { 105 Thread t = Thread.currentThread(); 106 //先看看当前线程t的threadLocals 有没有被赋值 107 ThreadLocalMap map = getMap(t); 108 //如果不等于null 也就是说当前线程的threadLocals 值已经有了 那我们就直接取对应的value值即可 109 if (map != null) { 110 ThreadLocalMap.Entry e = map.getEntry(this); 111 if (e != null) { 112 @SuppressWarnings("unchecked") 113 T result = (T)e.value; 114 return result; 115 } 116 } 117 //否则我们就给他返回一个初始值 这个初始值 你跟进去看源码就会发现是null了 118 return setInitialValue(); 119 } 120 121 //简单分析了 threadlocal 的set和get方法 总结起来就是 他的set和get操作 就是以当前线程为key 取对应的值。可以简单类比想象成是一个哈希表 这个哈希表的key就是线程。 122 //你要操作th的值 就得把当前的线程t 传到这个表里面 然后取得对应线程t的 对应value 即可。
6.从源码的角度 阐述MessageQueue的原理?
答:
1 //我们首先来看一下enqueueMessage的操作 这个操作主要是对消息队列进行一个插入消息的操作 2 //这个你细细看一下 就是一个很简单的单链表的操作罢了,随意虽然这东西 叫消息队列 但是其实跟队列并没有什么关系 3 //内部实现为一个链表 4 boolean enqueueMessage(Message msg, long when) { 5 if (msg.target == null) { 6 throw new IllegalArgumentException("Message must have a target."); 7 } 8 if (msg.isInUse()) { 9 throw new IllegalStateException(msg + " This message is already in use."); 10 } 11 12 synchronized (this) { 13 if (mQuitting) { 14 IllegalStateException e = new IllegalStateException( 15 msg.target + " sending message to a Handler on a dead thread"); 16 Log.w(TAG, e.getMessage(), e); 17 msg.recycle(); 18 return false; 19 } 20 21 msg.markInUse(); 22 msg.when = when; 23 //mMessages 这个你可以把他看成是单链表的表头 24 Message p = mMessages; 25 boolean needWake; 26 //当你第一次往消息队列里 插入消息的时候,表头就变成了你第一次传进来的这个消息 27 if (p == null || when == 0 || when < p.when) { 28 // New head, wake up the event queue if blocked. 29 msg.next = p; 30 mMessages = msg; 31 needWake = mBlocked; 32 } else { 33 // Inserted within the middle of the queue. Usually we don‘t have to wake 34 // up the event queue unless there is a barrier at the head of the queue 35 // and the message is the earliest asynchronous message in the queue. 36 needWake = mBlocked && p.target == null && msg.isAsynchronous(); 37 38 //如果不是第一次传消息 39 //简单的链表操作不多做分析 40 Message prev; 41 for (;;) { 42 prev = p; 43 p = p.next; 44 if (p == null || when < p.when) { 45 break; 46 } 47 if (needWake && p.isAsynchronous()) { 48 needWake = false; 49 } 50 } 51 msg.next = p; // invariant: p == prev.next 52 prev.next = msg; 53 } 54 55 // We can assume mPtr != 0 because mQuitting is false. 56 if (needWake) { 57 nativeWake(mPtr); 58 } 59 } 60 return true; 61 } 62 63 64 //next方法就是从消息队列中 取出一个消息 然后删除他 65 Message next() { 66 // Return here if the message loop has already quit and been disposed. 67 // This can happen if the application tries to restart a looper after quit 68 // which is not supported. 69 final long ptr = mPtr; 70 if (ptr == 0) { 71 return null; 72 } 73 74 int pendingIdleHandlerCount = -1; // -1 only during first iteration 75 int nextPollTimeoutMillis = 0; 76 //注意看这个for循环 括号体内是没有break语句的 也就是说这个循环 永远不会退出!如果这个消息队列里没有消息的话 77 //这个next方法 就阻塞了 因为你仔细看 这个里面没有break语句 只有当取出消息的时候才会return一个message回去 78 //只有这种情况成立的时候 这个循环才会结束 这个函数才会结束,除此之外 这个函数就永远阻塞在这里 79 for (;;) { 80 if (nextPollTimeoutMillis != 0) { 81 Binder.flushPendingCommands(); 82 } 83 84 nativePollOnce(ptr, nextPollTimeoutMillis); 85 86 synchronized (this) { 87 // Try to retrieve the next message. Return if found. 88 final long now = SystemClock.uptimeMillis(); 89 Message prevMsg = null; 90 Message msg = mMessages; 91 if (msg != null && msg.target == null) { 92 // Stalled by a barrier. Find the next asynchronous message in the queue. 93 do { 94 prevMsg = msg; 95 msg = msg.next; 96 } while (msg != null && !msg.isAsynchronous()); 97 } 98 if (msg != null) { 99 if (now < msg.when) { 100 // Next message is not ready. Set a timeout to wake up when it is ready. 101 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); 102 } else { 103 // Got a message. 104 mBlocked = false; 105 if (prevMsg != null) { 106 prevMsg.next = msg.next; 107 } else { 108 mMessages = msg.next; 109 } 110 msg.next = null; 111 if (DEBUG) Log.v(TAG, "Returning message: " + msg); 112 msg.markInUse(); 113 return msg; 114 } 115 } else { 116 // No more messages. 117 nextPollTimeoutMillis = -1; 118 } 119 120 // Process the quit message now that all pending messages have been handled. 121 if (mQuitting) { 122 dispose(); 123 return null; 124 } 125 126 // If first time idle, then get the number of idlers to run. 127 // Idle handles only run if the queue is empty or if the first message 128 // in the queue (possibly a barrier) is due to be handled in the future. 129 if (pendingIdleHandlerCount < 0 130 && (mMessages == null || now < mMessages.when)) { 131 pendingIdleHandlerCount = mIdleHandlers.size(); 132 } 133 if (pendingIdleHandlerCount <= 0) { 134 // No idle handlers to run. Loop and wait some more. 135 mBlocked = true; 136 continue; 137 } 138 139 if (mPendingIdleHandlers == null) { 140 mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; 141 } 142 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); 143 } 144 145 // Run the idle handlers. 146 // We only ever reach this code block during the first iteration. 147 for (int i = 0; i < pendingIdleHandlerCount; i++) { 148 final IdleHandler idler = mPendingIdleHandlers[i]; 149 mPendingIdleHandlers[i] = null; // release the reference to the handler 150 151 boolean keep = false; 152 try { 153 keep = idler.queueIdle(); 154 } catch (Throwable t) { 155 Log.wtf(TAG, "IdleHandler threw exception", t); 156 } 157 158 if (!keep) { 159 synchronized (this) { 160 mIdleHandlers.remove(idler); 161 } 162 } 163 } 164 165 // Reset the idle handler count to 0 so we do not run them again. 166 pendingIdleHandlerCount = 0; 167 168 // While calling an idle handler, a new message could have been delivered 169 // so go back and look again for a pending message without waiting. 170 nextPollTimeoutMillis = 0; 171 } 172 }
7.从源码的角度 阐述Looper的原理?
答:
1 //这是一个典型的在子线程里创建handler的操作 我们看到在这里创建handler的时候 我们做了2步操作 2 //第一步是创建looper 第二步是开启消息循环。 3 new Thread(){ 4 @Override 5 public void run() { 6 Looper.prepare(); 7 Handler handler=new Handler(); 8 Looper.loop(); 9 } 10 }.start(); 11 12 13 //我们主要看loop 这个方法 因为只有调用了这个方法 消息循环才真正启动 14 15 public static void loop() { 16 final Looper me = myLooper(); 17 if (me == null) { 18 throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread."); 19 } 20 final MessageQueue queue = me.mQueue; 21 22 // Make sure the identity of this thread is that of the local process, 23 // and keep track of what that identity token actually is. 24 Binder.clearCallingIdentity(); 25 final long ident = Binder.clearCallingIdentity(); 26 //可以看到 这又是一个死循环 27 for (;;) { 28 Message msg = queue.next(); // might block 29 //唯一跳出循环的方式 就是这个msg 为null 也就是消息队列的next方法返回null 30 //而next方法返回null的唯一方法 就是调用looper的quit方法 所以这里就能得知 31 //looper你必须手动帮他退出 否则loop方法就是无限循环下去 32 if (msg == null) { 33 // No message indicates that the message queue is quitting. 34 return; 35 } 36 37 // This must be in a local variable, in case a UI event sets the logger 38 Printer logging = me.mLogging; 39 if (logging != null) { 40 logging.println(">>>>> Dispatching to " + msg.target + " " + 41 msg.callback + ": " + msg.what); 42 } 43 44 //这个地方msg 就是发过来的消息,也就是消息队列那个链表里面存储的message ,target就是发送这条消息的handler对象。 45 //所以这里就能明白最终消息 还是交给handler的dispatchmessage方法来处理的 只不过这个dispatchmessage方法 是在创建handler 46 //的时候 所使用的looper去执行的 而我们创建looper的时机 正是在线程的run方法里,所以 代码逻辑的最终执行就是在创建handler 47 //的那个线程里 48 msg.target.dispatchMessage(msg); 49 50 if (logging != null) { 51 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 52 } 53 54 // Make sure that during the course of dispatching the 55 // identity of the thread wasn‘t corrupted. 56 final long newIdent = Binder.clearCallingIdentity(); 57 if (ident != newIdent) { 58 Log.wtf(TAG, "Thread identity changed from 0x" 59 + Long.toHexString(ident) + " to 0x" 60 + Long.toHexString(newIdent) + " while dispatching to " 61 + msg.target.getClass().getName() + " " 62 + msg.callback + " what=" + msg.what); 63 } 64 65 msg.recycleUnchecked(); 66 } 67 }
8.使用looper需要注意什么?
答:不需要的时候 记得终止looper。因为如果你手动处理完毕你需要的业务逻辑以后 如果不调用quit或者quitsafely方法 looper的loop方法就一直执行下去,永远不停止,你这个子线程永远都结束不了。
很容易就内存泄露 或者其他错误,所以我们要牢记 当子线程使用looper的时候 业务处理完毕 记得手动关闭looper。
9.从源码的角度 阐述handler的工作原理?
答:
1 //handler有2个发消息的方法一个是post 一个是send 只不过post最终也是走的send 2 //可以看出来 handler发送消息 其实就是往消息队列里放了一个message罢了 3 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 4 MessageQueue queue = mQueue; 5 if (queue == null) { 6 RuntimeException e = new RuntimeException( 7 this + " sendMessageAtTime() called with no mQueue"); 8 Log.w("Looper", e.getMessage(), e); 9 return false;
以上是关于Android 线程与消息 机制 15问15答的主要内容,如果未能解决你的问题,请参考以下文章
Android Framework | 消息机制的冷门知识点,你能答出来几个?