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     }
View Code

在源码里 就限定了 你这个子线程是不能访问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 }
View Code

 

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 即可。
View Code

 

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     }
View Code

 

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     }
View Code

 

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 View事件机制 21问21答

Android View事件机制 21问21答

Android Framework | 消息机制的冷门知识点,你能答出来几个?

Android Framework | 消息机制的冷门知识点,你能答出来几个?

Android Learning:多线程与异步消息处理机制

android线程消息传递机制——Looper,Handler,Message