Android内存优化场景

news2024/11/28 4:46:24

1、集合类

  • 内存泄露原因
    集合类 添加元素后,仍引用着 集合元素对象,导致该集合元素对象不可被回收,从而 导致内存泄漏
  • 实例演示
// 通过循环申请Object 对象 & 将申请的对象逐个放入到集合List
List<Object> objectList = new ArrayList<>();        
       for (int i = 0; i < 10; i++) {
            Object o = new Object();
            objectList.add(o);
            o = null;
        }
// 虽释放了集合元素引用的本身:o=null)
// 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象
  • 解决方案
// 释放objectList
objectList.clear();
objectList=null;

2、Static 关键字修饰的成员变量

  • 泄露原因
    若使被 ​​Static​​ 关键字修饰的成员变量 引用耗费资源过多的实例(如​​Context​​),则容易出现该成员变量的生命周期 > 引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露

  • 实例讲解

public class ClassName {
	 // 定义1个静态变量
	 private static Context mContext;
	 //...
	 // 引用的是Activity的context
	 mContext = context; 
	
	 // 当Activity需销毁时,由于mContext = 静态 & 生命周期 = 应用程序的生命周期,故 Activity无法被回收,从而出现内存泄露
}
  • 解决方案
    a) 尽量避免 ​​Static​​ 成员变量引用资源耗费过多的实例(如 ​​Context​​)
    若需引用 Context,则尽量使用Applicaiton的Context
    b)使用 弱引用​​(WeakReference)​​

3、单例模式(静态成员变量非常典型的例子 )

  • 储备知识
    单例模式 由于其静态特性,其生命周期的长度 = 应用程序的生命周期
  • 泄露原因
    若1个对象已不需再使用 而单例对象还持有该对象的引用,那么该对象将不能被正常回收 从而 导致内存泄漏
  • 实例演示
// 创建单例时,需传入一个Context
// 若传入的是Activity的Context,此时单例 则持有该Activity的引用
// 由于单例一直持有该Activity的引用(直到整个应用生命周期结束),即使该Activity退出,该Activity的内存也不会被回收
// 特别是一些庞大的Activity,此处非常容易导致OOM

public class SingleInstanceClass {    
    private static SingleInstanceClass instance;    
    private Context mContext;    
    private SingleInstanceClass(Context context) {        
        this.mContext = context; // 传递的是Activity的context
    }  

    public SingleInstanceClass getInstance(Context context) {        
        if (instance == null) {
            instance = new SingleInstanceClass(context);
        }        
        return instance;
    }
}
  • 解决方案
    单例模式引用的对象的生命周期 = 应用的生命周期
    Application的 Context,因 Application的生命周期 = 整个应用的生命周期
public class SingleInstanceClass {    
    private static SingleInstanceClass instance;    
    private Context mContext;    
    private SingleInstanceClass(Context context) {        
        this.mContext = context.getApplicationContext(); // 传递的是Application 的context
    }    

    public SingleInstanceClass getInstance(Context context) {        
        if (instance == null) {
            instance = new SingleInstanceClass(context);
        }        
        return instance;
    }
}

4、非静态内部类 / 匿名类

  • 储备知识
    非静态内部类 / 匿名类 默认持有 外部类的引用;而静态内部类则不会
  • 常见情况 3种,分别是:非静态内部类的实例 = 静态、多线程、消息传递机制(​​Handler​​)

4.1 非静态内部类的实例 = 静态

  • 泄露原因
    若 非静态内部类所创建的实例 = 静态(其生命周期 = 应用的生命周期),会因 非静态内部类默认持有外部类的引用而导致外部类无法释放,最终造成内存泄露即外部类中 持有 非静态内部类的静态对象

  • 实例演示

   // 背景:
   a. 在启动频繁的Activity中,为了避免重复创建相同的数据资源,会在Activity内部创建一个非静态内部类的单例
   b. 每次启动Activity时都会使用该单例的数据

public class TestActivity extends AppCompatActivity {  

    // 非静态内部类的实例的引用
    // 注:设置为静态  
    public static InnerClass innerClass = null; 

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {        
        super.onCreate(savedInstanceState);   

        // 保证非静态内部类的实例只有1个
        if (innerClass == null)
            innerClass = new InnerClass();
    }

    // 非静态内部类的定义    
    private class InnerClass {        
        //...
    }
}

// 造成内存泄露的原因:
// a. 当TestActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期 = 应用App的生命周期、持有外部类TestActivity的引用
// b. 故 TestActivity无法被GC回收,从而导致内存泄漏

4.2 多线程:AsyncTask、实现Runnable接口、继承Thread类

  • 储备知识
    多线程的使用方法 = 非静态内部类 / 匿名类;即 线程类 属于 非静态内部类 / 匿名类
  • 泄露原因
    当 工作线程正在处理任务 & 外部类需销毁时, 由于 工作线程实例 持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露
  • 多线程主要使用的是:
    a) ​​AsyncTask​​ 实现
    ​​b) Runnable​​ 接口 & 继承
    c) ​​Thread ​​类继承

3者内存泄露的原理相同,Thread​​类 为例说明

/** 
     * 方式1:新建Thread子类(内部类)
     */  
        public class MainActivity extends AppCompatActivity {

        public static final String TAG = "carson:";
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // 通过创建的内部类 实现多线程
            new MyThread().start();

        }
        // 自定义的Thread子类
        private class MyThread extends Thread{
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                    Log.d(TAG, "执行了多线程");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

   /** 
     * 方式2:匿名Thread内部类
     */ 
     public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 通过匿名内部类 实现多线程
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                    Log.d(TAG, "执行了多线程");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }.start();
    }
}


 /** 
  * 分析:内存泄露原因
  */ 
  // 工作线程Thread类属于非静态内部类 / 匿名内部类,运行时默认持有外部类的引用
  // 当工作线程运行时,若外部类MainActivity需销毁
  // 由于此时工作线程类实例持有外部类的引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露
  • 解决方案
    从上面可看出,造成内存泄露的原因有2个关键条件:
    a) 存在 ”工作线程实例 持有外部类引用“ 的引用关系
    b) 工作线程实例的生命周期 > 外部类的生命周期,即工作线程仍在运行 而 外部类需销毁
    解决方案的思路 = 使得上述任1条件不成立 即可。
// 共有2个解决方案:静态内部类 & 当外部类结束生命周期时,强制结束线程
// 具体描述如下

   /** 
     * 解决方式1:静态内部类
     * 原理:静态内部类 不默认持有外部类的引用,从而使得 “工作线程实例 持有 外部类引用” 的引用关系 不复存在
     * 具体实现:将Thread的子类设置成 静态内部类
     */  
        public class MainActivity extends AppCompatActivity {

        public static final String TAG = "carson:";
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            // 通过创建的内部类 实现多线程
            new MyThread().start();

        }
        // 分析1:自定义Thread子类
        // 设置为:静态内部类
        private static class MyThread extends Thread{
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                    Log.d(TAG, "执行了多线程");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

   /** 
     * 解决方案2:当外部类结束生命周期时,强制结束线程
     * 原理:使得 工作线程实例的生命周期 与 外部类的生命周期 同步
     * 具体实现:当 外部类(此处以Activity为例) 结束生命周期时(此时系统会调用onDestroy()),强制结束线程(调用stop())
     */ 
     @Override
    protected void onDestroy() {
        super.onDestroy();
        Thread.stop();
        // 外部类Activity生命周期结束时,强制结束线程
    }

4.3消息传递机制:Handler

具体请看文章:​ ​Android 内存泄露:详解 Handler 内存泄露的原因​

Android开发过程中,非静态内部类的一个典型使用场景就是Handler,例如:

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 
    start();
  }

  private void start() {
    Message msg = Message.obtain(); 
    msg.what = 1; 
    mHandler.sendMessage(msg);
  }

  private Handler mHandler = new Handler() { 
    @Override
    public void handleMessage(Message msg) { 
      if (msg.what == 1) {
      // do something
      } 
    }
  }};
}
  • 泄露原因
    通过Handler的消息机制我们可以知道,Handler的引用会作为成员变量保存在msg中,也就是msg持有对Hanlder的引用,而Handler作为匿名内部类持有外部类Activity的引用,也就相当于发出的msg间接持有了Activity的引用。msg对象被发送到MessageQueue中,等待Looper的轮询处理,若Activity退出时,MessageQueue中仍存在未处理的msg,那么此时Activity就无法及时被释放,从而造成内存泄露。

  • 解决方案
    通常在Android开发过程中,如果要使用内部类,那么可以采取静态内部类+弱引用的方式。

public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new MyHandler(this); start();
}

private void start() {
  Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg);
}

private static class MyHandler extends Handler {
  private WeakReference<MainActivity> activityWeakReference;
  public MyHandler(MainActivity activity) { 
    activityWeakReference = new WeakReference<>(activity);
  }

  @Override
  public void handleMessage(Message msg) {
    MainActivity activity = activityWeakReference.get(); 
    if (activity != null) {
      if (msg.what == 1) { 
        // do something
      } 
    }
  }
 }
}

Handler通过弱引用的方式持有Activity的引用,这样在GC进行回收时,Activity占用的内存可以及时释放,从而避免了内存泄露。

当然,这样的做法是依赖GC的回收时机的,在Activity退出到GC到达之间的这段时间内,还是会有不必要的内存占用,所以最完善的做法是在Activity销毁时,将Handler中的msg全部都移除掉,不再进行处理:

@Overrideprotected void onDestroy() {
     super.onDestroy(); 
    mHandler.removeCallbacksAndMessages(null);
}

5、资源对象使用后未关闭

  • 泄露原因
    对于资源的使用(如 广播​​BraodcastReceiver​​、文件流​​File​​、数据库游标​​Cursor​​、图片资源​​Bitmap​​等),若在​​Activity​​销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏
  • 解决方案
    在​​Activity​​销毁时 及时关闭 / 注销资源
// 对于 广播BraodcastReceiver:注销注册
unregisterReceiver()

// 对于 文件流File:关闭流
InputStream / OutputStream.close()

// 对于数据库游标cursor:使用后关闭游标
cursor.close()

// 对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null 
Bitmap.recycle()Bitmap = null;

// 对于动画(属性动画)
// 将动画设置成无限循环播放repeatCount = “infinite”后
// 在Activity退出时记得停止动画

6、 其他使用

除了上述4种常见情况,还有一些日常的使用会导致内存泄露
主要包括:
​​Context​​、WebView​​、Adapter​​
具体介绍如下
在这里插入图片描述

7、总结

在这里插入图片描述

参考

  • Android性能优化:手把手带你全面了解 内存泄露 & 解决方案

  • Android中内存泄露的常见场景以及优化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/470978.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VBA-自定义面板,使用SQL查询Excel数据

需求 定制插件&#xff0c;实现用户打开任意一个工作簿&#xff0c;写sql对Excel中的数据进行查询 案例sql需求场景&#xff1a; 需求 筛选日期小于’2023-4-24’&#xff0c;按group分区&#xff0c;求和各分组下的销售额&#xff0c;返回结果集新建工作表写入 数据源 现…

Docker-compose 启动 lnmp 开发环境

GitHub传送阵 docker-lnmp 项目帮助开发者快速构建本地开发环境&#xff0c;包括Nginx、PHP、MySQL、Redis 服务镜像&#xff0c;支持配置文件和日志文件映射&#xff0c;不限操作系统&#xff1b;此项目适合个人开发者本机部署&#xff0c;可以快速切换服务版本满足学习服务新…

国产开源项目管理软件ZenTao

本文应网友 ukiyoec 要求而写&#xff1b; 什么是禅道 &#xff1f; 禅道 (ZenTao)是国产开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、组织管理和事务管理于一体&#xff0c;是一款专业的研发项目管理软件&#xff0c;完整覆盖了研发项目管理的核心流程。禅…

2023-Hive性能企业级调优

Hive作为大数据平台举足轻重的框架&#xff0c;以其稳定性和简单易用性也成为当前构建企业级数据仓库时使用最多的框架之一。 但是如果我们只局限于会使用Hive&#xff0c;而不考虑性能问题&#xff0c;就难搭建出一个完美的数仓&#xff0c;所以Hive性能调优是我们大数据从业…

前端周总结

在vue里面引入ts文件报错&#xff1a; An import path cannot end with a .ts extension. Consider importing xx.js instead. 方法一&#xff08;最快&#xff09; 把引入的xx.ts后缀删除 方法二 # 在tsconfig.json中加入以下配置 "baseUrl": ".", &quo…

Oracle LiveLabs实验:DB Security - Data Masking and Subsetting (DMS)

概述 本实验介绍了适用于 Enterprise Manager 的 Oracle 数据屏蔽和子集 (DMS) 包的各种特性和功能。 它使用户有机会学习如何配置这些功能&#xff0c;以便在非生产环境中保护他们的敏感数据。 此实验申请地址在这里&#xff0c;时间为60分钟。 本实验也是DB Security Adva…

String AOP

AOP AOP(Aspect Object programmar) 面向切面编程&#xff0c;它是对某一类问题的统一处理&#xff0c;而StringAOP就是AOP思想的一种具体实现就像Ioc和DI。 AOP组成 切面(Aspect) 切⾯&#xff08;Aspect&#xff09;由切点&#xff08;Pointcut&#xff09;和通知&#x…

论文阅读笔记《Grounded Action Transformation for Robot Learning in Simulation》

Grounded Action Transformation for Robot Learning in Simulation 发表于AAAI 2017 仿真机器人学习中的接地动作变换 Hanna J, Stone P. Grounded action transformation for robot learning in simulation[C]//Proceedings of the AAAI Conference on Artificial Intellig…

Linux中的阻塞机制

我们知道在字符设备驱动中&#xff0c;应用层调用read、write等系统调用终会调到驱动中对应的接口。 可以当应用层调用read要去读硬件的数据时&#xff0c;硬件的数据未准备好&#xff0c;那我们该怎么做&#xff1f; 一种办法是直接返回并报错&#xff0c;但是这样应用层要获得…

linux通配符和正则表达式深层解析...

目录&#xff1a; (一)了解通配符和正则的作用 (二)通配符的使用 (三)正则表达式的使用 (四)扩展正则表达式的使用 (一)了解通配符和正则的作用 (1.1)在我们日常的工作中&#xff0c;我们都会使用到通配符或者正则表达式。通配符是一种特殊语句&#xff0c;主要有星号(*)和问号…

交换机和路由器到底有什么区别???

我&#xff1a;度娘度娘&#xff0c;交换机和路由器的区别是什么呢&#xff1f; 度娘&#xff1a;一个工作在第二层数据链路层&#xff0c;一个工作在第三层网络层。 我&#xff1a;哈&#xff1f;那工作在不同层会有什么区别&#xff1f;为什么要工作在不同层&#xff1f; …

2023五一数学建模A题完整思路

已更新五一数学建模A题思路&#xff0c;文章末尾获取&#xff01; A题完整思路&#xff1a; A题是一个动力学问题&#xff0c;需要我们将物理学概念运用到实际生活中&#xff0c;我们可以先看题目 问题1&#xff1a; 假设无人机以平行于水平面的方式飞行&#xff0c;在空中投…

Windows11安装sqlserver2012失败后解决方案

首先卸载 WinR打开运行输入services.msc查看所有服务/或者我的电脑管理找到服务列表/任务管理器进入服务列表&#xff0c;停止所有与Sql Server有关的服务&#xff0c;如下&#xff1a; 打开控制面板-卸载sqlserver所有相关软件&#xff1b; 删除SQL Server相关注册表&#…

【观察】中国软件行业进入“重构期”,看浪潮海岳如何“开新局”

众所周知&#xff0c;改开四十多来年&#xff0c;中国软件产业在经历了萌芽与低谷、摸索与转型后&#xff0c;逐步进入了快速发展期。特别是过去几年&#xff0c;在新的发展格局&#xff0c;信创替代的进程中&#xff0c;整个中国软件业更是加速进入了全新的“重构期”。 在此过…

Unity API详解——Quaternion类

Quaternion类又称四元数&#xff0c;由x、y、z和w这4个分量组成&#xff0c;属于struct类型。在Unity中&#xff0c;用Quaternion来存储和表示对象的旋转角度。Quaternion的变换比较复杂&#xff0c;对于GameObject一般的旋转及移动&#xff0c;可以用Transform中的相关方法实现…

CH32V307环境参数在线监测系统(一)

CH32V307环境参数在线监测系统是以CH32V307VCT6为核心&#xff0c;由ESP8266模块、DHT11温湿度传感器模块、TFT LCD显示屏组成。系统实物图如下所示&#xff1a; 系统功能主要有RTC实时时钟、WIFI网络授时、DHT11温度测量、温湿度数据实时上传到onenet平台、屏幕定时刷新等功能…

在Docker上安装和运行MySQL容器(纯步骤)

在Docker上安装和运行MySQL步骤 本文章只有操作步骤&#xff0c;没有原理解释&#xff0c;只是在学习当中提醒自己安装步骤。 第一步&#xff1a;从远程仓库拉取MySQL镜像 1.从远程仓库搜索mysql镜像 docker search mysql2.pull拉取镜像 这里我选择的是mysql的5.7版本 docker…

盘点 5 个 yyds 的 AI 绘画辅助工具

国外著名的 AI 作图工具 Midjourney、Stable Diiffusion 都可以根据你输入的指令生成一张图片。 如果你想输出高质量的图片&#xff0c;需要掌握一些 prompt 指令技巧。本文章便盘点了 5 个 GitHub 上的开源项目&#xff0c;引领你更好的上手 AI 作图。 本期推荐开源项目目录&…

软件杯龙源风电赛题培训!千万分钟数据和全流程基线等你来战

‍‍ “中国软件杯”大学生软件设计大赛是一项面向中国在校学生的公益性赛事&#xff0c;大赛由国家工业和信息化部、教育部、江苏省人民政府共同主办&#xff0c;是全国软件行业规格最高、最具影响力的国家级一类赛事。其中&#xff0c;作为重点赛题的龙源风电赛&#xff0c;上…

我们分析了9.12亿篇博客文章,得出了11条内容营销发现

我们分析了 9.12 亿篇博客文章&#xff0c;想要更好地了解目前的内容营销领域。 具体来说&#xff0c;我们研究了这些文章的内容格式、字数和标题等因素与社交媒体分享和反向链接的关联性。 在我们的数据合作伙伴BuzzSumo的帮助下&#xff0c;我们有了一些非常有趣的发现。 …