阅读更多

0顶
0踩

移动开发
本文来自:http://blog.nimbledroid.com

像Java这样具有垃圾回收功能的语言的好处之一,就是程序员无需手动管理内存分配。这减少了段错误(segmentation fault)导致的闪退,也减少了内存泄漏导致的堆空间膨胀,让编写的代码更加安全。然而,Java 中依然有可能发生内存泄漏。所以你的安卓 APP 依然有可能浪费了大量的内存,甚至由于内存耗尽(OOM)导致闪退。

传统的内存泄漏是由忘记释放分配的内存导致的,而逻辑上的内存泄漏则是由于忘记在对象不再被使用的时候释放对其的引用导致的。如果一个对象仍然存在强引用,垃圾回收器就无法对其进行垃圾回收。在安卓平台,泄漏 Context 对象问题尤其严重。这是因为像 Activity 这样的 Context 对象会引用大量很占用内存的对象,例如 View 层级,以及其他的资源。如果 Context 对象发生了内存泄漏,那它引用的所有对象都被泄漏了。安卓设备大多内存有限,如果发生了大量这样的内存泄漏,那内存将很快耗尽。

如果一个对象的合理生命周期没有清晰的定义,那判断逻辑上的内存泄漏将是一个见仁见智的问题。幸运的是,activity 有清晰的生命周期定义,使得我们可以很明确地判断 activity 对象是否被内存泄漏。onDestroy() 函数将在 activity 被销毁时调用,无论是程序员主动销毁 activity,还是系统为了回收内存而将其销毁。如果 onDestroy 执行完毕之后,activity 对象仍被 heap root 强引用,那垃圾回收器就无法将其回收。所以我们可以把生命周期结束之后仍被引用的 activity 定义为被泄漏的 activity。

Activity 是非常重量级的对象,所以我们应该极力避免妨碍系统对其进行回收。然而有多种方式会让我们无意间就泄露了 activity 对象。我们把可能导致 activity 泄漏的情况分为两类,一类是使用了进程全局(process-global)的静态变量,无论 APP 处于什么状态,都会一直存在,它们持有了对 activity 的强引用进而导致内存泄漏,另一类是生命周期长于 activity 的线程,它们忘记释放对 activity 的强引用进而导致内存泄漏。下面我们就来详细分析一下这些可能导致 activity 泄漏的情况。

1.静态 Activity

泄漏 activity 最简单的方法就是在 activity 类中定义一个 static 变量,并且将其指向一个运行中的 activity 实例。如果在 activity 的生命周期结束之前,没有清除这个引用,那它就会泄漏了。这是因为 activity(例如 MainActivity) 的类对象是静态的,一旦加载,就会在 APP 运行时一直常驻内存,因此如果类对象不卸载,其静态成员就不会被垃圾回收。
void setStaticActivity() {
  activity = this;
}

View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
    setStaticActivity();
    nextActivity();
  }
});




内存泄漏场景 1 - Static Activity

2. 静态 View

另一种类似的情况是对经常启动的 activity 实现一个单例模式,让其常驻内存可以使它能够快速恢复状态。然而,就像前文所述,不遵循系统定义的 activity 生命周期是非常危险的,也是没必要的,所以我们应该极力避免。

但是如果我们有一个创建起来非常耗时的 View,在同一个 activity 不同的生命周期中都保持不变呢?所以让我们为它实现一个单例模式,就像这段代码。现在一旦 activity 被销毁,那我们就应该释放大部分的内存了。
void setStaticView() {
  view = findViewById(R.id.sv_button);
}

View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
    setStaticView();
    nextActivity();
  }
});




内存泄漏场景 2 - Static View

内存泄漏了!因为一旦 view 被加入到界面中,它就会持有 context 的强引用,也就是我们的 activity。由于我们通过一个静态成员引用了这个 view,所以我们也就引用了 activity,因此 activity 就发生了泄漏。所以一定不要把加载的 view 赋值给静态变量,如果你真的需要,那一定要确保在 activity 销毁之前将其从 view 层级中移除。

3. 内部类

现在让我们在 activity 内部定义一个类,也就是内部类。这样做的原因有很多,比如增加封装性和可读性。如果我们创建了一个内部类的对象,并且通过静态变量持有了 activity 的引用,那也会发生 activity 泄漏。
void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
}

View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createInnerClass();
        nextActivity();
    }
});




内存泄漏场景 3 - Inner Class

不幸的是,内部类能够引用外部类的成员这一优势,就是通过持有外部类的引用来实现的,而这正是 activity 泄漏的原因。

4. 匿名类

类似的,匿名类同样会持有定义它们的对象的引用。因此如果在 activity 内定义了一个匿名的 AsyncTask 对象,就有可能发生内存泄漏了。如果 activity 被销毁之后 AsyncTask 仍然在执行,那就会组织垃圾回收器回收 activity 对象,进而导致内存泄漏,直到执行结束才能回收 activity。
void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        startAsyncTask();
        nextActivity();
    }
});




内存泄漏场景 4 - AsyncTask

5. Handlers

同样的,定义一个匿名的 Runnable 对象并将其提交到 Handler 上也可能导致 activity 泄漏。Runnable 对象间接地引用了定义它的 activity 对象,而它会被提交到 Handler 的 MessageQueue 中,如果它在 activity 销毁时还没有被处理,那就会导致 activity 泄漏了。
void createHandler() {
    new Handler() {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }.postDelayed(new Runnable() {
        @Override public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}


View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createHandler();
        nextActivity();
    }
});




内存泄漏场景 5 - Handler

6. Threads

同样的,使用Thread和TimerTask也可能导致activity泄漏。
void spawnThread() {
    new Thread() {
        @Override public void run() {
            while(true);
        }
    }.start();
}

View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
      spawnThread();
      nextActivity();
  }
});




内存泄漏场景 6 - Thread

7. Timer Tasks

只要它们是通过匿名类创建的,尽管它们在单独的线程被执行,它们也会持有对 activity 的强引用,进而导致内存泄漏。
void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}

View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        scheduleTimer();
        nextActivity();
    }
});




内存泄漏场景 7 - TimerTask

8. Sensor Manager

最后,系统服务可以通过 context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果 context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。
void registerListener() {
       SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
       Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
       sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        registerListener();
        nextActivity();
    }
});




内存泄漏场景 8 - Sensor Manager

现在,我们展示了八种很容易不经意间就泄漏大量内存的情景。请记住,最坏的情况下,你的 APP 可能会由于大量的内存泄漏而内存耗尽,进而闪退,但它并不总是这样。相反,内存泄漏会消耗大量的内存,但却不至于内存耗尽,这时,APP 会由于内存不够分配而频繁进行垃圾回收。垃圾回收是非常耗时的操作,会导致严重的卡顿。在 activity 内部创建对象时,一定要格外小心,并且要经常测试是否存在内存泄漏。
  • 大小: 73.6 KB
  • 大小: 106.7 KB
  • 大小: 90.4 KB
  • 大小: 74.6 KB
  • 大小: 125.7 KB
  • 大小: 74.6 KB
  • 大小: 74.6 KB
  • 大小: 74.9 KB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 常见的八种导致 APP 内存泄漏的问题1

    1. 静态 Activity 3. 内部类 4. 匿名类

  • 常见的八种导致 APP 内存泄漏的问题(上)

    这减少了段错误(segmentation fault)导致的闪退,也减少了内存泄漏导致的堆空间膨胀,让编写的代码更加安全。然而,Java 中依然有可能发生内存泄漏。所以你的安卓 APP 依然有可能浪费了大量的内存,甚至由于内存...

  • 常见的八种导致 APP 内存泄漏的问题(下)

    Runnable 对象间接地引用了定义它的activity 对象,而它会被提交到 Handler 的 MessageQueue 中,如果它在 activity 销毁时还没有被处理,那就会导致 activity 泄漏了。 Threads 同样的,使...

  • Android常见的八种导致 APP 内存泄漏的问题

    这减少了段错误(segmentation fault)导致的闪退,也减少了内存泄漏导致的堆空间膨胀,让编写的代码更加安全。然而,Java 中依然有可能发生内存泄漏。所以你的安卓 APP 依然有可能浪费了大量的内存,甚至由于内存...

  • MATLAB实现参数化重采样时频变换(PRTF)用于振动与声音信号故障诊断

    内容概要:本文详细介绍了参数化重采样时频变换(PRTF)在振动与声音信号故障诊断中的应用。首先通过仿真信号展示PRTF的基本原理,即通过非线性时间轴映射提高时频分辨率,特别是在处理非平稳信号时的优势。接着讨论了PRTF的具体实现步骤,包括重采样、时频分析、坐标系转换等关键技术点。文中还提供了多个实际案例,如齿轮箱故障诊断、压缩机阀片断裂检测、蝙蝠回声定位信号处理等,展示了PRTF在不同应用场景中的灵活性和有效性。此外,文章分享了一些实用经验和技巧,如窗函数选择、抗混叠滤波、多尺度融合等,帮助读者更好地理解和应用PRTF。 适合人群:具备一定MATLAB编程基础和技术背景的信号处理工程师、研究人员。 使用场景及目标:适用于处理非平稳信号,尤其是振动和声音信号的故障诊断。目标是提高时频分辨率,清晰呈现故障特征,从而快速准确定位故障源。同时,也为研究者提供了一种新的信号处理工具,拓展了传统时频分析方法的应用范围。 其他说明:PRTF虽然强大,但在某些情况下并非最佳选择,如处理稳态信号或需要极高频率分辨率的任务。因此,使用者应根据具体情况选择合适的工具。此外,由于PRTF计算量较大,实时性要求较高的场景需考虑硬件加速或其他优化手段。

  • 毕设课设-基于MATLAB的汽车出入库识别系统.zip

    基于MATLAB的汽车出入库识别系统是一份适用于毕业设计或课程设计的项目,它主要围绕车辆进出仓库的自动识别技术开发。该系统充分利用MATLAB这一强大的数学计算和图形处理软件,实现了汽车识别的核心功能。 项目主要包括以下几个关键部分: 1. **图像采集与预处理**:通过摄像头或传感器捕捉汽车的实时图像,对图像进行预处理,如灰度化、边缘检测或特征提取,提高后续识别的精度。 2. **目标检测与识别**:利用MATLAB的机器视觉工具箱,可能采用了模板匹配、特征点匹配(如SIFT、SURF或HOG)、或者现代的深度学习技术(如卷积神经网络CNN),来识别出汽车的特征。 3. **车牌识别**:针对汽车的车牌进行识别,这通常涉及到字符分割、识别和验证,可能结合了OCR(Optical Character Recognition)技术。 4. **数据分析与管理系统**:收集并分析出入库数据,用于优化仓库管理策略,如实时流量监控、车辆调度等。 5. **文档与代码完整性**:项目不仅提供了完整的工作流程和算法实现,还包含了详尽的README.md文档,以便使用者了解项目的结构和使用方法,以及注意事项。 这个系统的优势在于将理论知识应用到实际场景中,既锻炼了学生的编程能力,也展示了MATLAB在计算机视觉领域的实用性。通过下载和交流,有助于参与者提升自己的技术能力,并推动自动化仓储系统的研发和优化。

  • (源码)基于51单片机的密码锁控制器.zip

    # 基于51单片机的密码锁控制器 ## 项目简介 本项目是一个基于51单片机的密码锁控制器,通过结合LCD显示器和键盘,实现了一个简单的密码输入与验证系统。该系统可以用于需要密码保护的应用场景,如门禁系统、保险箱等。用户可以通过键盘输入密码,系统会根据输入的密码进行验证,并通过LED灯显示验证结果。 ## 项目的主要特性和功能 1. LCD显示功能使用LCD显示器实时显示密码输入的相关信息。 2. 密码设置与修改用户可以设置和修改一个4位数字(09)的密码。 3. 超级用户密码系统内置一个超级用户密码“1234”,用于特殊权限操作。 4. 密码验证反馈密码输入正确时,系统会亮绿灯密码输入错误时,系统会亮红灯。 ## 安装使用步骤 ### 前提条件 假设用户已经下载了本项目的源码文件,并具备基本的单片机开发环境(如Keil等)。 ### 步骤 1. 解压源码文件将下载的源码文件解压到本地目录。

  • (源码)基于Python和强化学习算法的智能体训练系统.zip

    # 基于Python和强化学习算法的智能体训练系统 ## 项目简介 本项目是一个基于Python和强化学习算法的智能体训练系统,旨在通过深度学习和策略优化技术,训练智能体在复杂环境中进行决策和行动。项目结合了多种强化学习算法,如TRPO(Trust Region Policy Optimization),并使用了如Pommerman这样的复杂环境进行训练和评估。 ## 项目的主要特性和功能 强化学习算法包括TRPO在内的多种强化学习算法,适用于连续动作空间的强化学习任务。 环境模拟使用Pommerman环境进行智能体的训练和评估,环境包含复杂的棋盘布局和动态变化的炸弹、火焰等元素。 预训练与微调支持预训练模型的加载和微调,加速训练过程。 多模型评估支持多个模型的同时评估,比较不同模型在相同环境下的表现。 状态抽象与特征提取通过状态抽象和特征提取,优化智能体的决策过程。

  • 制造业2022年制造业上市公司高质量发展:城市群与主要城市百强企业分布分析

    内容概要:本文档展示了2022年中国制造业上市公司百强企业在不同城市群和城市的分布情况。从城市群角度看,百强企业主要集中在长三角(19家)、粤港澳(16家)和京津冀(11家)三大国家级城市群,这些地区凭借强大的发展基础、完善的产业链和优越的营商环境成为制造业高质量发展的领头羊。从具体城市分布来看,深圳和北京各有10家企业上榜,上海有9家。其中,深圳以比亚迪、中兴等大企业为代表,在营收规模上位居全国第一;北京依托科技和人才优势支持企业发展;上海则在高端制造业特别是集成电路领域处于领先地位。 适合人群:对中国经济地理、制造业发展趋势感兴趣的读者,以及从事相关行业研究的专业人士。 使用场景及目标:①了解中国制造业区域布局和发展趋势;②为政策制定者提供参考依据;③为企业投资决策提供数据支持。 阅读建议:建议重点关注各城市群和城市的具体数据,结合当地产业特色和发展优势进行分析,以便更好地理解中国制造业的空间分布规律及其背后的原因。

  • 房地产营销策划 -湖南涟源博盛生态园年度营销方案.pptx

    房地产营销策划 -湖南涟源博盛生态园年度营销方案.pptx

  • 基于粒子群算法PSO的宽带消色差超透镜Matlab设计与FDTD仿真

    内容概要:本文详细介绍了利用粒子群算法(PSO)在Matlab中设计宽带消色差超透镜的方法及其FDTD仿真验证。首先,通过定义合理的初始参数范围和适应度函数,将超透镜的纳米结构参数(如纳米柱的直径、高度、周期)作为粒子的位置,采用PSO进行优化。适应度函数结合了预存的相位延迟查找表和实时FDTD仿真结果,确保优化过程中能够高效评估不同结构参数的效果。文中还讨论了惯性权重的动态调整、震荡因子的引入以及适应度函数中物理约束的添加,以提高优化效果并防止陷入局部最优。最终,通过FDTD仿真验证优化结果,展示了在可见光波段内的聚焦效率和焦斑尺寸的改进。 适合人群:从事光学设计、超材料研究、电磁仿真领域的科研人员和技术开发者。 使用场景及目标:适用于需要设计高性能宽带消色差超透镜的研究项目,旨在通过粒子群算法优化超透镜结构参数,减少色差并提高聚焦效率。 其他说明:文中提供了详细的Matlab代码片段和FDTD仿真设置示例,帮助读者更好地理解和实施该方法。此外,强调了在实际应用中需要注意的参数选择和物理约束,以确保设计方案的可行性和有效性。

  • FLAC 3D中深基坑支护结构(冠梁+钢支撑+钻孔灌注桩)的数值模拟及优化技巧

    内容概要:本文详细介绍了利用FLAC 3D软件进行深基坑支护结构的数值模拟方法,特别是针对冠梁、钢支撑和钻孔灌注桩的组合支护结构。文章首先解释了钻孔灌注桩的建模要点,强调了桩土接触面参数设置的重要性。接着讨论了钢支撑的激活时机及其对支护系统的影响,指出合理的开挖步控制可以更好地模拟实际情况。对于冠梁,则着重于其与桩顶的正确耦合方式以及弯矩分布的监测。此外,还分享了一些实用的经验教训和技术细节,如避免常见的建模错误、优化参数选择等。 适合人群:从事岩土工程、地下结构设计的专业人士,尤其是有一定FLAC 3D使用经验的研究人员和工程师。 使用场景及目标:适用于需要精确模拟深基坑开挖过程中支护结构行为的工程项目,旨在提高数值模拟的准确性,为实际施工提供科学依据和支持。 其他说明:文中提供了大量具体的FLAC 3D命令示例和实践经验,有助于读者快速掌握相关技能并在实践中灵活运用。同时提醒读者关注模型验证的重要性,确保模拟结果能够真实反映工程实际状况。

  • 前端铺子开发者 前端杂货铺 小程序在线课堂+工具组件小程序uniapp移动端.zip

    前端铺子开发者 前端杂货铺 小程序在线课堂+工具组件小程序uniapp移动端

  • Delphi 12.3控件之geniso(CD iso Generator)可启动光盘文件制作器可执行文件.zip

    Delphi 12.3控件之geniso(CD iso Generator)可启动光盘文件制作器可执行文件.zip

  • (源码)基于Arduino的传感器应用项目.zip

    # 基于Arduino的传感器应用项目 ## 项目简介 这是一个基于Arduino开发的项目集合,主要聚焦于传感器应用及相关开发。通过此项目,您将能够了解并实践如何使用Arduino进行硬件编程,以实现对各种传感器的读取和控制。 ## 项目的主要特性和功能 ### 1. 传感器读取 此项目包含多个示例,可以读取不同类型的传感器数据,如温度、湿度、光线、压力等。 ### 2. 实时数据反馈 通过Arduino,项目能够实现实时读取传感器的数据并在某些媒介(如LED灯、LCD显示屏等)上进行反馈。 ### 3. 自动化控制 根据项目需求,可以实现基于传感器数据的自动化控制,例如自动开关灯光、调节风扇速度等。 ## 安装使用步骤 ### 1. 下载源码文件 ### 2. 安装Arduino IDE 确保您的计算机上安装了Arduino IDE,这是编写和上传代码到Arduino设备所必需的。 ### 3. 导入项目文件

  • 房地产活动策划 -2025商业地产脆皮打工人春日养生局(万物回春主题)活动策划方案.pptx

    房地产活动策划 -2025商业地产脆皮打工人春日养生局(万物回春主题)活动策划方案.pptx

  • h5py-3.1.0-cp37-cp37m-manylinux1_x86_64.whl

    该资源为h5py-3.1.0-cp37-cp37m-manylinux1_x86_64.whl,欢迎下载使用哦!

  • 基于Comsol仿真的远场涡流检测技术及其在工业探伤中的应用研究

    内容概要:本文详细介绍了利用Comsol软件进行远场涡流检测仿真的方法和技术要点。首先构建了一个二维轴对称模型,模拟了线圈和含缺陷铁磁管道之间的相互作用。文中强调了空气域大小、材料参数设置以及频率选择对检测效果的重要影响。通过调整不同的仿真参数如频率、线圈位置等,探讨了它们对磁场强度和相位变化的影响规律。此外,还分享了一些提高仿真效率的经验,例如合理的网格划分策略和参数化扫描的应用。最后指出远场涡流检测在工业探伤领域的潜在价值,特别是在检测埋地管道内部缺陷方面的优势。 适合人群:从事无损检测、电磁场仿真等相关工作的科研人员和技术工程师。 使用场景及目标:适用于希望深入了解远场涡流检测原理并掌握具体实施步骤的研究者;旨在为实际工程项目提供理论支持和技术指导。 其他说明:文中提供了大量实用的操作技巧和注意事项,有助于读者快速上手并在实践中优化自己的仿真流程。

  • (源码)基于STM32F10x微控制器的综合驱动库.zip

    # 基于STM32F10x微控制器的综合驱动库 ## 项目简介 本项目是一个基于STM32F10x系列微控制器的综合驱动库,旨在为开发者提供一套全面、易于使用的API,用于快速搭建和配置硬件资源,实现高效、稳定的系统功能。项目包含了STM32F10x系列微控制器的基本驱动和常用外设(如GPIO、SPI、Timer、RTC、ADC、CAN、DMA等)的驱动程序。 ## 项目的主要特性和功能 1. 丰富的外设驱动支持支持GPIO、SPI、Timer、RTC、ADC、CAN、DMA等外设的初始化、配置、读写操作和中断处理。 2. 易于使用的API接口提供统一的API接口,简化外设操作和配置,使开发者能够专注于应用程序逻辑开发。 3. 全面的时钟管理功能支持系统时钟、AHB时钟、APB时钟的生成和配置,以及时钟源的选择和配置。 4. 电源管理功能支持低功耗模式、电源检测和备份寄存器访问,帮助实现节能和延长电池寿命。

Global site tag (gtag.js) - Google Analytics