Android(安卓)Handler解析和相关问题
Handler解析
Handler工作流程
(1)通过sendMessage将Message存入MessageQueue:
Handler.sendMessage(Message msg)
->Handler.sendMessageDelayed(Message msg,long delayMillis)
->Handler.sendMessageAtTime(MEssage msg,long uptimeMillis)
->Handler.enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
->MessageQueue.enqueueMessage(Message msg, long when)
流程中的MessageQueue.enqueueMessage的作用是存储消息,源码如下:
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } 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; }
从源码中可以看出, MessageQueue.enqueueMessage是根据when的值将Message插入消息链表,when的值越小插入的位置越靠前,如果when是0,则插入链表表头。调用sendMessage时when的值为SystemClock.uptimeMillis(),调用sendMessageDelayed(Message msg, long delayMillis)时when的值为SystemClock.uptimeMillis() + delayMillis。可以看出when的值实际上就是当前时间+指定延迟毫秒数,MessageQueue中消息是按照时间顺序排序的。
(2)Looper.loop()从MessageQueue中取出消息,交给Handler处理。
public static void loop() { ... ... 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); ... } } ... }
由源码可以看出,loop()通过MessageQueue的next()方法从消息队列中取出消息。然后调用Handler.dispatchMessage(Message msg) -> Handler.handleMessage(Message msg)方法让Handler处理消息。如果消息队列没有消息可以取出了,MessageQueue的next()方法会被阻塞,所以loop()方法也会阻塞,直到消息队列中又存在消息了,next()不再阻塞。如果一直没有新消息处理,loop()会一直阻塞,如果想退出loop()循环,可以调用MessageQueue的quit(boolean safe)方法。
相关问题
-
一个线程有几个Handler?
一个线程有任意多个Handler。 -
一个线程有几个Looper?如何保证?1个Looper有几个MessageQueue?
一个线程有1个Looper。1个Looper有一个MessageQueue。
Looper的构造方法是私有的,Looper实例由Looper.prepare()来构造,prepare()代码如下:
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)); }
由代码可见,先判断ThreadLocal的get()方法判断ThreadLocalMap里面有没有Looper,如果已经有Looper了,则抛出异常,如果没有,则new一个Looper并调用ThreadLocal的set()方法将Looper保存进ThreadLocalMap中。
每一个线程Thread都会有一个变量ThreadLocal.ThreadLocalMap,这个Map是用来保存线程上下文的。(一个线程对应一个Map)prepare代码段中调用了set()方法,set()方法代码如下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value);//this就是ThreadLocal,value在这里对应Looper else createMap(t, value); }
由代码中注释可以看出,ThreadLocal和Looper分别作为键和值被存入了Map中。 所以:一个Thread对应一个ThreadLocalMap,ThreadLocalMap中存在着一个键值对
在Looper类中,变量MessageQueue的类型为final,所以当Looper中的MessageQueue一旦初始化,就不能再被修改,所以一个Looper有一个MessageQueue。
- Handler内存泄露原因?怎么解决这个问题?
在如下代码中会发生内存泄露:
public class MainActivity extends AppCompatActivity { TextView textView; static int count = 0; Handler mHandler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { if(msg.what == 1){ super.handleMessage(msg); textView.setText(String.valueOf(msg.arg1)); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.text1); new Thread() { @Override public void run() { super.run(); for (int i = 0; i < 100; i++) { Message msg = new Message(); msg.what = 1; msg.arg1 = count ++; mHandler.sendMessage(msg); try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); }}
Handler是一个匿名内部类,它持有外部类Activity的实例引用,当GC垃圾回收机制进行回收时发现这个Activity居然还有其它引用存在,因而就不会去回收这个Activity,进而导致Activity泄露。
解决内存泄露的方法:使用static和WeakReference。因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露。同时,我们需要使用外部类的成员,可以通过使用“activity.”类获取外部变量,如果直接使用强引用,显然会导致Activity泄露,所以使用弱引用WeakReference。解决问题后的代码如下:
public class MainActivity extends AppCompatActivity { TextView textView; static int count = 0; MyHandler myHandler; public static class MyHandler extends Handler{ private WeakReference<MainActivity> mWeakReference; public MyHandler(MainActivity activity){ mWeakReference = new WeakReference<>(activity); } @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); MainActivity mainActivity = mWeakReference.get(); if(msg.what == 1){ super.handleMessage(msg); mainActivity.textView.setText(String.valueOf(msg.arg1)); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.text1); myHandler = new MyHandler(MainActivity.this); new Thread() { @Override public void run() { super.run(); for (int i = 0; i < 100; i++) { Message msg = new Message(); msg.what = 1; msg.arg1 = count ++; myHandler.sendMessage(msg); try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); }}
- 为什么其他的内部类不存在类似Handler的内存泄露的问题?
回顾Message发送,处理的流程,在发生消息时,有一步调用了Handler.enqueueMessage(),源码如下所示:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this;//this是Handler msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
关注第一行代码:msg.target=this,这句话将Message和Handler绑定到了一起,即Message由同一个Handler发送,由同一个Handler处理。我们又知道,当Handler作为非静态内部类使用时,Handler持有外部Activity。所以造成了以下关系:Message->包含Handler->持有Activity。而Message是放在MessageQueue中等待让Handler处理的,试想一种情况,一个Message发送到MessageQueue后设定其1分钟之后再处理,那么在这一分钟之内,MessageQueue会一直占用一块内存,同时MessageQueue持有这个刚发送过来的Message,Message又持有Activity,所以这个Activity即使调用onDestroy()也不能将Activity的内存进行释放。
总结:MessageQueue->持有Message->包含Handler->持有Activity,只要MessageQueue中有消息未处理,该消息对应的Activity就一直不能被释放。
回到正题,为什么其他的内部类没有这个问题呢?
所有的内部类都会持有外部类对象,但其他内部类在持有外部类对象的时候没有什么耗时的操作,也没有另外一个东西去持有这个内部类。
- 为何主线程可以new Handler?如果想在子线程中new Handler要做些什么准备?
在一个android应用启动时,java层调用的第一个函数是ActivityThread.java的main方法,在该main方法中,已经实现了对Looper的初始化。所以在主线程中,已经存在Looper了,所以可以直接new Handler。
如果想在子线程中new Handler,则需要先准备一个Looper,流程:Looper.prepaer() ; Looper.loop();代码如下所示:
public class Test1 extends AppCompatActivity { Handler threadHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test1); test(); Button button = findViewById(R.id.button2); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Message msg = new Message(); msg.what = 1; threadHandler.sendMessage(msg); } }); } public void test(){ new Thread(new Runnable() { @Override public void run() { if(threadHandler == null){ Looper.prepare(); threadHandler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); if(msg.what == 1) Toast.makeText(getApplicationContext(),"test",Toast.LENGTH_SHORT).show(); } }; Looper.loop(); } } }).start(); }}
可以调用Looper.quit()方法结束Looper。
6. 子线程中维护的Looper,消息队列无消息的时候的处理方式是什么?有什么用?
查看Looper.loop()源代码如下:
public static void loop(){ ... for (;;) { Message msg = queue.next(); // might block ... }...}
可见通过一个无限循环来不断去除Message,当没有Message时,阻塞,下面查看MessageQueue.next()方法:
Message next() { ...for (;;) { ... //nativePollOnce是一个native函数, nextPollTimeoutMillis值为-1则表示无限等待,直到 //有事件发生为止。如果值为0,则无需等待,立即返回。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { ... if (msg != null && msg.target == null) { ... } if (msg != null) { ... } else { // No more messages.当没有消息时 nextPollTimeoutMillis = -1; } }... }...}
可以看出,当MessageQueue中没有消息时,会将nextPollTimeoutMillis设置为-1,等下次循环调用nativePollOnce()时,会使当前线程无限等待,线程被挂起,即MessageQueue.next()函数被阻塞->Looper.loop()函数被阻塞。
可以调用Looper.quit()方法结束Looper,唤醒当前线程。Looper.quit()具体实现下面再讲。
7. 既然可以存在多个Handler往MessageQueue中添加数据,那它内部是如何确保线程安全的?
查看源码,可以看到关于MessageQueue的操作都会加锁:
synchronized (this) { ...}
对this加锁,this即为MessageQueue,保证了同时只有一个Handler访问MessageQueue。
8. 我们使用Message时应该如何创建它?
我们应该使用Handler.obtainMessage()方法获取Message,而不是使用new Message()。
首先,我们看一下Looper.quit(),它的作用是销毁一个Looper。Looper.quit()->调用MessageQueue.quit()->调用MessageQueue.removeAllMessagesLocked()->调用Message.recycleUnchecked()。Message.recycleUnchecked()源码如下:
...private static Message sPool;//空消息队列...@UnsupportedAppUsage 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 = UID_NONE; workSourceUid = UID_NONE; when = 0; target = null; callback = null; data = null;//将这个消息插入空消息链表头部 synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool;//把这个Message的next指向sPool sPool = this;//sPool表头变为this刚销毁的Message sPoolSize++;//记录空消息链表中空Message的数目 } } }
由以上源码可以看出,销毁一个Message时并没有把这个Message销毁掉,而是把这个Message的所有属性都重置为空,然后将这个Message的next属性指向sPool,sPool是空消息链表。此时刚刚被销毁的Message变成了空消息链表的表头。从内存上看,这个Message并没有被销毁,而是清除数据后存入了另一个消息列表中。
Handler.obtainMessage()会调用Message.obtain()来获取Message,以下是Message.obtain()的源码:
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
很显然的可以看出,Message.obtain()是在从sPoolSync空消息链表中取Message的,如果sPoolSync为空,才会new Message()。这样做就减少了不断的去new Message()的过程。试想如果我们每次创建Message时都用new Message()的方法,不仅浪费了时间,而且sPoolSync会变的越来越大,也浪费了内存。
这种模式称为享元设计模式,源码中使用该模式的好处在于,节省了new Message()的时间,而且不必在不用某个消息时去回收这个消息。如果不使用这种模式,需要Message时就new Message(),不使用时就销毁Message,则可能出现内存抖动(OOM)的问题。
9. Looper死循环为什么不会导致应用卡死?
首先了解一下什么是应用卡死(ANR):5秒钟之内没有响应输入的事件,比如按键、屏幕触摸等;广播接收器在10秒内没有执行完毕。
AMS管理机制:每一个应用都运行于自己的虚拟机中,也就是每一个应用都有自己的一个main函数。 应用启动流程:launcher->zygote->art application->activityThread->main()。应用中所有生命周期的函数(包括Activity、Service所有生命周期)都运行在这个Looper里面,而且,它们都是以消息的方式存在的。
看完上面两个概念不禁会想,不是说5秒钟不响应就会出现ANR吗?为什么在没有消息的时候Looper.loop()休眠好长时间都不会出现ANR呢?
分析:Looper.loop()休眠,即主线程休眠了,这时CPU时间片交给了应用中其它线程来使用,应用的主线程仍处于等待状态。仔细阅读ANR的概念会发现,ANR指的是消息(按键、屏幕触摸、广播等都是由Message形式传递的)没有被及时处理,产生ANR的根本原因不是由于线程在睡眠,而是由于没有及时处理消息。所以当MessageQueue中没有Message时主线程阻塞,此时没有消息可以处理,也就自然不会出现ANR。
比如,下面这个事件才会真正导致ANR:比如线程当前正在处理消息1,这时发生了一个点击事件,这个点击事件被排在了消息1之后处理,而消息1在点击事件之后5秒钟之内还没有被处理完,所以就造成了点击事件在5秒钟之内没有响应,所以会导致ANR。
结论:应用卡死ANR与Looper没有任何关系,Application在没有消息需要处理时,主线程睡眠;ANR导致应用卡死,而Looper在没有消息处理时会睡眠。
更多相关文章
- Android(安卓)实现图片保存到本地并调用本地地址显示图片
- Unity调用安卓
- Android获取activity的所有子view(使用到递归)
- android 中 浏览器调用本地app应用
- Android(安卓)APK安装过程及原理详解
- Android之SurfaceView学习
- Android(安卓)Service
- 多线程实现更新android进度条。
- Android应用程序调用系统解锁页面