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

相关问题

  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在没有消息处理时会睡眠。

你可能感兴趣的:(Android,android)