handler学习思路
- handler是是什么,做什么用,相关知识了解?
- handler主线程代码示例
- handler子线程代码示例
- handler,MessageQueue,Looper,ThreadLocal,的关系及源码解析
一、Handler是啥?用来做什么的? 为什么要这么用?
1.在了解handler是啥之前,先通俗的讲一下android通信的一些题外内容。
进程: 是系统进行资源分配和调度的最小单位,是一个具有一定功能的程序的对数据进行操作的活动比如一个程序(APP)是至少在一个进程运行的,也可以通过给相关组件设置Process参数来设置在其其他进程运行。
线程: 是CPU调度和分派的基本单位,通俗的讲就是电脑手机可以独立运行的最小单位,线程没有自己独立的内存,而是在进程里面的,同一进程里面的线程共享自己所属进程的所有资源。
系统通信: 通俗的讲就是数据或者信号在系统内部或者系统间传递的过程,又因为系统里面有进程和程的单元,所以就有了跨进程通信 IPC机制(Socket、AIDL、Message、Binder、ContentProvider、以及IO文件系统)和跨线程通信 Handler机制(Handler)。
线程同步: 只一个对象在同一时间只能被操作处理一次,而不允许被并发操作,典型的有两类方式。
一、同步锁机制(synchronized):及上过锁的对象会被处理的时候阻塞在消息队列中等待前面一个对象执行完毕再跟着执行(以时间的方式换取同步操作)。
二、Handler 来处理UI线程的操作: 通过子线程操作数据,然后mUIHandler将消息发送到主线程的MessageQueue中,等待looper分发给对应的mUIHandler来处理消息,(用空间方式换取同步操作,ps:因为耗时操作被并发执行,而UI更新的时间很小)。
2.Handler通常干嘛用的? 为嘛要用Handler?
Handler是用来做跨线程通信只用,通常是用来解决子线程数据处理,UI线程更新之用
原因之一: UI更新限制:android默认更新UI操作的时候有一个在ViewRootImpl中的方法checkThread来验证是否是UI线程;
// 验证是否在UI线程执行UI操作 void checkThread(){ if (mThread != Thread.CurrentThread()) { throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views"); } }
但是又因为android规定:UI线程如果5秒无法响应屏幕点击事件或者广播接收事件,则会报系统未响应(ANR),故:耗时的数据操作就在子线程进行,再通过Handler回传给UI线程进行UI更新操作,所以Handler就通常用来做UI更新操作,(ps:系统广播是10未响应也会出现ANR)
- 原因之二: UI更新同步问题: android的控件如果在多线程并发中执行操作,会导致控件出现不可控制的状态,所以需要控件同步操作;同步分两种,时间操控的同步锁,控件操控的多线程并发Handler操作;之所以不用同步锁,1.同步锁会大大增加UI控件的逻辑;2.其次同步锁会阻碍其他线程对这个控件的操作,影响UI执行效率,导致其他线程阻塞;所以Handler就被用来操作单线程的UI更新操作;
二、Handler在UI线程中代码示例
- 示例一:(new handler的衍生类来实现handMessage(Message msg))
Handler mHander = new Handler(){
@Override
public void handMessage(Message msg){
// 执行消息处理
switch(msg.what){
case 0:
log.d(“处理消息1的消息”);
break;
}
}
}
new Thread("Thread 2") {
@Override
public void run() {
super.run();
Log.d(TAG, "run: [Thread 2 的uid 为] : " + currentThread().getId() + " 值为 " +mThreadLocal.get());
mHandler.sendEmptyMessage(0);
Log.d(TAG, "run: 当前时间 " + System.currentTimeMillis());
}
}.start();
- 示例二:(new Handler.CallBack() 不使用Handler的衍生来生成新的Handler)
// 创建Handler的回调接口
private Handler.Callback mHandlerCallBack = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "handleMessage: 接收到的消息内容为: " + msg.obj.toString());
return true;
}
return false;
}
};
private Handler mCallBackHandler = new Handler(mHandlerCallBack);
new Thread("Thread 1") {
@Override
public void run() {
super.run()
String msgs = "[Thread 1 线程uid ] : " + currentThread().getId();
Message message = new Message();
message.what = 1;
message.obj = msgs;
mCallBackHandler.sendMessage(message);
}
}.start();
三、Handler在非UI线程的实现
private Handler mThreadHadnler;
new Thread("handlerThread") {
@Override
public void run() {
Looper.prepare();
mThreadHadnler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 3:
Log.d(TAG, "handleMessage: 获取到的消息内容为:" + msg.obj.toString());
break;
}
}
};
Looper.loop();
}
}.start();
new Thread("sonThread"){
@Override
public void run() {
String msgs = "[sonThread 线程uid ] : " + currentThread().getId() ;
Message message = new Message();
message.what = 3;
message.obj = msgs;
mThreadHadnler.sendMessage(message);
}
}.start();
四、Handler的源码分析
-
1.Handler工作流程图
handler工作流程图.png - 2.ThreadLocal,Message, MessageQueue,Hadnler解释
1.ThreadLocal解析 : 是一个可以用来存储线程独有的数据的类, 作用是获取线程loop取消息(参见方法四和五);
ThradLocal的原理.png
/** 方法一 * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 拿到线程的ThreadLocalMap对象进行存储 if (map != null) map.set(this, value); else createMap(t, value); } /** 方法二 * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 拿到线程的ThreadLocalMap对象进行存储 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } /** 方法三 * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** 方法四 * 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(); } /** 方法五 * 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(); }
2.Message的解析: 跨线程通信的信息的载体, 底层运用了IPC跨进程通信的知识(目前没有解析>native层源码,故如有问题欢迎指正,待完善native的源码再来完善),有两种创建方式:1.new 对象; 2.通过消息池,及obtain()系列
3.MessageQueue的解析: 跨线程通信的消息列表的通道,底层运用了管道的内容(目前没有解析>native层源码,故如有问题欢迎指正,待完善native的源码再来完善)
boolean enqueueMessage(Message msg, long when) { synchronized (this) { 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 { 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); } }
4.Looper的解析: Looper及消息通道的消息分配人员,通过死循环来实现读取MessageQueuenext出来的消息,如果消息为null,及跳出了looper的循环,所以有两种方法跳出looper,quit()方法一和quitSafely()方法二跳出之后后续的Message无法交给Handler处理;loop()方法三通过MessageQueue获取Message,然后传递给Message.disPatchMessage(msg)处理;
/** 方法一 * Quits the looper. * <p> * Causes the {@link #loop} method to terminate without processing any * more messages in the message queue. * </p><p> * Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. * </p><p class="note"> * Using this method may be unsafe because some messages may not be delivered * before the looper terminates. Consider using {@link #quitSafely} instead to ensure * that all pending work is completed in an orderly manner. * </p> * * @see #quitSafely */ public void quit() { mQueue.quit(false); } /** 方法二 * Quits the looper safely. * <p> * Causes the {@link #loop} method to terminate as soon as all remaining messages * in the message queue that are already due to be delivered have been handled. * However pending delayed messages with due times in the future will not be * delivered before the loop terminates. * </p><p> * Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. * </p> */ public void quitSafely() { mQueue.quit(true); } /** 方法三 * 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; for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } try { msg.target.dispatchMessage(msg); // 此处的Handler及发送Message的Handler end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } } }
5.Handler的解析: 发送消息到队列以及处理Looper分配的消息。发送消息两种: >sendMessage(Message msg)系列 方法一 和post(Runnable r)系列 方法二;自身两种处理消息方式:>handleMessage(msg)方法三 以及CallBack接口中的handleMessage(msg)系列 方法四;
/** 方法一 * 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); } /** 方法二 * Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached. * * @param r The Runnable that will be executed. * * @return Returns true if the Runnable 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 post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } /** 方法三 * 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); } } /** 方法四 * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. * * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ public interface Callback { public boolean handleMessage(Message msg); }
五、Handler机制的注意以及疑问和后续的学习内容
handler机制中的关系
- 一个APP至少一个进程,
- 一个进程至少一个线程,
- 一个线程仅有一个looper(初始线程没有looper,需要自己prepare()),一个MessageQueue,可以有多个handler;
- 一个线程如果需要通过Handler发送一个消息,必须初始化一个looper
- 主线程main方法已经自动帮我们初始化了looper 可以通过getMainLooper()获取,并且主线程的Looper不可以被手动quit()掉;
- 消息是被handler发送到所在线程的MessageQueue中;
hander机制的疑问
- Message为什么可以从子线程被Handler发送到主线程?
- MessageQueue是如何实现消息的存储以及分发的?
- MessageQueue会不会被放满?
- MessageQueue既然一有消息就可以分配,那没消息它在干嘛?如果是停着啥也不干那又是如何做到阻塞的?为什么阻塞不会造成ANR呢?
- 线程又是如何共享进程中所有资源的?
- 线程可以被开启多少个?频繁开启消耗资源,那有没有什么好的方法可以不那么消耗资源的使用线程?
- 线程,进程,和堆,栈有什么关系么?
- 跨进程通信又是啥?
学习是一个寻寻渐进的过程,我们既要学会观察现象,了解现象,也需要透过现象看本质。最后,祝愿自己及广大开发猿能够在技术的道路上越走越远。
网友评论