App性能优化:内存优化

App性能优化:内存优化

目录:

  1. 内存优化工具
  2. 内存管理机制
  3. 内存抖动解决
  4. 内存泄漏解决
  5. MAT详解
  6. 小结
背景介绍
  • 内存是大问题但缺乏关注
  • 压死骆驼的最后一根稻草
表现形式
  • 内存抖动:锯齿状,GC导致卡顿
  • 内存泄漏:可用内存减少、频繁GC
  • 内存溢出:OOM、程序异常
1、工具选择
  • Memory Profiler
  • Memory Analyzer
  • LeakCanary
Memory Profiler
  1. 实时图标展示内存使用量
  2. 识别内存泄漏、抖动
  3. 提供捕获堆转储、强制GC以及跟踪内存分配的能力
Memory Profiler的使用
  1. 非常直观
  2. 线下平时使用
Memory Analyzer(MAT)
  1. 强大的Java heap 分析工具,帮助查找内存泄漏和内存占用
  2. 生成整体报告,分析问题
  3. 线下深入使用
LeakCanary
  1. 自动内存泄漏检测
  2. 线下集成
2、android内存管理机制
Java内存管理机制

在这里插入图片描述

Java内存回收算法-标记清除算法
  • 标记出所有需要回收的对象
  • 统一回收所有被标记的对象
    在这里插入图片描述
标记回收算法存在的问题
  • 标记和清除的效率不高
  • 产生大量的内存碎片
Java内存回收算法之 复制算法
  • 将内存分成两份,只将数据存储在其中一块上。
  • 当需要垃圾回收时,也是标记出废弃的数据,然后将有用数据复制到另一块内存上,最后将第一块内存全部清除
    在这里插入图片描述
复制算法的问题
  • 实现简单,运行高效
  • 浪费一半空间,代价大
Java内存回收算法之 标记整理算法
  1. 标记过程与“标记-清除算法”一样
  2. 存活对象往一端移动
  3. 清理其余内存
    在这里插入图片描述
标记整理算法的特点
  1. 避免标记-清理导致的内存碎片
  2. 避免复制算法的空间浪费
Java内存回收算法之 分代收集算法
  1. 结合多种收集算法优势
  2. 新生带对象存活率低:复制算法
  3. 老年代对象存活率高:标记-整理算法
Android内存管理机制
  • 内存弹性分配,分配值和最大值受具体设备影响
  • OOM场景:内存真正不足,可用内存不足
Dalvik 与Art区别
  • Dalvik仅固定一种回收算法
  • Art回收算法可运行期选择
  • Art具备内存整理能力,减少内存空洞
Low Memory killer
  • 进程分类:前端、可见、服务、后台、空进程,优先级依次降低
  • 回收收益
内存抖动案例

######制造内存抖动

    companion object {
        var datas: ArrayList<TestClass>? = null
        val handler = object : Handler() {
            override fun handleMessage(msg: Message) {
                //创造内存抖动
                for (i in 0..100) {
                    datas = ArrayList<TestClass>(100000)
                }
                sendEmptyMessageDelayed(0, 1000)
            }
        }
    }

在这里插入图片描述

内存抖动解决技巧
  1. 找循环或者频繁调用的地方
  2. 通过Profiler定位,锯齿状或者频繁的GC
内存泄漏
  • 定义:内存中存在没有用的对象,又无法回收
  • 表现:内存抖动、可用内存逐渐减少
内存泄漏案例
  1. 静态变量持有Activity引用导致Activity无法被回收
//定义一个接口
public interface Callback {

    void doOperation();
}
//定义持有Activity引用的类
public class CallbackMananger {
    public static ArrayList<Callback> sCallbacks = new ArrayList();

    public static void addCallback(Callback callback) {
        sCallbacks.add(callback);
    }

    public static void removeCallBack(Callback callback) {
        sCallbacks.remove(callback);
    }
}

  1. 在生成LeakCanaryActivity,显示一张图片

class MemoryLeakActivity : AppCompatActivity(), Callback {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory_leak)
        val bitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_leaktest)
        iv.setImageBitmap(bitmap)
        //保存Activity引用
        CallbackMananger.addCallback(this)
    }

    override fun doOperation() {

    }
}

  1. 重复打开关闭 页面,即可在Profiler中看到内存成阶梯状上升,如下图所示:
    在这里插入图片描述
    显然:这里每次GC的的时候并没有导致MemoryLeakActivity被回收,内存图形呈现阶梯状显示。

  2. 使用MAT分析内存泄漏
    1. 进入官网下载MAT工具:MAT下载地址
    2. 下载对应系统版本
    在这里插入图片描述

  3. 安装MAT工具,双击zip文件解压后得到mat.app
    在这里插入图片描述

  4. 获取堆文件 Heap Dump
    在这里插入图片描述

  5. 将文件存到本地
    在这里插入图片描述
    在这里插入图片描述

  6. 注意这里是Android的hprof文件,需要使用android 的platform-tool里面的hprof-conv工具对文件进行一次格式转化,转化命令如下:
    ./hprof-conv /Users/hudingpeng/Desktop/memory-20200221T114049.hprof 2.hprof
    在这里插入图片描述

  7. 这样在platform-tools目录下会生成一个2.hprof文件,如下图所示:
    在这里插入图片描述

  8. 打开MAT工具:

    1. 直接点击mat.app会报错,按照以下步骤运行
    2. 右键mat.app => 显示包内容 =>进入 mat.app/Contents/MacOS ,此目录下就有工具MemoryAnalyzer =>双击直接打开工具就行了
      在这里插入图片描述
  9. 在这里插入图片描述
    10.点击open a Heap Dump选择我们的转化后的文件2.hprof 文件即可,下面分析MAT工具的一些常用用法

MAT工具分析内存泄漏
  1. 打开hprot文件,选择Histogram
    在这里插入图片描述

  2. 搜索我们的泄漏的MemoryLeakActivity,可以看到有4个MemoryLeakActivity对象,表示已经泄漏了
    在这里插入图片描述

  3. 右键选择查看with incoming references
    在这里插入图片描述

  4. 选择一个,点击查看到GC Root的路径,看到具体引用的地方
    在这里插入图片描述
    在这里插入图片描述
    这样我们就看到了具体泄漏的地方就是,CallbackManager类里面的静态变量sCallbacks

  5. 点击Group by package 可以按包名排序
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这样也可以很方便看到我们的包名下的类对象生成的情况,这里也可以看到MemoryLeakActivity在内存里有4个对象

  6. 第三个菜单项:dominator_tree 视图,显示对象占用内存的百分比
    在这里插入图片描述
    可以看到对象占用内存的百分比,给我们的内存优化提供一定的参考

  7. 第四个菜单项:OQL,相当于一种数据查询语言,我们直接搜索我们的类,点击感叹号查询,如下图所示
    在这里插入图片描述

  8. Top Consumer条目:通过图表的形式显示占用内存最多的对象
    在这里插入图片描述
    在这里插入图片描述
    这对我们的内存占用优化也是一个不错的参考

关于BitMap的优化
Btimap的内存模型
  1. 获取Bitmap占用的内存

    1. getByteCount()方法
    2. 宽 x 高 x 一像素占用的内存
  2. 常规方式:
    背景:图片对内存大小至关重要、图片宽高大于控件宽高

  3. 通过Epic ARTHook优雅检测不合理的图片
    Epic:Epic 是一个在虚拟机层面、以 Java Method 为粒度的 运行时 AOP Hook 框架。简单来说,Epic 就是 ART 上的 Dexposed(支持 Android 4.0 ~ 10.0)。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。
    Epic 被 VirtualXposed 以及 太极 使用,用来实现非 Root 场景下的 Xposed 功能,已经经过了相当广泛的验证。
    非常厉害的AOP框架,参考连接:Epic AOP框架
    这里我们Hook setImageBitmap方法:

import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

import com.optimize.performance.utils.LogUtils;
import com.taobao.android.dexposed.XC_MethodHook;

public class ImageHook extends XC_MethodHook {

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        // 实现我们的逻辑
        ImageView imageView = (ImageView) param.thisObject;
        checkBitmap(imageView,((ImageView) param.thisObject).getDrawable());
    }


    private static void checkBitmap(Object thiz, Drawable drawable) {
        if (drawable instanceof BitmapDrawable && thiz instanceof View) {
            final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            if (bitmap != null) {
                final View view = (View) thiz;
                int width = view.getWidth();
                int height = view.getHeight();
                if (width > 0 && height > 0) {
                    // 图标宽高都大于view带下的2倍以上,则警告
                    if (bitmap.getWidth() >= (width << 1)
                            && bitmap.getHeight() >= (height << 1)) {
                        warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large"));
                    }
                } else {
                    final Throwable stackTrace = new RuntimeException();
                    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                        @Override
                        public boolean onPreDraw() {
                            int w = view.getWidth();
                            int h = view.getHeight();
                            if (w > 0 && h > 0) {
                                if (bitmap.getWidth() >= (w << 1)
                                        && bitmap.getHeight() >= (h << 1)) {
                                    warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace);
                                }
                                view.getViewTreeObserver().removeOnPreDrawListener(this);
                            }
                            return true;
                        }
                    });
                }
            }
        }
    }


    private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
        String warnInfo = new StringBuilder("Bitmap size too large: ")
                .append("\n real size: (").append(bitmapWidth).append(',').append(bitmapHeight).append(')')
                .append("\n desired size: (").append(viewWidth).append(',').append(viewHeight).append(')')
                .append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n')
                .toString();

        LogUtils.i(warnInfo);
    }

}

在App onCreate方法里注册要Hook的方法,传入ImageHook

 DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook());
            }
        });
more:LeakCanary 监控内存泄漏
内存优化总结
优化大方向
  1. 内存泄漏
  2. 内存抖动
  3. Btimap
优化细节
  1. LargeHeap属性 (建议开启,大概率能申请到更多内存)
  2. onTrimMemory (系统块干掉app了,可以手动清理界面,跳转到主界面)
  3. 使用系统优化过的集合:SparseArray
  4. 谨慎使用SharedPreferences
  5. 谨慎使用外部库
  6. 业务架构设计合理
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值