Handle消息机制

Handle消息机制

概要

首先我们说下我们为什么要设计一个handle?

现在出个题,如果没有handler你在2个线程之间如何同步数据?
实现这势必就需要弄锁机制,但是只有主线程才可以更新ui,这个时候上锁阻塞就非常影响主线程,接着,如果能够线程通信,数据的传送是不是需要依次过来,队列就来了。类似一种生产者消费者模型。Handler的最大作用就是线程的通信,并不是线程切换。

ThreadLocal

ThreadLocal通常称为“线程局部变量”,也就说某些数据是以线程为作用域,在不同线程中有不同的数据副本。简单来说,就是每个线程对应一个值,你在这个A线程只能存A线程的数据和得到A线程的数据,不能得到B线程的,有点像hashmap。
下面通过例子来说明,首先定义一个 ThreadLocal 对象,选择 Boolean 类型,如下所示

private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();

然后分别在主线程、子线程1和子线程2中设置和访问它的值

private void threadLocal() {
        mThreadLocal.set(true);
        Log.d(TAG, "[Thread#main]threadLocal=" + mThreadLocal.get());
        new Thread() {
            @Override
            public void run() {
                super.run();
                mThreadLocal.set(false);
                Log.d(TAG, "[Thread#1]threadLocal=" + mThreadLocal.get());
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                super.run();
                 mThreadLocal.set(true);
                Log.d(TAG, "[Thread#2]threadLocal=" + mThreadLocal.get());
            }
        }.start();
    }

虽然在不同的线程中访问的是同一个 ThreadLocal 对象,但是通过 ThreadLocal 获取到的值是不一样的。
之所以会这样,我们来说一下它的工作原理。

public class Thread implements Runnable {
  
    ThreadLocal.ThreadLocalMap threadLocals = null;

  static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

  private Entry[] table;
  }

也就是每一个线程都有一个

ThreadLocal.ThreadLocalMap threadLocals ,而threadLocals 内部有个table[]数组

其实就是ThreadLocal 在调用set()方法的时候set()里面去获取当前线程然后在根据当前线程的
ThreadLocal.ThreadLocalMap threadLocals ,把ThreadLocal 的引用对像,跟要放数据,一起放在这个线程的 ThreadLocal.ThreadLocalMap threadLocals的table[]数组中
,reference的索引位置加1就是value的索引位置,也就是类似
table[0]=ThreadLocal 引用 ,table[1]=value.
这样,每个线程就可以放n个ThreadLocal 引用,和n个value,对应关系就是那个+1的映射关系。而ThreadLocal的gei()方法也是一样,先
得到当前线程,然后在根据当前线程的 ThreadLocal.ThreadLocalMap threadLocals 的table[]数组,根据哪个个ThreadLocal的引用在table[]数组位置得到相应的value值。

总结,也就是说,每个线程都有个ThreadLocal.ThreadLocalMap threadLocals,而这个ThreadLocal.ThreadLocalMap threadLocals有个table[]数组,当一个ThreadLocal 对象引用 在一个线程里面set的时候就会把当前这个对象引用和 set的值放在当前线程的table[]数组里面,reference的索引位置加1就是value的索引位置,也就是类似table[0]=ThreadLocal 引用 ,table[1]=value, 那么当这个ThreadLocal 引用再次调用get的时候,就会从当前线程的table[]数组里面 根据这个引用获取 对应的值。这样,创建new了n ThreadLocal ,做了set 和get ,每个线程就可以放n个ThreadLocal 引用,和n个value。
所以 同一个 ThreadLocal 对象,在不同的线程 set 不同的值 ,那么每个线程的 table[]存放不同的值, 在不同的 线程 get 出来值 是不一样的 。

ThreadLocal与Looper

我们知道,我们的程序应用开启流程是由一个launch(app)去生产一个zygote,然后zygote去孵化一个独立的jvm。然后在这个jvm开启了我们的应用,应用被开起第一个被调用的就是
AcitivityThread.java 里面的main函数,而在这个main函数里面为当前主线程做了
Looper.prepareMainLooper()

在这里插入图片描述

在这个 prepareMainLooper方法会创建一个Looper 对象, 并调用 ThreadLocal 的set 方法
把 Looper 对象 存放在当前线程的
ThreadLocal.ThreadLocalMap threadLocals的 table[]数组 内,形成 table[0] = threadLocals , table[1] = looper
并且 把这个 Looper 对象 赋予 Looper类的一个静态static sMainLooper 对象
在这里插入图片描述
在这里插入图片描述

注意 ThreadLocal 没有持有 Looper 对象引用 , Looper 不是 存放在 ThreadLocal 中,
而是通过 ThreadLocal 存放在 当前线程的 ThreadLocalMap threadLocals的 table[]数组 内

并且只set 一次,所以一个线程也就只有一个 Looper,不同的looper 代表不同的线程

Handle基本使用

Handle操作基本有2种。

第一种就是用handleMessage,步骤为在开启的线程中做耗时任务,需要改变UI的时候就发送消息出来,然后再主线程handler里去改变,handler里也是主线程包含在内的。
类似源代码类似如下:

android.os.Handler handler =new android.os.Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //收到消息要做的更改UI的操作,msg是收到的消息
            }

        

    };//收到消息改变UI
       Button button = (Button) findViewById(R.id.c);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                     handler.sendEmptyMessage(0x123);//开启线程做耗时工作,需要改变UI就发送信息给主线程的handler去做
                    }
                });
                thread.start();

            }
        });

在这里边,创建Handler的线程和其handleMessage运行的线程是同一线程,mHandler是在主线程中创建的,所以其handleMessage方法也是在主线程中运行。mHandler.sendMessage(Message)可以在任何线程,
sendMessage还有许多变形,可以发送空message(只携带what参数)、延时消息、定时消息等。使用方式很简单。

第二种 采用handler.post

Handler handler =new Handler();
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText(R.string.sample);
                        }
                    });
                }
            }).start();
        }
    });

其实handle发送 post 了runable对象,其实还是调用了send 方法,这只是一种对runable的封装。看如下源码:

 public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
 public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

可见,两个方法都是通过调用sendMessageDelayed方法实现的,所以可以知道它们的底层逻辑是一致的。
但是,post方法的底层调用sendMessageDelayed的时候,却是通过getPostMessage®来将Runnable对象来转为Message,我们点进getPostMessage()可以看到:

 private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

从上面可以知道 。post个runable其实还是send 个message,其实就是把 runable 放在message.callback 中 ,即
message.callback=runable
然后最终还是send 个message对象到messagequeue

这么做的含义:
为了更方便开发者根据不同需要进行调用。当我们需要传输很多数据时,我们可以使用sendMessage来实现,因为通过给Message的不同成员变量赋值可以封装成数据非常丰富的对象,从而进行传输;当我们只需要进行一个动作时,直接使用Runnable,在run方法中实现动作内容即可。

也有一种方便的集成函数,叫做 runOnUiThread,即在一个子线程里调用showResponse(finalResult); 这个函数,finalresult是把子线程的控件需要的数据拿到主线程中,再在主线程中,

 public void showResponse(final String s) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTextView.setText(s);
            }
        });
    }

对于kotlin 写法上 是这样的

  val handler = object :Handler(threadLooper){
            override fun handleMessage(msg: android.os.Message) {
                super.handleMessage(msg)
                //这种创建handle方式不需要返回类型,handleMessage是 Handler 内的
            }
        }

        var handler1 = Handler(threadLooper){
            false
            //这种创建 handler1 方式是 是 Callback callback内的 handleMessage ,所以需要返回一个布尔值
            //false代表消息没处理完需要继续走 Handler 内的 handleMessage
            //true 代表消息处理完不需要走 Handler 内的 handleMessage
        }

Handle的消息机制工作流程

在一个线程中 调用 looper.prepare 创建 looper 对象,内部会把looper 通过 ThreadLocal 放 到当前 线程的 ThreadLocalMap threadLocals的 table[]数组 内, 在looper 对象创建初始化的时候 ,会创建 messagequeue 消息队列,接着,调用Loopr.looper() 开启这个机制

当某个Handle 在当前创建的时候,他就会 通过 ThreadLocal 根据当前线程的 ThreadLocalMap threadLocals 获取当前 线程 这个 looper 对象,后续 Handle 在其他线程发送消息就会 往 当前线程 的 looper 的 messagequeue 消息队列里面插入消息

整个流程是 分为 往消息队列存放message 流程往消息队列取出 message 流程

存放message流程:一般都是在子线程操作

handle1 在主线程 创建,然后在子线程 send message,那么这个消息就会把当前这个handle1附加在这个message.target身上
也就是 message.target = handle1,
后续在子线程 做存放message 流程
handle.sendMessage->handler.enqueueMessage->MessageQueue.enqueueMessage 也就是最后 通过 enqueueMessage 把 message 存放在 messagequeue 消息队列里面

然后在主线程会做 取出 message 流程

ActivityThread 进程开启的时候就会调用
Looper.loop()会开启死循环,也就是会调用消息队列的messagequeue.next() 方法, 也就是 next 会一直在队伍里面循环取出 消息
没有就阻塞等待,拿到 message 就会调用 message.target.dispatchMessage
根据 message.target知道 是handle1 那就是就会调用刚创建的handle1.dispatchMessage ,让 handler1.handleMessage去做处理消息

从上面知道线程间的通信原理其实就是 线程间内存共享
MessageQueue 他就是多个线程共享的资源,他存放着很多个Message, 正是因为 线程间内存共享,MessageQueue 是多个线程共享的资源,所以才可以,在子线程 往它存放数据,在主线程获取数据,而多个线程共享一个数据源,共同存放一个资源,比如多个handle 往一个MessageQueue 插入消息数据,那么必须上锁才可以保证线程的同步,也就是拿MessageQueue 做对象锁。

对于handle的dispatchMessage()方法,我们看下源码;
dispatchMessage

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 如果有Runnbale,则直接执行它的run方法
        handleCallback(msg);
    } else {
        //如果有实现自己的callback接口
        if (mCallback != null) {
            //执行callback的handleMessage方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //否则执行自身的handleMessage方法
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

根据源码我们清楚得到,先是判断这message是否是带着callback ,即runable,是那么去run,然后结束。不然的话,判断再创建handle 的方式是否用callback的方式,即:

  Handler.Callback callback= new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                //处理消息
                return false;
            }
        };
        Handler handler= new Handler(callback);
        handler.sendEmptyMessage(123);

平时我们创建handle是派生一个handle子类然后重写handleMessage,即

android.os.Handler handler1=new android.os.Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);

            }
        };
        handler1.sendEmptyMessage(123);

创建方式的不同,有个区别就是前后顺序不一样,如果Callback的handleMessage返回false,则后面的可以正常执行。但当返回true时,Message就被截获了,后面的handleMessage将不会被执行。

所以dispatchMessage先是判断这message是否是带着callback ,即runable,是那么去run,然后结束。不然的话,判断再创建handle 的方式是否用callback的方式,有就去调用callback的handleMessage,没有就调用的handleMessage()。

Handler postDelay

存放message 的消息队列是 单链表实现

Mesaage 类里有一个变量叫做Mesaage next。也就是 ,messagequeue 里面的mesaage们,是由一个mesaage 的next指向下一个message的,
mesaage ->next->mesaage ->next->message
这种就叫单链表,单链表就是next只向单方向的下一个索引,如果是双链表,就双方向的。

并且每个mesaage都有它的对应操作时间,也就是,不管handle 是延迟发送一个mesaage,还是不延迟,发送的mesaage都有它的一个对应时间,比如当handle 第一次发送一个mesaage,延迟时间是10s到messagequeue ,当下一个mesaage,延迟时间是5s,进入到messagequeue ,她会对messagequeue 里面的所有mesaage做一个遍历,哪个需要早操作就放前面,然后把5s的mesaage排在最前面。

当Looper.loop的 时候,会调用 messagequeue.next() 去 获取 消息队列里的 消息 给到 Looper,如果 消息队列 没有消息,或者 第一个消息
他的时间还没到达,那么 MessageQueue 调用 nativePollOnce() 阻塞,Looper 阻塞

比如说

postDelay() 一个10秒钟的 Runnable A、消息进队,MessageQueue 调用 nativePollOnce() 阻塞,Looper 阻塞,

紧接着 post() 一个 Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把 B 插入消息队列的头部(A的前面),然后调用nativeWake() 方法唤醒线程 ;

Looper 处理完这个消息再次调用 messagequeue.next(), 第二个消息 A 还没到时间,计算一下剩余时间(假如还剩9秒)继续调用 nativePollOnce() 阻塞;直到阻塞时间到或者下一次有 Message 进队

messagequeue 的next()方法是需要Loopr.looper()这个方法开启的,也就是Loopr才是开启的死循环寻找消息的源头,唯一跳出这个循环 是looper.quit(),looper.quit()后,messagequeue 的个next()方法会返回个null,然后两者死循环结束,这时候就全部结束,handle send ()就会返回fasle。looper 还有一种退出的方法是looper.quitsafely(),looper.quit()是立即结束,looper.quitsafely()是等队伍中消息做完再结束。所以是实现开发的时候,不用的工作了就应该,looper.quit()或者looper.quitsafely(),不然死循环寻找消息会一直。

messagequeue锁

从上面知道,线程间通信或者说Handler的原理其实是线程间内存共享,而共享就是 messagequeue 消息队列资源
equeuemessage 方法往 messagequeue 存放数据message ,next 方法往 数据队列取出数据message

问:既然可以存在多个handle往一个messagequeue插入消息的,handle又可能处于不同个线程,那她内部是如何保证线程安全的,

答:messagequeue里的equeuemessage方法用
synchronized(this)也就是拿了messagequeue的对象锁,也就是 多个 个handle做的插入消息操作,谁先拿到了messagequeue对象锁,谁就插入操作,造成同步线程安全
在这里插入图片描述

并且 ,取数据 也用 messagequeue的对象上锁 ,证明 取数据 和插入数据 是互斥访问的,只有等待一方做完,另外一方才可以再继续做 ,谁先拿到锁 谁先取或者寸
在这里插入图片描述

创建Message方式

我们创建一个message 应该以什么方式?

创建一个message 用 Message msg = Message.obtain(); 获得一个消息。

message被处理完后,它占用的内存位置和大小并不会被回收,而是把这块区域的内容置空,然后放到缓存池中,方便下次复用

我们用Message.obtain()方法获取一个消息时,会先从缓存池看是否有我要的箱子大小,如果缓存池没有消息,才会去创建消息。这样做可以做的目的是为了防止内存碎片造成的内存抖动引起的oom

这样做的原因是,我们知道,我们每一次new 出一个对象,都会在应用内存总大小中随机位置抠出一块区域,而如果在短时间内,又不断的扣出很多大大小的的洞,尽管会gc回收,在这样短时间内不断创建和回收的同时,就会出现很多内存碎片,从而造成内存抖动,而如果突然需要一个极大的占用内存大小的箱子,而内存占用大小又连续的,尽管内存中是有足够的大小给予的但是扣了很多个位置导致没有连续大位置所以就是oom。所以用Message.obtain();会先去看有没有适合的箱子已经存在,没有再去new对象。
在这里插入图片描述

同一个Message 对象 不要发送两次。如下面的代码是有问题会报错抛出的:

//同一个Message发送了两次
Message msg = Message.obtain();
handler.sendMessage(msg);
handler.sendMessage(msg);

这是因为消息都是发送到MessageQueue存放,一旦 Message 被发送到 MessageQueue,它就应该被视为“已占用”或“已提交”状态,
如果 Message 在第一次发送后但在处理前被再次发送,那么它的内部状态(如 what、arg1、arg2、obj 等)可能在第二次发送时与第一次发送时不同,导致处理逻辑混乱。

为了避免异常,通常的做法是在每次发送消息后不再使用同一个 Message 对象,除非你确定它不会再被发送到队列中。如果你需要发送多个相似的消息,你应该为每个消息创建一个新的 Message 对象(通过 Message.obtain() 或直接构造)并设置相应的属性数据,而不是用原来的Message去发送一次

因此,修改后的代码可能如下所示:

handler.sendMessage(Message.obtain());  
handler.sendMessage(Message.obtain());

子线程使用创建Looper

问:每个线程是如何创建looper的,当前线程的Handler 创建后 是如何找到当前线程的looper的,如何在子线程使用Handle?

当在一个线程中执行
执行 Looper.prepare() 的时候就会为当前线程创建一个looper对象
Looper.prepare() 内部其实是 创建一个 looper对象 然后调用 Threadlocal set 存放 Looper 对象

Threadlocal可以在不同线程中互不干扰的存储并提供数据,在handle这里,存储的数据就是looper,Looper保存在ThreadLocal里面的,源码保证只可以有一次set,也就是只有一个ThreadLocal引用,所以,也就只有一个looper

当在一个 线程创建Handle 对象的时候,handle 默认是用的所处线程的looper,也就是Handle里面会调用 Looper.getMainLooper() 来获取当前线程的looper进行绑定,也就是从当前线程的ThreadLocal 中调用 get 获取这个looper对象并以此绑定
而当执行 Looper.loop()的时候内部的消息循环才会开始

在主线程ActivityThread 中 默认已经调用
Looper.prepareMainLooper();以及Looper.loop();
所以handle 可以 直接 在主线程 创建

如果在子线程创建handle,
就要设置主线程的looper或者为子线程创建个Looper,也就是 Looper.prepare();
handle 想要通信到 哪个线程, 就用哪个线程的 looper 根据情况自己设置

在子线程中使用handle

fun main() {


    //主线程looper
    var mainLooper: Looper =Looper.getMainLooper()

    Thread{
        //在子线程再次创建属于当前线程的 looper
        Looper.prepare()
        //获取子线程的 looper
        val threadLooper: Looper = Looper.getMainLooper()
        //创建一个与当前Looper关联的Handler
        //就算这里参数不指定 threadLooper 创建的handle 默认是用的所处线程的looper,
        //也就是Handle里面会调用 Looper.getMainLooper() 来获取当前线程的looper进行绑定
        val handler = object :Handler(threadLooper){
            override fun handleMessage(msg: android.os.Message) {
                super.handleMessage(msg)
                //这种创建handle方式不需要返回类型,handleMessage是 Handler 内的
            }
        }

        Looper.loop()

        // 注意在不需要的记得 退出循环
        threadLooper.quit() 


    }.start()


}

HandleThread 原理

HandleThread 是个线程,内部自己创建一个属于此线程的Looper
也就是内部会调用 Looper.prepare() 去创建

这样在此线程创建的Handler 就会跟此线程的 Looper 进行绑定

HandleThread 源码

HandleThread 在 线程 run 方法 和 getLooper 方法 内 使用了互斥访问,让其他线程可以通过 HandleThread 对象 调用
getLooper 方法 获取到 HandleThread 线程的 Looper 对象 ,并且通过互斥访问,保证其他线程在调用
getLooper 方法 获取到 HandleThread 线程的 Looper 对象 之前, HandleThread 线程的 Looper 对象 对象是先赋值有值了
也就是 run 中 synchronized {} 先执行 再 执行 getLooper 的 synchronized {},

也就是,其他线程获取 调用 HandleThread 对象 的
getLooper 方法 会被阻塞挂起,直到 线程开启 run 中 synchronized {} 执行完再执行 。

而 isAlive() 放弃是判断一个线程是否活跃也就是开启, 防止 如果先调用了 在主线程调用 了 HandleThread 对象 getLooper ()
而不调用 HandleThread 对象 的start () ,主线程会 因为 wait () 一直阻塞挂起问题。

wait ()是释放锁,并且释放cpu资源给其他线程,并且他阻塞住调用的线程,停在那里等待notify()

互斥锁
应用场景如下:

有时候我们 写代码的时候 在主线程开启一个线程去做事情,然后希望上面的代码先执行 ,下面的代码在上面的代码获取结果 后才执行

public class MyThread extends Thread {

    String result;

    @Override
    public void run() {
        super.run();

        result = "result";

    }
}


String getResult() {
    return result;
}

}


//在主线程中开启一个线程做事情
val myThread = MyThread()
myThread.start()
 // 但是在这里,你无法保证,线程的中任务先执行,然后下面的获取数据才执行, 很有可能下面的主线程获取资源执行了,它获取的数据是null 
//在这里是可以简单的使用 sleep (1000) 让 主线程 停止等待下,等上面执行完 然后下面才执行,但是还有更好方法
myThread.getResult()

那就是采用 互斥访问

public class MyThread  extends Thread {

    String result;

    @Override
    public void run() {
        super.run();
        Log.d("MyThread", "run: 1");
        synchronized (this){
            sleep(2000);
            result = "result";
            Log.d("MyThread", "run: 2");
            notifyAll();
        }
    }


    String getResult() throws InterruptedException {
        Log.d("MyThread", "getResult: 1");
         if(!isAlive()){
            return null;
        }
        synchronized(this){
            Log.d("MyThread", "getResult: 2");
            while (result == null){
                Log.d("MyThread", "getResult: 3");
                wait();
            }
        }
        Log.d("MyThread", "getResult: 4");
        return result;
    }

}

val myThread = MyThread()
myThread.start()

myThread.getResult()
Log.d("activity", "获取资源成功")

我们希望的是 线程里面 获取到资源 是最先执行, 然后 主线程获取资源的时候才执行
在这里 通过 两个 synchronized 锁,进行了两个 synchronized{}块内的互斥访问

先是利用 myThread 这个对象锁 ,对子线程的 synchronized {} 和主线程的 synchronized {} 谁先抢到锁,谁就先执行

有两种情况:

  • 如果是 子线程的 synchronized {} 先拿到锁,那么当 主线程 调用 getResult
    方法的时候
    它会停在锁synchronized {}外 等待获取,这个时候主线程是卡住的你会发现主线程的
    Log.d(“activity”, “获取资源成功”) 这句打印是 得 等到 主线程 myThread.getResult()
    获取锁 之后才会执行完

在这里插入图片描述

  • 如果 是 主线程的 synchronized {} 先拿到锁,那么他会因为没有拿到资源,资源 ==null ,不断死循环在 wait()
    也就 不断的在释放锁, 也就是主线程会停在 wait 这里,而这个时候 锁会给到子线程的 synchronized {}
    获取资源,然后 ,获取完资源后,调用了notifyAll() 唤醒主线程 wait ,也就是 wait
    后面就会继续执行,而由于获取到资源了,所以不再死循环 所以就返回 资源 最终主线程获取成功这一句话才会打印

这样不管cpu 如何调度 谁先获取锁,都能保证 子线程获取到资源 是最先执行, 然后 主线程获取资源的时候才执行

Looper 与anr

问:looper一直在做死循环,当一条消息处理太久,为什么不会因此 导致anr 当消息队列空的时候,他会睡眠阻塞,为什么不会造成anr

anr 只包含四种情况 :service处理很久,input处理很久,contentprovider处理很久,brodcast处理很久,ams发现了,就会报告

而 looper假如有消息处理很久,会导致卡顿,但是不一定会导致anr,不属于anr触发的范围

Android使用了Linux的epoll机制来优化I/O操作。在消息队列为空时,Looper会利用epoll机制使线程进入休眠状态,并且会释放CPU资源,当有新消息到来时,epoll机制会唤醒线程,Looper继续处理消息

Handle内存泄漏

Handle 引起内存泄漏的原因:

Handler 在activity 等 使用如下创建方式

//匿名内部类  
Handler handler=new Handler(){
  @Override
  public void handleMessage(Message msg) {
    super.handleMessage(msg);
  }
};



  //非静态内部类
  class AppHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
      switch (msg.what) {
        // TODO: 2019/4/30 
      }
    }
  }

匿名内部类 或者 非静态内部类 默认持有外部类的引用,上面 Handler 在activity 等 创建方式 使得Handler默认持有外部类activity的引用

由上面的知识我们知道,Handler 持有 Activity ,而 发送的消息 message 它的target 又持有Handler ,即 message.target = Handle ,
而 message 又存放在 MessageQueue,所以 MessageQueue 持有 message ,而 MessageQueue 又是在 Looper 种创建的,也就是
Looper 持有 MessageQueue ,而 Looper 又存放在 当前线程的 ThreadLocalMap 的table[] 数组中

也就是整个链路引用持有链路如下

ThreadLocalMap > Looper > MessageQueue > Message > Handler >Activity

而 Looper 对象 会是static 静态的
在这里插入图片描述

当 Handler 发送的一条延迟消息,或者说 MessageQueue 中的消息还没被处理完,队列中还存在消息,
这个时候 Activity 想要被销毁走了 onDestroy 应该被销毁
当gc 回收的时候,根据上面的持有链路,Looper 对象 会是static ,也是生命周期是整个进程都在,它间接持有 Activity ,导致
gc 回收的时候, Activity 应该被释放,而因为被 一个生命周期比自己还长的 Looper 对象持有,导致无法释放,所以就内存泄漏了

解决内存泄漏的办法

解放内存泄漏的办法 的方法原理就是切掉上面的引用链路持有,也就算不再让 静态的 static Looper 持有 Activity

  1. 静态内部类+弱引用

第一,在创建内部类Handle 的时候需要用static

private static class MyHandler extends Handler{
	private final WeakReference<MineActivity> mMineActivityWeak;
	public MyHandler(MineActivity mineActivity){
		mMineActivityWeak = new WeakReference<>(mineActivity);
	}
	@Override
	public void handleMessage(@NonNull Message msg) {
		super.handleMessage(msg);
		MineActivity mineActivity = mMineActivityWeak.get();
	
    }
}

当你把 Handler 设置为 static 静态的,那么这个时候 Handler 就不再有 Activity,
进而斩断链路持有关系, 静态的 static Looper 也就不再 持有 Activity

第二

当这个 静态的 Handler 想要用到外面的 Activity 来操作, 就得通过 弱引用的方式去引用 Activity,
不然如果还是用强引用,那么 Handler 还是会持有 Activity,gc 回收的时候还是会内存泄漏

使用弱引用的一个常见目的是为了访问外部类的成员变量。
当我们在静态内部类中需要访问外部类的成员变量时,就得使用弱引用来解决,这样引用的对象才可以被垃圾回收器回收

  1. 清空 Handler 发送过的 消息队列的消息
  @Override
  protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);

  }

创建的 这个 Handler 移除掉它 在 MessageQueue 中 存放的消息 ,
这样那些 带有 target 是 Handler 的message ,就不再有,
MessageQueue 就不再 带有 target 是 Handler 的message ,
这样上面的持有链路也就被斩断,那么activity 就自然可以释放掉了

注意这里的 mHandler.removeCallbacksAndMessages 移除消息是 只移除掉 这个 mHandler 往 MessageQueue 存放的消息
还有其他 Handle 的消息还存放着

同步屏障

上面中我们知道,线程的消息都是放到同一个MessageQueue里面,并且是按照每个信息的对应的时间排列起来的顺序,looper从MessageQueue拿信息并不需要遍历所有信息,它只需要拿第一个,因为队列中第一个就是最早需要处理的消息,那么问题来了,MessageQueue的信息们是同步的一个个执行的,那么同一个时间范围内的消息,如果有一个消息它需要立刻执行,那我们怎么办,做法思想就是对任务队列中插入一个同步屏障,然后把这个需要立刻处理的信息设置为是异步的,这样当looper从MessageQueue的队列中第一个信息发现了这个同步屏障,那么他就去遍历,在队列中找到这个异步的消息并且排斥了同步的消息,做完异步的消息后,再撤销对这个同步屏障。

设置一个同步屏障是利用

MessageQueue的postSyncBarrier这个方法。
而设置一条信息为异步的是调用

Message message=Message.obtain();
message.setAsynchronous(true);
handler.sendMessage(message);

接着最后撤销掉这个同步屏障,调用的是

MessageQueue的removeSyncBarrier这个方法

那么他们的原理是什么呢?
一开始,当你设置信息为异步消息,然后调用MessageQueue的postSyncBarrier,在postSyncBarrier中

/**
*
@hide
**/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token
synchronized (this) {
final int token = mNextBarrierToken++;
//从消息池中获取Message
final Message msg = Message.obtain();
msg.markInUse();
//就是这里!!!初始化Message对象的时候,并没有给target赋值,因此 target==null
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
//如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
prev = p;
p = p.next;
}
}
//根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

可以看到,这样,一条 target == null 的消息就进入了消息队列。 (也就是在消息队列中设置了同步屏障。)
接着。我们说looper会调用MessageQueue的next()来返回一个message,并且这个message队列的第一个。当MessageQueue的next()的发现队列第一个的message的target == null,那么就会遍历整个队列找出异步的消息然后排斥同步去处理。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值