破茧(二)Handler机制

经过上篇文章我发现,自己写出一篇技术文章比看书、看视频还要学习的更好、更快、更深刻,毕竟你没有吃透这个技术点,你就无法详细有序的写出这个技术点的每一个环节,所以要再接再厉!

这次重新深入了解一下常用的Handler机制;虽然常用,但也有许多不为人知的细节。

一、前言

本次参考了以下几篇文章,感谢各位作者!!!

你真的懂Handler吗?Handler问答

Handler用法及解析

Android基础夯实--你了解Handler有多少?

二、了解Handler

在Android体系中,UI操作并不是线程安全的,并且这些操作必须在UI线程执行,于是Android封装了一套消息封装、传递、处理机制,这就是Handler。Handler主要用于异步消息处理,涉及到的类有:Looper、MessageQueue、Message。

  • Looper:每一个线程只有一个Looper,每个线程在初始化Looper之后,Looper会维护好该线程的消息队列MessageQueue,用来存放Handler发送的Message;其中Loopler.looper方法是一个死循环,不断地从MessageQueue取消息,如果有消息就发给Handler处理消息,没有消息就阻塞;
    Looper的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。

  • MessageQueue:这是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue;其他非主线程,不会自动创建Looper;列队中的消息是根据消息的时间来排序的。

  • Message:消息对象,存放在MessageQueue中,一个MessageQueu可以包括多个Message;当我们需要发送一个Message时,不建议直接new Message(),Message内部保存了一个缓存的消息池,用Message.obtain()的方式来从缓存池中获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。

    image.png

三、Handler发送消息的方法

    //发送消息
    new Handler().sendMessage(Message msg);
    //延时发送消息(比如填1000表示延迟1秒)
    new Handler().sendMessageDelayed(Message msg, long delayMillis);
    //延时发送消息(第二个参数为:相对系统开机时间的绝对时间,填SystemClock.uptimeMillis()+1000表示延迟一秒)
    new Handler().sendMessageAtTime(Message msg, uptimeMillis);

    //发送带标记的空消息(内部创建了message,并设置msg.what);
    new Handler().sendEmptyMessage(int what);
    //延时发送带标记的延时空消息
    new Handler().sendEmptyMessageDelayed(int what, long delayMillis);
    new Handler().sendEmptyMessageAtTime(int what, long uptimeMillis);

    //发送消息
    new Handler().post(Runnable r);
    //发送延时消息
    new Handler().postDelayed(Runnable r, long delayMillis);
    new Handler().postAtTime(Runnable r, long uptimeMillis);
  • 问题来了sendMessage和post有什么区别?
    通过查看源码能够发现,post的方法内也是通过sendMessage来实现消息传递的,它是发送了一个没有标记的Message,简化了消息发送、处理的过程;当我们需要通过标识来执行多个不同的动作的时候,使用sendMessage,当我们只用执行一个动作的时候使用post更加方便快捷。

四、如何在子线程中使用Handler

Handler的使用离不开Looper,在Main线程中会自动生成Looper,而子线程则需要自己新建Looper,并跟Handler绑定,才能正常使用Handler;

  • 通过先调用 Looper.prepare() 在当前线程初始化一个 Looper
//初始化一个Looper
Looper.prepare();
Handler handler = new Handler();
// Handler调用完成后需要进行这一步
Looper.loop();
  • 也可以通过HandlerThread在子线程中使用Handler,HandlerThread自动帮我们创建了Looper,通过getLooper()可以获得Looper
      HandlerThread downloadBThread = new HandlerThread("downloadBThread");
        downloadBThread.start();
        Handler downloadBHandler = new Handler(downloadBThread.getLooper());

        // 通过postDelayed模拟耗时操作
        downloadBHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), "下载B完成", Toast.LENGTH_SHORT).show();
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        tv_B.setText("B任务已经下载完成");
                    }
                });
            }
        }, 1000 * 7);

五、Handler的泄露事件

先来看看下面的代码

public class MainActivity extends AppCompatActivity {
 
    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
                ......
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //activity被执行时,被延迟的这个消息存于主线程消息队列中1分钟,
        //此消息包含handler引用,而handler由匿名内部类创建,持有activity引用,
        //activity便不能正常销毁,从而泄露
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                ......
            }
        }, 1000 * 60);
    }
}
  1. 外部类Activity中定义了一个非静态内部类Handler,非静态内部类默认持有对外部类的引用。如果外部Activity突然关闭了,但是MessageQueue中的消息还没处理完,那么Handler就会一直持有对外部Activty的引用,垃圾回收器无法回收Activity,从而导致内存泄漏。
  2. 在postDelayed中,我们在参数中传入一个非静态内部类Runnable,这同样会造成内存泄漏,假如此时关闭了Activity,那么垃圾回收器在接下来的1000000ms内都无法回收Activity,造成内存泄漏。

解决方案

  1. 将非静态内部类Handler和Runnable转为静态内部类,因为非静态内部类(匿名内部类)都会默认持有对外部类的强引用。
  2. 改成静态内部类后,对外部类的引用设为弱引用,因为在垃圾回收时,会自动将弱引用的对象回收。

避免内存泄漏的例子:

public class MainActivity extends AppCompatActivity {
 
    //创建静态内部类
    private static class MyHandler extends Handler{
        //持有弱引用MainActivity,GC回收时会被回收掉.
        private final WeakReference mAct;
        public MyHandler(MainActivity mainActivity){
            mAct =new WeakReference(mainActivity);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity mainAct=mAct.get();
            super.handleMessage(msg);
            if(mainAct!=null){
                //执行业务逻辑
            }
        }
    }
    private static final Runnable myRunnable = new Runnable() {
        @Override
        public void run() {
            //执行我们的业务逻辑
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyHandler myHandler=new MyHandler(this);
        //延迟5分钟后发送
        myHandler.postDelayed(myRunnable, 1000 * 60 * 5);
    }
}

六、Handler其他可能发生的错误

  • 错误1:如果Activity被关闭,但是handler刚好还在处理消息,需要用的资源已被释放,会出现空指针异常;需要在ondestory中去remove掉我们要处理的事件。
    //避免内存泄露的方法:
    //移除标记的消息
        new Handler().removeMessages(int what);
    //移除回调的消息
        new Handler().removeCallbacks(Runnable runnable);
    //移除回调和所有message
        new Handler().removeCallbacksAndMessages(null);
  • 错误2:有时候:removeCallbacks会失效,不能从消息队列中移除;出现这情况是activity切入后台,再回到前台,此时的runnable由于被重定义,和原先的runnable并非同一个对象;给runnable加上static可以解决这个问题。

你可能感兴趣的:(破茧(二)Handler机制)