面试准备之最详细的Handler的使用、源码分析
来源:互联网 发布:数据有效性wps2016 编辑:程序博客网 时间:2024/06/08 14:37
最近准备离职,所以特意准备了一下,很多次面试都会问我们“”Handler“”相关知识,作为一名Android开发者,Handler更是我们必然会接触的东西,还记得当时接触只是知道怎么用,但是为什么可以这样用就不知道了,我觉得做技术要做到:“知其所以然”,所以此篇文章对这几天看handler的一个总结。
首先我们思考以下几个问题:
1、为什么会有Handler?Handler是干什么的?
2、handler是如何使用的?
3、handler的工作原理是怎样的?源码是如何实现的?
4、handler使用中会遇到常见的问题有哪些?譬如发生内存泄露又是如何解决的?
5、面试时遇到:说下Handler机制, 这种问题如何解答的?
接下来我们待着自己的问题一个一个解答:首先看图,有大致的思路,看完整篇文章可以回来验证一下:
一、为什么会有Handler?Handler是干什么的
在正常开发中我们知道一个Android应用程序运行时会创建一个主线程就是我们常说的UI线程,但是我们再实际中可能会需要进行网络请求或者耗时操作这写都是在子线程中执行,这个时候我们想去改变界面是不可以的,因为子线程不允许直接更新视图(因为在ViewRootImpl的setview中会有requestLayout();在其内部调用checkthread(),当检测不是主线程时会抛出异常),所以Android通过Handler消息机制来实现线程之间的通讯。
二、handler是如何使用的
2、1 handler.sendMessage : 用于在不同的线程间发送消息
sendEmptyMessage(int);//发送一个空的消息sendMessage(Message);//发送消息,消息中可以携带参数sendMessageAtTime(Message, long);//未来某一时间点发送消息sendMessageDelayed(Message, long);//延时Nms发送消息
使用举例
//在主线程Activity中定义Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what==1){ tv_1.setText(msg.obj.toString()); } }
tv_1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //开启子线程 new Thread(new Runnable() { @Override public void run() { //创建Message Message message =new Message(); message.obj="子线程Handler消息"; message.what=1; //发送消息 handler.sendMessage(message); } }).start(); } });;
最终我们会实现点击 tv_1后tv_1显示“子线程Handler消息”这是我们简单的一个使用。
2、2 handler.post : 用于在将来执行某任务
post(Runnable);//提交计划任务马上执行postAtTime(Runnable, long);//提交计划任务在将来指定的时间点执行postDelayed(Runnable, long);//提交计划任务延时long(s)后执行
使用举例:
//在主线程Activity中定义private Handler mHandler=new Handler();
tv_1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //开启子线程 new Thread(new Runnable() { @Override public void run() { //耗时操作,完成之后提交任务更新UI final String data = "子线程Handler消息"; mHandler.post(new Runnable() { @Override public void run() { tv_1.setText(data); } }); } }).start(); } });;
运行后会有和上面同样的效果,以上就是我们在正常开发中的使用。
三、handler的工作原理是怎样的?源码是如何实现的?
首先要明白,要分析这种源码,我们不可能以创造者的身份去看,只能反推理,就是通过他暴露给我们的方法去看内部的调用及实现,我们使用的就是handler.sendMessage和handler.post,所以接下来以这两个为入口,看看里面都干啥了,怎么调用的。
3、1 handler.sendMessage(入口一)
调用链路比较长,这里直接看(先看代码再看解释),建议大家跟着源码看一遍:
1、在Handler中如何调用的?
//1、入口handler.sendMessage//2、在Handler里调用的方法public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); //继续往下跟踪 }//3、到这里,发现当你不设置时间时参数delayMillis设置为0,public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);//继续走 } //4、到这里 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { //message的queue 设置为mQueue下面分析 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);//继续往下走 }//5、到这里,我们可以看到最终是MessageQueue.enqueueMessage吧msg处理了。所以去MessageQueue往下看private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; //当前方法执行在Handler,所以msg.target =handler if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }//注意:以上所有方法都在Handler中执行
2、在上面我们一直传递的是message,我们来看看message到底是什么?
public final class Message implements Parcelable { .... //很多代码就不粘贴了 public int what; public int arg1; public int arg2; Handler target; long when; public Object obj; Runnable callback; ....
可以看出来Message 就是一个序列话的bean,存储着各种的相关的数据如what,obj等等
3、接上面 “1” 分析的继续到MessageQueue,看其enqueueMessage做什么了?
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { //检查Handler是否为null throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) {//检查这个message是否再使用 throw new IllegalStateException(msg + " This message is already in use."); } //最重要的在这里,简单说下这里使用单链表来存储message,跟据发送的时间来进行关联。 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 { // 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; }
解释:其实上面这个enqueueMessage方法就干了一件事就是将message以单链表的形式存储,通过prev和msg.next等(可以自己发送多个消息去走for循环走一遍,你会发现当前的message.next会指向下个message),为什么要用链表呢?我也不知道……,但是链表和数组比插入和删除快,而数组查询快(因为有下标),如果想了解链表等知识的同学自己去看下,
可能到这里会有基础不强的小伙伴蒙了,不要担心,可能它以链表存储我们不是能理解,但是我们知道他走到这里是吧msg处理完了存储起来了就好,不要太追求代码细节从而遇到“只见树木,不见森林”的状态
4、现在已经把消息存起来了,那如何取出,又发送到主线程中的呢?接下来分析,还是通过“反推理”吧。
我们都知道在Handler中还有个角色Loop,他正常使用时是这样的
Looper.prepare();Looper.loop();
为什么要写这两个,这两行代码干啥的呢??
这里就不得不说Handler了,我们看Handler的构造方法:
//1、看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()); } } //1、先看这里,再构建Hnadler时会拿到一个Looper,看2 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //2、稍后看这里,获取存储message的MessageQueue mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }//2、从Handler构造器中看实例化了一个mLooper ,继续往下看3 public static @Nullable Looper myLooper() { return sThreadLocal.get(); }//3、看sThreadLocal.get() public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
在这里看sThreadLocal为何物?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
它就是用来存储Looper的。从上面看到Handler初始化时需要的Looper 是sThreadLocal.get来获取到的,此时考虑,那如果不事先把looper给set进去,那么他返回的就是空,所以我们在哪里set的呢?就是上面所说的Looper.prepar(),接下来我们看Looper.prepar():
public static void prepare() { prepare(true); }private static void prepare(boolean quitAllowed) { //由上面知道sThreadLocal.get()返回的是looper,这里判断当loope存在时抛出异常,意味着一个线程只能有一个Looper,从而保证了线程安全 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); //可以看到是在这里新创建了一个Looper然后set进去的 }
5、接下来看上一行的new Looper都干啥了;
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }//通过上面知道一个线程只能有一个Looper,而在创建Looper时创建了mQueue ,所以mQueue 也只有一个,mThread就是当前线程
6、接下来就是说怎么把消息取出的呢,还记得上面我们写的Looper.loop()方法
public static void loop() { final Looper me = myLooper(); if (me == null) {//首先判断Looper 是否为null throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue;//取出创建Loope时创建的MessageQueue // 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(); //对以单链表的存储的MessageQueue取出消息 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)); } //看这里最重要,最终把 消息取出来之后交由msg.target处理,再上面1、Handler中我们分析过 //msg.target=this=Handler,表示最终消息是通过Hnadler处理了。 try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } ... } //对消息进行回收 msg.recycleUnchecked(); } }
7、接下来看msg.target.dispatchMessage(msg)方法:也就是Handler里的dispatchMessage方法:
/*** Handle system messages here.*/public void dispatchMessage(Message msg) { if (msg.callback != null) { //这里是用 post时调用的等下说 handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //用sendMessage()时调用,把消息传到handleMessage处理,handleMessage方法 handleMessage(msg); }}/*** Subclasses must implement this to receive messages.* 最终会走这个方法 handleMessage*/public void handleMessage(Message msg) {}//handlerMessage()方法是个空方法,大家也就明白为什么要在Handler里重写这个handleMessage
到此我们就明白了整个handler.sendMessage()消息发送、及分发的流程,
3、2 handler.post(入口二)
同理我们一样跟踪post方法:
//1、入口handler.post(new Runnable() { @Override public void run() { }});//2、public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }//3、看上面2里getPostMessage(r)做什么操作了;private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } //就是把Message的callback赋值为传入的Runnable ,也就是message.callback执行就是Runnable执行了。 //4、继续2处往下走看sendMessageDelayed方法 public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } //最终发现调用的sendMessageAtTime()是不是很熟悉呢,没错就是上面我们handler.sendMessage里有的,就不往下分析了
由于调用流程都是一样的,所以最终也会调用handler里的dispatchMessage,然后我们看:
public void dispatchMessage(Message msg) { if (msg.callback != null) { //这里就是用 post时调用的、不为null会执行下面的方法 handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //看这里,把消息传到handleMessage处理 handleMessage(msg); } }//看handleCallback方法private static void handleCallback(Message message) { message.callback.run(); }//可以看到message.callback执行,就是之前传入的Runnable执行了。
到此整个Handler分析就完成了,看到这里你就要明白几个概念了,要是还不明白你会不好意思的:
1、Message(消息)
定义:Handler接收和处理的消息对象(Bean对象)
作用:通信时相关信息的存放和传递
2、Handler(处理者)
定义:Message的主要处理者
作用:负责发送Message到消息队列&处理Looper分派过来的Message
3、Message Queue(消息队列)
定义:采用单链表的数据结构来存储消息列表
作用:用来存放通过Handler发过来的Message,按照先进先出执行
4、Looper(循环器)
定义:扮演Message Queue和Handler之间桥梁的角色
作用:
消息循环:循环取出Message Queue的Message
消息派发:将取出的Message交付给相应的Handler
5、ThreadLocal
定义:线程内部的数据存储类
作用:负责存储和获取本线程的Looper
疑问:可能会有人会有疑问,为什么咱们定义Handler的时候不用写Loopre.prepar呢,
解答:那是因为启动模式里的ActivityThread里的程序入口Main方法里定义了,如下:
//1、public static void main(String[] args) { ... Process.setArgV0("<pre-initialized>"); //看这个方法 Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); ...//2、 public static void prepareMainLooper() { prepare(false);//看这个方法 synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}//3、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 } //如果有不相信的可以在子线程中不写Looper.prepar,直接创建Handler,看看结果如何(会抛出异常)
四、handler使用的内存泄露及解决办法
原因:我们知道创建Handler的时候是在Activity里创建的,当在同一个线程下的handler共享一个looper对象,消息中保留了对handler的引用,由于Java在生成内部类的时候,原本没有构造器的内部类会被生成一个带外部类参数的构造器,这个内部类就会持有外部类的隐式引用。Handler其实隐式的持有了Activity的引用,只要有消息在队列中,那么handler便无法被回收,如果handler不是static那么使用Handler的Service和Activity就也无法被回收。这就可能导致内存泄露。当然这通常不会发生,除非你发送了一个延时很长的消息,当前的Activity还已经关闭了,就会造成内存泄露。
解决思路:既然知道原因了那就兵来将挡、水来土挡啦,如下:
1、在java里,非静态内部类和匿名类都会潜在的引用它们所属的外部类。但是,静态内部类却不会。因此将handler改成静态的就可以有效的避免对Activity的隐式引用,从而避免内存泄漏。
2、如果想要在handler内部去调用所在的外部类Activity,那么可以在handler内部使用弱引用的方式指向所在Activity,这样同样不会导致内存泄漏。
3、解决方法
private static class MyHandler extends Handler{ //持有弱引用MainActivity,GC回收时会被回收掉. private final WeakReference<MainActivity> mActivty; public MyHandler(MainActivity activity){ mActivty =new WeakReference<MainActivity>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity=mActivty.get(); super.handleMessage(msg); if(activity!=null){//防止Activity已经关闭了还去操作 //执行业务逻辑 tv1.setText("厉害了!"); } } }
五、常问的面试题分析
1、说下Handler机制
大致就是通过Handler将子线程携带信息的message发送到唯一的Messagequeue中,其采用先进先出的方式来管理message,然后通过Looper个循环器将其创建时关联的Messagequeue从中循环取出Message,然后再交由Handler处理,从而实现子线程与主线程的通信(我说的比较简单,相信你完整看完整篇之后会有自己更多的理解)
2、为什么就能再主线程中更新UI了?
我们知道handler就是把所有的message发送到唯一的MessageQueue中,最终还是会用handler处理,因为handler在主线程中,所以再这里更新主线程没有问题。
3、为什么在Activity里创建Handler不用写Looper.prepar()?
因为再启动模式里的ActivityThread的程序入口Main方法里已经调用了Looper.prepareMainLooper();来进行Looper的初始化。
4、如何保证线程安全的
因为在Looper.prepar时会判断“”sThreadLocal.get()“”得到的Looper是否存在,如果存在则抛出异常,当不存在时会创建Looper,通过ThreadLocal.set将Looper存储起来,这样就保证了只有一个Looper了,从而保证了线程安全。
总结:整个篇幅可能有点长,但是写的够详细,希望对大家有帮助,如果真想弄懂Handler还是建议大家看着源码一起往下看,一遍看不懂两到三遍指定懂了。篇幅中可能会有部分观点受个人眼界限制,欢迎指教改正,qq:2145228494,
感谢生活、感谢科技、感谢分享!
- 面试准备之最详细的Handler的使用、源码分析
- 面试准备之okhttp的使用及源码分析(一)
- Android面试之Handler的详细讲解和使用
- Handler的源码分析
- Handler的源码分析
- handler的源码分析
- 最简洁的Handler、Looper、Message源码级原理分析
- 强大的Handler详细分析
- handler机制的源码分析
- 最详细的U-BOOT源码分析及移植
- 最详细的U-BOOT源码分析及移植
- 最详细的U-BOOT源码分析及移植
- 最详细的U-BOOT源码分析及移植
- 最详细的U-BOOT源码分析及移植
- 最详细的U-BOOT源码分析及移植
- Android进阶之路 - Handler的详细使用(一)
- Tomcat源码分析之ClassLoader部分的设计详细分析
- Tomcat源码分析之ClassLoader部分的设计详细分析
- 【Codeforces418D】Big Problems for Organizers
- LeetCode 68 Text Justification
- 获取电脑日期时间代码段
- Windows 无法启动 VMware Authorization Service 服务
- HDU
- 面试准备之最详细的Handler的使用、源码分析
- JavaScript 原型
- javascript正则表达式
- Unity shader学习之屏幕后期处理效果之Bloom效果
- ELK技术实战-安装Elk 5.x平台
- 设置启动进程
- 开启User流程
- linux命令
- 变量与字符串