学习android中的Handler机制和由此引发的内存泄露以及解决办法

本文主要是介绍学习android中的Handler机制和由此引发的内存泄露以及解决办法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、Handler机制以及使用方法  

       Handler、Looper、Message都是android的异步消息处理机制,异步消息处理线程启动之后,会进入一个无限循环体中,每循环一次从消息队列中取出一个消息,然后回调消息处理函数,执行完成之后,继续进行循环,如果消息队列为空,则线程会阻塞等待。

 1   Looper作用

       创建一个MessageQueue,然后进入一个无限循环体,然后不断的从MessageQueue中读取消息,而消息的创建者是一个或者多个Handler。

    (1)与当前线程绑定,保证一个线程只有一个Looper实例,同时Looper实例也之后一个MessageQueue。

    (2)loop()方法,不断从MessageQueue中取消息,交给消息的target属性的dispatchMessage去处理。

  2  Handler:

    Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue,因为 Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

    Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue中读取消息,然后回调

msg.target.dispatchMessage(msg)方法。

    一般用来更新UI,声明的时候直接初始化,会首先得到当前线程中保存的Looper实例,然后与Looper中的MessageQueue关联。

    sendMessage方法,会给msg的taget赋值为handler本身,然后加入MessageQueue中。

    在构造Handler实例时,重写handleMessage方法。

(再Activity中没有显示调用Looper.prepare和Looper.loop()方法,为什么Hander可以成功创建呢,因为再Activity的启动代码中,已经再当前的UI线程中调用了Looper.prepare()和Looper.loop()方法。

   3  Handler的post方法创建的线程和UI线程有什么关系?

    mHandler.post(new Runnable()){

        public void run(){

         mTxt.setText("hdjhfjdh");

        }

    }

      然后run方法中可以写更新UI的代码,其实这个Runnable并没有创建什么线程,而是发送了一条消息,下面看源码:

[java] view plain copy
  1. public final boolean post(Runnable r)  
  2.    {  
  3.       return  sendMessageDelayed(getPostMessage(r), 0);  
  4.    }  
  1. private static Message getPostMessage(Runnable r) {  
  2.       Message m = Message.obtain();  
  3.       m.callback = r;  
  4.       return m;  
  5.   }  

      可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.

      注:产生一个Message对象,可以new  ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

[java] view plain copy
  1. public final boolean sendMessageDelayed(Message msg, long delayMillis)  
  2.    {  
  3.        if (delayMillis < 0) {  
  4.            delayMillis = 0;  
  5.        }  
  6.        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
  7.    }  

[java] view plain copy
  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {  
  2.        MessageQueue queue = mQueue;  
  3.        if (queue == null) {  
  4.            RuntimeException e = new RuntimeException(  
  5.                    this + " sendMessageAtTime() called with no mQueue");  
  6.            Log.w("Looper", e.getMessage(), e);  
  7.            return false;  
  8.        }  
  9.        return enqueueMessage(queue, msg, uptimeMillis);  
  10.    }  
最终和handler.sendMessage一样,调用了sendMessageAtTime,然后调用了enqueueMessage方法,给msg.target赋值为handler,最终加入MessagQueue.

可以看到,这里msg的callback和target都有值,那么会执行哪个呢?

其实上面已经贴过代码,就是dispatchMessage方法:

  1. public void dispatchMessage(Message msg) {  
  2.        if (msg.callback != null) {  
  3.            handleCallback(msg);  
  4.        } else {  
  5.            if (mCallback != null) {  
  6.                if (mCallback.handleMessage(msg)) {  
  7.                    return;  
  8.                }  
  9.            }  
  10.            handleMessage(msg);  
  11.        }  
  12.    }  

第2行,如果不为null,则执行callback回调,也就是我们的Runnable对象。

4.Handler在不同线程之间传递消息

    可以再子线程中创建一个Handler,然后使用这个Handler实例在任何其他线程中发送消息,最终处理消息的代码会在你创建的Handler实例的线程中运行。

    new Thread()

    {

        private Handler handler();

        public void run(){

        Looper.prepare();

        handler = new Handler(){

            public void handleMessage(android.os.Message msg){

            };

        };

二、内存泄露及处理办法

    java中的GC自动检查内存中的对象(虚拟机决定),如果GC发现一个或者一组对象可达状态,则将该对象从内存中回收。也就是说一个对象不被任何引用所指向,该对象会在被GC发现的时候回收。另外如果一组对象中只包含互相的引用,而没有来自他们外部的引用,仍属于不可达状态,同样会被回收。

   1、Handler中为什么会在成内存泄露呢?

    Handler mHandler = new Handler(){

        public void handleMessage(Message msg){

            //处理逻辑

        }

    }

    当使用内部类创建Handler的时候,会隐式的持有一个外部类对象(通常是一个Activity)引用,不然怎么通过Handler操作Activity中的view,而Handler一般伴随着一个耗时的后台线程,后台线程处理完成之后,会通过消息机制通知Handler,然后Handler把消息更新到Activity,如果在Handler执行耗时逻辑的时候Activity关闭了,这个时候Handler持有Activity的引用,而Activity持有Handler的引用,此时GC就无法回收Activity,直到耗时逻辑执行结束。另外如果你执行Handler的Delayed方法,该方法会将你的Handler装入一个Message,并把这条Message推入到MessageQueue中,在delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

    内存泄露的危害之后一个,就是虚拟机占用内存过高,导致OOM(内存溢出) ,程序出错,比如打开一个Activity,然后关闭,内存泄露,循环几次后,程序占用内存超过系统限制,然后挂掉。

    2、解决内存泄露的办法

    方法一:在关闭Activity的时候关闭后台程序,线程关闭相当于切断了Handler和外部连接的线。

     如果Handler被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks方法,把消息对象从消息队列中移除。

    方法二:将Handler声明为静态类

    静态类不持有外部类的对象,所以你的Activity可以随意被回收

    static class MyHandler extent Handler{

        public void handleMessage(Message msg){

               //处理逻辑

        }

    }

    但是添加静态内部类之后不持有外部对象引用,导致程序不允许在Handler中处理Activity对象,需要在Handler中增加一个对Activity的弱引用

    statica  class MyHandler extends Handler{

        WeakReference<Activity>mActivityReference;

        MyHandler(Activity activity){

            mActivityReference = new WeakReference<Activity>(activity);

            public void handleMessage(Message msg){

                final Activity activity = mActivityReference.get();

                if(activity != null){

                    //处理逻辑

                }

            }

        }

    }

当activity结束后需要取消掉该Handler对象的Message和Runnable,可以使用removeCallbacks(Runnable r)和 removeMessages(int what)方法。

public  void onDestory(){

    mHandler.removeCallbacksAndMessages(null);

}

3、强、软、弱、虚引用

    四种引用方式可以更加灵活的控制对象的生命周期。

    (1)强引用

    Object object = new Object();那么object就是一个强引用,如果对象有强引用,GC不能回收,当内存空间不足时宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会随意回收具有强引用的对象。

    (2)软引用

    如果内存空间充足,垃圾回收机制不会回收,如果内存空间不足,就会回收。如果一个软引用所引用的对象被回收,这个软引用就会被虚拟机加入到与之关联的引用队列中。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

    (3)弱引用

    具有弱引用的对象生命周期更短,在垃圾回收线程扫描到时,一旦发现都会回收,但是GC优先级是一个优先级比较低的线程,因此不会很快发现只具有弱引的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 

    (4)虚引用

    虚引用不会决定对象的生命周期,任何时候都可以被回收,虚引用可以被用来跟踪对象被垃圾回收的活动,虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 

https://blog.csdn.net/panyongcsd/article/details/46605613

https://blog.csdn.net/jdsjlzx/article/details/51386440

这篇关于学习android中的Handler机制和由此引发的内存泄露以及解决办法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1080036

相关文章

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

Spring Security 单点登录与自动登录机制的实现原理

《SpringSecurity单点登录与自动登录机制的实现原理》本文探讨SpringSecurity实现单点登录(SSO)与自动登录机制,涵盖JWT跨系统认证、RememberMe持久化Token... 目录一、核心概念解析1.1 单点登录(SSO)1.2 自动登录(Remember Me)二、代码分析三、

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

java内存泄漏排查过程及解决

《java内存泄漏排查过程及解决》公司某服务内存持续增长,疑似内存泄漏,未触发OOM,排查方法包括检查JVM配置、分析GC执行状态、导出堆内存快照并用IDEAProfiler工具定位大对象及代码... 目录内存泄漏内存问题排查1.查看JVM内存配置2.分析gc是否正常执行3.导出 dump 各种工具分析4.

Java中的xxl-job调度器线程池工作机制

《Java中的xxl-job调度器线程池工作机制》xxl-job通过快慢线程池分离短时与长时任务,动态降级超时任务至慢池,结合异步触发和资源隔离机制,提升高频调度的性能与稳定性,支撑高并发场景下的可靠... 目录⚙️ 一、调度器线程池的核心设计 二、线程池的工作流程 三、线程池配置参数与优化 四、总结:线程

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期