Android 内存原理详解以及优化(二)

上一篇讲了内存原理,如果还没看可以先看上一篇:Android 内存原理详解以及优化(一)
这一篇我总结一下我们经常遇到的内存优化问题:
1.内存抖动
自定义view的ondraw是会被频繁调用的,那在这个方法里面就不能频繁的new object 对象频繁的创建和回收就会内存抖动,凡是频繁调用的方法,都禁止类似这样的操作。
2.内存泄露
内存泄露的本质是某个对象已经不需要再用了,但是它却没有被系统所回收,一直在内存中占用着空间,而导致它无法被回收的原因大多是由于它被一个生命周期更长的对象所引用,用什么引用?引用链(在上一篇,gc内存的垃圾回收机制 使用的是GCRoot 可达性分析有解释)
举个例子,如果一个 Activity 被一个单例对象所引用,那么当退出这个 Activity 时,由于单例的对象依然存在(单例对象的生命周期跟整个 App 的生命周期一致),而单例对象又持有 Activity 的引用,这就导致了此 Activity 无法被回收,从而造成内存泄漏。

知道了内存泄漏的根本原因,再分析为什么会出现内存泄漏就很简单了,下面就针对一些常见的内存泄漏进行分析。
单例造成的内存泄漏

刚才已经分析过了,假设有一个单例是这样的
public class SingleTon {

private static SingleTon singleTon;

private Context context;

private SingleTon(Context context) {
    this.context = context;
}

public static SingleTon getInstance(Context context) {
    if (singleTon == null) {
        synchronized (SingleTon.class) {
            if (singleTon == null) {
                singleTon = new SingleTon(context);
            }
        }
    }
    return singleTon;
}

}

这是单例模式饿汉式的双重校验锁的写法,这里的 singleTon 持有 Context 对象,如果 Activity 中调用 getInstance 方法并传入 this 时,singleTon 就持有了此 Activity 的引用,当退出 Activity 时,Activity 就无法回收,造成内存泄漏,所以应该修改它的构造方法
private SingleTon(Context context) {
this.context = context.getApplicationContext();
}

通过 getApplicationContext 来获取 Application 的 Context,让它被单例持有,这样退出 Activity 时,Activity 对象就能正常被回收了,而 Application 的 Context 的生命周期和单例的生命周期是一致的,所有再整个 App 运行过程中都不会造成内存泄漏。
非静态内部类造成的内存泄漏

我们知道,非静态内部类会持有外部类的引用,如果这个非静态的内部类的生命周期比它的外部类的生命周期长,那么当销毁外部类的时候,它无法被回收,就会造成内存泄漏。
外部类中持有非静态内部类的静态对象

假设 Activity 的代码是这样的
public class MainActivity extends AppCompatActivity {

private static Test test;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (test == null) {
        test = new Test();
    }

}

private class Test {

}

}

这个其实和单例的原理是一样的,由于静态对象 test 的生命周期和整个应用的生命周期一致,而非静态内部类 Test 持有外部类 MainActivity 的引用,导致 MainActivity 退出的时候不能被回收,从而造成内存泄漏,解决的方法也很简单,把 test 改成非静态,这样 test 的生命周期和 MainActivity 是一样的了,就避免了内存泄漏。或者也可以把 Test 改成静态内部类,让 test 不持有 MainActivity 的引用,不过一般没有这种操作。

Handler 或 Runnable 作为非静态内部类

handler 和 runnable 都有定时器的功能,当它们作为非静态内部类的时候,同样会持有外部类的引用,如果它们的内部有延迟操作,在延迟操作还没有发生的时候,销毁了外部类,那么外部类对象无法回收,从而造成内存泄漏,假设 Activity 的代码如下
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            
        }
    }, 10 * 1000);
}

}

上面的代码中,Handler 和 Runnable 作为匿名内部类,都会持有 MainActivity 的引用,而它们内部有一个 10 秒钟的定时器,如果在打开 MainActivity 的 10 秒内关闭了 MainActivity,那么由于 Handler 和 Runnable 的生命周期比 MainActivity 长,会导致 MainActivity 无法被回收,从而造成内存泄漏。

那么应该如何避免内存泄漏呢?这里的一般套路就是把 Handler 和 Runnable 定义为静态内部类,这样它们就不再持有 MainActivity 的引用了,从而避免了内存泄漏
public class MainActivity extends AppCompatActivity {

private Handler handler;

private Runnable runnable;

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

    handler = new TestHandler();
    runnable = new TestRunnable();
    handler.postDelayed(runnable, 10 * 1000);
}

private static class TestHandler extends Handler {

}

private static class TestRunnable implements Runnable {
    @Override
    public void run() {
        Log.d(TAG, "run: ");
    }
}

private static final String TAG = "MainActivity";

}

还要在 onDestory 调用 handler 的 removeCallbacks 方法来移除 Message,因为如果在退出 Activity 关闭后,正好触发执行 run 方法,就也会造成message持有activity的引用,内存泄露
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacks(runnable);
}

还有一种特殊情况,如果 Handler 或者 Runnable 中持有 Context 对象,那么即使使用静态内部类,还是会发生内存泄漏
public class MainActivity extends AppCompatActivity {

private Handler handler;

private Runnable runnable;

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

    handler = new TestHandler(this);
    runnable = new TestRunnable();
    handler.postDelayed(runnable, 10 * 1000);
}

private static class TestHandler extends Handler {
    private Context context;
    private TestHandler(Context context) {
        this.context = context;
    }
}

private static class TestRunnable implements Runnable {
    @Override
    public void run() {
        Log.d(TAG, "run: ");
    }
}

private static final String TAG = "MainActivity";

}

上面的代码,使用 leakcanary 工具会发现依然会发生内存泄漏,而且造成内存泄漏的原因和之前用非静态内部类是一样的,那么为什么会出现这种情况呢?

这是由于在 Handler 中持有 Context 对象,而这个 Context 对象是通过 TestHandler 的构造方法传入的,它是一个 MainActivity 对象,也就是说,虽然 TestHandler 作为静态内部类不会持有外部类 MainActivity 的引用,但是我们在调用它的构造方法时,自己传入了 MainActivity 的对象,从而 handler 对象持有了 MainActivity 的引用,handler 的生命周期比 MainActivity 的生命周期长,因此会造成内存泄漏,这种情况可以使用弱引用的方式来引用 Context 来避免内存泄漏,代码如下
public class MainActivity extends AppCompatActivity {

private Handler handler;

private Runnable runnable;

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

    handler = new TestHandler(new WeakReference<Context>(this));
    runnable = new TestRunnable();
    handler.postDelayed(runnable, 10 * 1000);
}

private static class TestHandler extends Handler {
    private Context context;
    private TestHandler(WeakReference<Context> weakContext) {
        context = weakContext.get();
    }
}

private static class TestRunnable implements Runnable {
    @Override
    public void run() {
        Log.d(TAG, "run: ");
    }
}

private static final String TAG = "MainActivity";

}

其他的内存泄漏情况

还有一些其他的会导致内存泄漏的情况,比如 BraodcastReceiver 未取消注册,InputStream 未关闭等,这类内存泄漏非常简单,只要在平时写代码时多多注意即可避免。
处理内存泄露当然不能没有 leakCanary这个工具,它的原理,我也分三篇来分析了,敢兴趣可以看看:leakcanary源码详解

3.内存溢出
3.1 对于大图片加载的时候,会内存溢出。这是我开发中遇到的,如果泛指的话,可以归结于为打对象分配内存 加载大图片bitmap内存溢出
3.2 内存泄露逐渐积累也会内存溢出。
上文中说了内存泄露的处理,在此不再赘述。
3.3 jni native 内存地址分配的,这个是听说,待验证。印象中是有的。

4.扩大内存的手段
4.1一个应用如果使用了largeHeap,会请求系统为Dalvik虚拟机分配更大的内存空间。使用起来也很方便,只需在manifest文件application节点加入android:largeHeap=“true” 默认128M ,设置后能分配的多大,取决于系统限制和设备硬件情况。注意不用用这个方法去解决oom,你走偏了,走正道。
4.2 应用开多进程,这样一个进程分配128M,多个能翻倍使用。
在 AndroidManifest.xml 中配置 android:process:

第一种:如 android:process = “:remote”,以 : 开始,后面的字符串是可以随意指定的。如果包名是 com.cah.androidtest,所以实际进程名是 
com.cah.androidtest:remote。这种设置形式表示该进程为当前应用的私有进程,其他应用的组件不可以和它跑在同一进程中

第二种:如 android:process = “com.cah.androidtest.remote”,以小写字母开头,表示运行在一个以这个名字命名的全局进程中,其他应用的组件
可以和它跑在同一进程中(使用 SharedUID,且签名一致),从而减少资源的占用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

A股本周在3000点以下继续筑底,本周依然继续探底?

夜已深&#xff0c;市场传来了3个浓烈的消息&#xff0c;炸锅了&#xff0c;恐有大事发生&#xff0c;马上告诉所有人&#xff1a; 消息面&#xff1a; 1、中国经济周刊首席评论员钮文新称&#xff1a;不要等中小投资者都彻底希望&#xff0c;销户离场了&#xff0c;才发现该…

新恒汇过会一年多注册仍遥遥无期,实控人大额负债入股资金靠借款

《港湾商业观察》施子夫 自2022年6月递表深交所创业板获受理&#xff0c;新恒汇电子股份有限公司 &#xff08;以下简称&#xff0c;新恒汇&#xff09;的上市之路无疑颇显诸多坎坷。2022年7月&#xff0c;深交所下发第一轮审核问询函&#xff1b;同年11月&#xff0c;深交所下…

Bugly的底层是怎么实现的

Bugly 入门 首先&#xff0c;简要介绍什么是 Bugly 以及它的主要功能&#xff1a; Bugly 是什么&#xff1a; Bugly 是腾讯提供的一款移动应用质量监控工具&#xff0c;主要用于捕捉应用的崩溃、ANR&#xff08;应用无响应&#xff09;、卡顿和错误日志。 主要功能&#xff1…

微型导轨如何提升数控机床的稳定性?

数控机床是加工设备中常用的机床&#xff0c;精度和稳定性是衡量数控机床性能的重要指标。而微型导轨作为数控机床中重要的传动元件&#xff0c;数控机床与其具体结构性能是密不可分的&#xff0c;那么微型导轨如何提高数控机床的稳定性呢&#xff1f; 1、微型导轨通过采用先进…

【见刊通知】MVIPIT 2023机器视觉、图像处理与影像技术国际会议

MVIPIT 2023&#xff1a;https://ieeexplore.ieee.org/xpl/conhome/10578343/proceeding 入库Ei数据库需等20-50天左右 第二届会议征稿启动&#xff08;MVIPIT 2024&#xff09; The 2nd International Conference on Machine Vision, Image Processing & Imaging Techn…

暑假提升(3)[平衡二叉树之二--红黑树]

命为志存。 —— 朱熹 红黑树RBTree 1、诞生原因2、红黑树的概念3、红黑树的性质4、红黑树的设计4、1、节点设计4、2、插入操作的设计 5、总结 1、诞生原因 由于二叉树的局限性&#xff0c;进一步出现平衡二叉树&#xff0c;来帮助我们来进一步提升我们对数据的处理&#xff0…

【LabVIEW学习篇 - 1】:初始LabVIEW

文章目录 初始LabView前面板和程序框图前面板&#xff08;Front Panel&#xff09;程序框图&#xff08;Block Diagram&#xff09;交互和工作流程 练手小案例&#xff1a;LabView中实现加法操作 初始LabView LabVIEW&#xff08;Laboratory Virtual Instrument Engineering W…

数据要素资产化路径

一、数据治理&#xff1a;包括数据规范管理、数据治理管理、元数据管理、数据架构管理。 二、数据资产运营&#xff1a;包括数据目录视图、数据全生命周期、数据资产估值、数据资产定价、数据交易流通。 方向1&#xff1a;产业数字化&#xff08;难度系数&#xff1a;*&#…

出现d3dcompiler_43.dll缺失我们要怎么修复?教你科学修复d3dcompiler_43.dll

出现d3dcompiler_43.dll缺失其实也算是一种比较常见的dll文件丢失&#xff0c;毕竟现在很多在使用电脑的时候&#xff0c;都会胡乱的下载东西&#xff0c;然后导致电脑中毒&#xff0c;感染到d3dcompiler_43.dll文件&#xff0c;而导致d3dcompiler_43.dll文件被损坏&#xff0c…

docker安装oracle 11g

最近把一些常用数据库都移到docker了&#xff0c;而且是windows下&#xff0c;很是方便。偶尔还是要用一下Oracle&#xff0c;今天就试一下安装oracle 11g 在docker上。 一、搜索并拉取镜像 docker search oracle_11gdocker pull iatebes/oracle_11g二、运行容器和测试连接 …

微信小程序开发-003-首页(轮播图,状态栏,导航栏)

哈喽小伙伴们大家好,我是程序媛小李,今天,我们继续来开发微信小程序. 在这里,先贴上首页的效果图: 整个页面大概可以分为顶部的状态栏区域,轮播图区域,公司信息区域,商品导航区域,商品推荐区域,以及最下面的导航栏区域. 一,底部导航栏 在这里,我们遵循从外到内的原则,我们先来…

小白·使用Tesseract-OCR工具读取图片

1、直接pip安装 工具使用vscode和pycharm都可以。 这里介绍使用vscode的方法。 (1)、调出终端 (2)、安装依赖 (3)、编写代码 import pyocr import pyocr.builders from PIL import Image import re# 获取Tesseract-OCR工具 tools pyocr.get_available_tools() tool tools[…

数据融合工具(3)国家基本比例尺地形图分幅计算

情景再现&#xff0c;呼叫小编 数据获取和使用过程中&#xff0c;经常听到一个名词“分幅图幅号”…… 你的数据是按多大比例尺分幅的&#xff1f;我不知道&#xff0c;就一些字母和数值。 你把G47E018018范围内的数据裁剪提供&#xff0c;这个范围是啥&#xff1f; 你把镶嵌…

Android14之获取包名/类名/服务名(二百二十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

常见的自动化工具开发必备的源代码!

随着科技的飞速发展&#xff0c;自动化工具已经成为我们日常工作中不可或缺的一部分&#xff0c;自动化工具不仅极大地提高了工作效率&#xff0c;还降低了人为错误的可能性。 然而&#xff0c;要想开发出高效、稳定的自动化工具&#xff0c;掌握一些常见的源代码技巧是至关重…

新手入门:无服务器函数和FaaS简介

无服务器&#xff08;Serverless&#xff09;架构的价值在于其成本效益、弹性和扩展性、简化的开发和部署流程、高可用性和可靠性以及使开发者能够专注于业务逻辑。通过自动化资源调配和按需计费&#xff0c;无服务器架构能够降低成本并适应流量变化&#xff0c;同时简化开发流…

【版面费优惠丨ACM独立出版丨接受全文摘要投稿】2024年生物医药和智能技术国际学术会议(ICBIT 2024,8月23-25)

“2024年生物医药和智能技术国际学术会议&#xff08;ICBIT 2024&#xff09;”拟定于2024年8月23-25日于珠海召开。近年来&#xff0c;智能技术已经逐渐走入生物医药领域&#xff0c;并在与生物医药领域的融合创新中凸显出巨大的发展潜力和社会价值。人工智能技术在生物医药领…

工业电脑一体机在高清视频处理中的应用

工业电脑一体机在高清视频处理中的应用广泛&#xff0c;尤其是在需要高性能计算、稳定性和实时处理能力的场景中。以下是工业电脑一体机在高清视频处理中的具体应用&#xff1a; 视频监控与分析&#xff1a; 工业电脑一体机能够处理多个高清视频流&#xff0c;实现实时监控&a…

Stable-diffusion 4.8大模型与Lora

SD大模型与Lora、生成这些图片提示词。下载地址如下。 地址链接&#xff1a;https://pan.baidu.com/s/1rJaH7VvyiBYas9zopj-pFA?pwdzgma 提取码&#xff1a;zgma 一、这是SD压缩文件&#xff0c;双击后进行解压 二、解压后&#xff0c;可以看到一堆文件夹与文件&#xff0c…

【分布式系统】注册中心Zookeeper

目录 一.Zookkeeper 概述 1.Zookkeeper 定义 2.Zookkeeper 工作机制 3.Zookkeeper 特点 4.Zookkeeper 数据结构 5.Zookkeeper 应用场景 统一命名服务 统一配置管理 统一集群管理 服务器动态上下线 软负载均衡 6.Zookkeeper 选举机制 第一次启动选举机制 非第一次…