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)方法。

相关问题

  1. 一个线程有几个Handler?
    一个线程有任意多个Handler。

  2. 一个线程有几个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中的ThreadLocal是final类型的变量,所以ThreadLocal只存在一个,所以在一个线程中只会存在一个Looper。
      在Looper类中,变量MessageQueue的类型为final,所以当Looper中的MessageQueue一旦初始化,就不能再被修改,所以一个Looper有一个MessageQueue。

  1. 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();    }}
  1. 为什么其他的内部类不存在类似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就一直不能被释放。
      回到正题,为什么其他的内部类没有这个问题呢?
      所有的内部类都会持有外部类对象,但其他内部类在持有外部类对象的时候没有什么耗时的操作,也没有另外一个东西去持有这个内部类。

  1. 为何主线程可以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在没有消息处理时会睡眠。

更多相关文章

  1. android字体闪烁动画(线程)
  2. Android消息处理机制4——Looper
  3. 多线程实现更新android进度条。
  4. Android 线程池管理
  5. Android中更新UI的线程:Thread 、Handler、Looper、TimerTask等
  6. 跨平台移动开发 Android使用JPush推送消息
  7. Android中的线程
  8. 浅谈Android中的线程的通信及Handle机制

随机推荐

  1. 用.Net打造一个移动客户端(Android/IOS)的
  2. Android四大组件之BroadCast
  3. Android Activity之间跳转出现短暂黑屏的
  4. No usable Android build tools found. H
  5. Android Studio自带的sdk manager打不开
  6. 简单android音乐播放器中 android学习(四)
  7. Android SystemUI引用RenderScript库
  8. Android初学者
  9. Android(安卓)Service之串行化Service:Int
  10. appium for window 环境搭建