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. Unity调用安卓
  3. Android获取activity的所有子view(使用到递归)
  4. android 中 浏览器调用本地app应用
  5. Android(安卓)APK安装过程及原理详解
  6. Android之SurfaceView学习
  7. Android(安卓)Service
  8. 多线程实现更新android进度条。
  9. Android应用程序调用系统解锁页面

随机推荐

  1. PHP 括号配对判断及修正
  2. 一个好用的PHP验证码类
  3. 用于上传多个文件的PHP代码
  4. 使用AJAX值的Fileupload不会以其他字段的
  5. 在PHP中从JSON获取数据
  6. 强制动态加载的类来扩展或实现接口
  7. PHP加密解密明文相同每次密文不一样非对
  8. 仅获取单元素php数组中的值的最佳方法。
  9. 将主DB复制到不同的从站
  10. 在CodeIgniter中包含视图的最佳方法。