详解Android内存优化策略

目录
  • 前言
  • 一、内存优化策略
  • 二、具体优化的点
    • 1.避免内存泄漏
    • 2.Bitmap等大对象的优化策略
      • (1) 优化Bitmap分辨率
      • (2) 优化单个像素点内存
      • (3) Bitmap的缓存策略
      • (4) drawable资源选择合适的drawable文件夹存放
      • (5) 其他大对象的优化
      • (6) 避免内存抖动
    • 3.原生API回调释放内存
    • 4.内存排查工具
      • (1)LeakCanary监测内存泄漏
      • (2)通过Proflier监控内存
      • (3)通过MAT工具排查内存泄漏
  • 总结

前言

在开始之前需要先搞明白一个问题,为什么要做内存优化?或者说做内存优化的目的是什么?

一、内存优化策略

内存优化一般从两个方向着手优化,一方面就是上篇博客写的防止内存泄漏,避免不必要的内存资源浪费;另一方面就是APP中大对象的优化,减小大对象占用的内存。

二、具体优化的点

1.避免内存泄漏

这里直接看上篇博客就行:
详解Android内存泄露及优化方案

2.Bitmap等大对象的优化策略

图片加载算是内存占用的罪魁祸首,而且也是最常见的,所以优化bitmap的占用内存是很关键的。
Bitmap的内存计算公式如下:

Bitmap占用内存 = 分辨率 * 单个像素点的内存

比如说一个 1920 * 1080 的图片,它所占用的内存就是1920 * 1080 * 单个像素点内存。因此,对于Bitmap的优化就可以从分辨率和单个像素点两个方面来进行优化。

(1) 优化Bitmap分辨率

通常APP加载一张图片时候,ImageView的大小是确定的,比如一个ImageView的大小设置为 100 * 100 ,但是被加载的Bitmap的分辨率是 200 * 200,那么就可以通过采样压缩将该 ‘Bitmap' 的分辨率压缩到 ‘100 * 100'。通过这一压缩操作可以直接减少4倍的内存大小。代码如下:

val options = BitmapFactory.Options()
options.inSampleSize = 2 // 设置采样率为2,则会每两个像素点采一个像素,最终分辨率宽高变为原来的 1/2
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image, options)

(2) 优化单个像素点内存

计算机中的图像一般都是由 红、绿、蓝 三个通道加上一个透明通道组成的,因此一个像素点也是由红、绿、蓝,以及一个透明通道组成,对应到内存就是通过byte来表示,比如用2个 byte 来存储一个像素点,那么每个通道就占用 4 bit 的内存,而如果用 4 个 byte 来存储一个像素点,那么每个通道就占用 1 个byte。4 字节的像素点,相比2字节的像素点可以表示的色彩会更加丰富,因此四字节的像素点组成的图像质量也更加清晰。(一个Byte由8 bits组成,是数据存储的基础单位,1Byte又称为一个字节)

在 Android 的 Bitmap 中单个像素点占用的内存与 Bitmap 的 inPreferredConfig 参数配置有关系,代码设置如下:

     final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//只解析图片边沿,获取宽高
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        BitmapFactory.decodeFile(filePath, options);
        // 计算缩放比
        options.inSampleSize = calculateInSampleSize(options, desWidth, desHeight);
        // 完整解析图片返回bitmap
        options.inJustDecodeBounds = false;
        Bitmap bm = BitmapFactory.decodeFile(filePath, options);

options.inPreferredConfig = Bitmap.Config.RGB_565;设置的参数如下表:

Config设置 占用内存(byte) 备注
ALPH_8 1 只包含一个透明通道,透明通道占用 8bit,即 1byte
RGB_565 2 包含R/G/B三个颜色通道,不包含透明通道,三个通道占用的内存分别为5bit/6bit/5bit
ARGB_4444 2 已废弃,包含A/R/G/B四个颜色通道,每个通道占用4bit
ARGB_8888 4 24位真彩色,Android默认配置,每个通道占用 8bit
RGBA_F16 8 Android 8.0 新增,每个通道占用16bit,即两个字节

在Android系统中 Bitmap 的默认色彩模式为 ARGB_8888, 即每个像素占用了4byte,那么在默认情况下,一张分辨率为1920 * 1080 的图片,加载到内存后占用的内存大小为1920 * 1080 * 4 = 7.91M

可以通过设置 inPreferredConfig 参数来设置对应的色彩模式,例如,一个不包含透明通道的图片,我们可以将其设置为RGB_565,即保证了图片的质量,又减少了内存的占用。
此时,一张 1920 * 1080 的图片加载到内存后的内存大小为 1920 * 1080 * 2 = 3.955M,比默认情况下的内存占用减小了一半。

(3) Bitmap的缓存策略

通过缓存策略也可以一定程度上的优化内存占用问题,比如 Glide 框架中采用了三级本地缓存策略来实现Bitmap的优化,通过设置活动缓存、LRU内存缓存和本地缓存。对于相同参数的ImageView,在内存中只保存一份,以此来减少内存大小。

(4) drawable资源选择合适的drawable文件夹存放

例如我们只在 hdpi 的目录下放置了一张 100 * 100 的图片,那么根据换算关系,分辨率匹配到 xxhdpi 的手机去引用这张图片时就会被拉伸到 200*200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到 assets 或者 nodpi 的目录下。

(5) 其他大对象的优化

可以使用更加轻量级的数据结构。例如,我们可以考虑使用 ArrayMap/SparseArray 而不是 HashMap 等传统数据结构,相比起 Android 系统专门为移动操作系统编写的 ArrayMap 容器,在大多数情况下,HashMap 都显示效率低下,更占内存。另外,SparseArray更加高效在于,避免了对key与value的自动装箱,并且避免了装箱后的解箱。

(6) 避免内存抖动

内存抖动是指在短时间内突然创建大量的对象,频繁的引发GC回收,造成内存波动的情况。在开发中应该避免频繁的创建对象,来避免内存抖动。因为内存抖动会频繁触发 GC,而GC又会引起 STW 问题,直接影响程序的性能。

比如在绘制自定义View的时候一定要避免在onDraw或者onMeasure中创建对象。

3.原生API回调释放内存

Android系统提供了一些回调来通知当前应用的内存使用情况,比如下边的两个方法:

onLowMemory() 通常来说,当所有的Background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种情况下,需要尽快释放当前应用的非必须的内存资源,从而确保系统能够继续稳定运行。尤其是要释放Glide中缓存的Bitmap资源,通过调用Glide.onLowMemory方法进行资源回收。

onTrimMemory() Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,所有正在运行的应用都会收到这个回调,同时在这个回调里面会传递以下的参数,代表不同的内存使用情况,收到onTrimMemory()回调的时候,需要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面可以提高系统的整体运行流畅度,另外也可以避免自己被系统判断为优先需要杀掉的应用。例如调用Glide.onTrimMemory()来进行bitmap的回收。

4.内存排查工具

(1)LeakCanary监测内存泄漏

在debug模式下会一直开着LeakCanary来检测内存泄漏问题,根据LeanCannary提供的引用连可以快速定位到内存泄漏的位置。

(2)通过Proflier监控内存

在一个功能开发完成后可以通过Profiler来检测APP的内存使用情况。反复的打开关闭页面,然后触发GC,内存是否能够减少。

(3)通过MAT工具排查内存泄漏

MAT提供了很强大的功能,可以查看对象的深堆、浅堆的内存大小等。

总结

平时开发对于这块的关注不是很多,可能在没有出现内存不足的问题前不会考虑这些,项目的要求没有那么高,学习过这些点以后需要在开发中慢慢关注这些问题。

到此这篇关于详解Android内存优化策略的文章就介绍到这了,更多相关Android内存优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-09-09

Android 优化Handler防止内存泄露

Android 优化Handler防止内存泄露 Demo描述: Handler可能导致的内存泄露及其优化 1 关于常见的Handler的用法但是可能导致内存泄露 2 优化方式请参考BetterHandler和BetterRunnable的实现 package cc.cc; import java.lang.ref.WeakReference; import android.os.Bundle; import android.os.Handler; import android.os.Messag

详解Android的内存优化--LruCache

概念: LruCache 什么是LruCache? LruCache实现原理是什么? 这两个问题其实可以作为一个问题来回答,知道了什么是 LruCache,就只然而然的知道 LruCache 的实现原理:Lru的全称是Least Recently Used ,近期最少使用的!所以我们可以推断出 LruCache 的实现原理:把近期最少使用的数据从缓存中移除,保留使用最频繁的数据,那具体代码要怎么实现呢,我们进入到源码中看看. LruCache源码分析 public class LruCache<

详解Android使用Handler造成内存泄露的分析及解决方法

一.什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收:另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收. Android中使用Handler造成内存泄露的原因 private Han

浅谈Android性能优化之内存优化

1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法.栈帧包括局部标量表, 操作数栈. 本地方法栈:本地方法栈主要是为执行本地方法服务的.而Java栈是为执行Java方法服务的. 方法区:该区域被线程共享.主要存储每个类的信息(类名,方法信息,字段信息等).静态变量,常量,以及编译器编译后的代码等. 堆:Java中的堆是被线程共享的,

jsp Ewebeditor使用说明

<display-name>defaultroot</display-name> <servlet> <servlet-name>debugjsp</servlet-name> <description>Added to compile JSPs with debug info</description> <servlet-class>org.apache.jasper.servlet.JspServlet&l

基于JSP的RSS阅读器的设计与实现方法(推荐)

阅读器访问地址:http://easyrss.tk/,欢迎体验! 阅读导览 一. 概述 二. 设计的基本概念和原理 三. 设计方案 四. 主要源代码 五. 阅读器使用说明 概述 获得信息是在人类的生活中是必不可少的环节.如果现在的社会对获得信息不快捷,那么这个社会将不会像如今这般的发达和进步.在当今网络技术相当发达的今天,大量的信息充斥在网上.现在网络越来越发达,用户在网上既能工作也能娱乐.当用户在网上需浏览很多个网站才能获取自己多需的信息时,那就感觉很累.因为现在每个网站都有很多信息,找到自己

常用网页编辑器漏洞手册(全面版)fckeditor,ewebeditor

FCKeditor FCKeditor编辑器页/查看编辑器版本/查看文件上传路径 FCKeditor编辑器页 FCKeditor/_samples/default.html 查看编辑器版本 FCKeditor/_whatsnew.html 查看文件上传路径 fckeditor/editor/filemanager/browser/default/connectors/asp/connector.asp?Command=GetFoldersAndFiles&Type=Image&Curren

win2000/2003下整合IIS+Tomcat5支持jsp

windows2003 + IIS6 + Tomcat5.配置2000和2003下整合IIS+TOMCAT详细的配置如下:1.安装JDK,(我安装在 D:\java)设置环境变量[CLASSPATH.JAVA_HOME.Path](根据自己的JDK安装路径设置)"我的电脑"->点右键->"属性"->"高级"->"环境变量"->"系统变量"->新建->变量名:CLAS

ajax跳转到新的jsp页面的方法

ajax可以实现局部刷新页面,即在不刷新整个页面的情况下更新页面的局部信息. 项目中遇到一个问题:在用户列表也,当点击某个按钮时需要去查询用户的信息,查询成功跳转到用户详情界面:查询失败,则在原页面弹出提示信息. 想到两个解决办法: 方法一: 点击按钮,调用普通方法去查询用户信息,查询成功跳转到用户详情页面:查询失败,重定向调用查询用户列表的方法,在查询用户列表的方法结束后重新跳转到用户列表页面并弹出提示信息,相当于重新加载了用户列表页面. 方法二: 根据需求,不可以重新加载用户列表页面.用aj

jsp文件下载功能实现代码

本文实例为大家分享了jsp实现文件下载功能的3种方法,供大家参考,具体内容如下 第一种.采用转发的方式: package cn.jbit.download.servlet; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.serv

extjs 分页使用jsp传递数据示例

Ext.onReady(function() { store3 = new Ext.data.JsonStore({ autoDestroy: true, totalProperty : 'totle', url: 'service.do', storeId: 'myStore', root: 'data', fields : [ 'id', "name", "pw" ] }); var pagingToolbar = new Ext.PagingToolbar({

springmvc无法访问/WEB-INF/views下的jsp的解决方法

最近在搭建springmvc的框架,遇到的这样的问题: 在地址栏访问登陆界面访问不了,http://localhost/XXXX/WEB-INF/views/login.jsp.直接返回404.查了一下,springmvc在对保护的资源是这样访问的. 1.首先在springmvc-servlet.xml中添加视图解析. <!-- 视图处理器 --> <bean id="viewResolver" class="org.springframework.web.

JSP的setProperty的使用方法

JSP的setProperty的使用方法 一 介绍 <jsp:setProperty>作用 给已经实例化的javabean对象的属性赋值,一共有四种形式. 二 四种形式 三 实例 1.login.jsp <%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8" %> <% String path = req

JSP的Cookie在登录中的使用

JSP的Cookie在登录中的使用 一 功能需求 实现记忆用户名和密码功能. 二 代码 1.login.jsp <%@ page language="java" import="java.util.*,java.net.*" contentType="text/html; charset=utf-8"%> <% String path = request.getContextPath(); String basePath = r