新闻类App (MVP + RxJava + Retrofit+Dagger+ARouter)性能优化之内存优化

Github地址:新闻类App (MVP + RxJava + Retrofit+Dagger+ARouter)
关于内存优化,之前写过一篇文章,android性能优化之内存优化,大家可以先看下这篇文章

内存问题

内存抖动:图形是锯齿状,GC导致卡顿
内存泄漏:可用内存减少,不断的GC
内存溢出:OOM异常,程序异常

内存优化工具

工具使用大家可以找相关资料,这里我并不介绍工具的使用
Memory Profiler
1.实时图表表示应用内存使用量,可以识别内存泄漏,内存抖动等
2.提供捕获堆转储,强制GC以及跟踪内存分配的能力

image.png

Memory Analyzer
MAT:强大的java Heap分析工具,查找内存泄漏以及内存占用
生成整体报告,分析问题等
线下使用
LeakCanary
自动检查内存泄漏工具
Github:https://github.com/square/leakcanary

内存管理机制

  • Java内存管理机制


    image.png

java内存回收算法

标记-清除算法

  • 标记出所有需要回收的对象
  • 统一回收所有被标记的对象

缺点

  • 效率不高
  • 产生大量不连续的内存碎片

复制算法

  • 将内存划分为大小相等的两块
  • 一块内存用完之后复制存活对象到另一块
  • 清理另一块内存

优缺点

  • 相对标记清除算法,实现简单,运行高效
  • 浪费一半空间,代价大

标记-整理算法

  • 标记过程与“标记-清除算法”一样
  • 存活对象往一端进行移动

优缺点

  • 避免标记-清除导致的内存碎片
  • 避免复制算法的空间浪费

分代收集算法

  • 结合多种收集算法优势
  • 新生代对象存活率低,可用复制算法
  • 老年代对象存活率高,可用标记-整理

Android内存管理机制

  • 内存弹性分配,分配值与最大值受具体设备影响
  • OOM场景:内存真正不足或者可用内存不足

Dalivk与Art区别

  • Dalivk仅固定一种回收算法
  • Art回收算法可运行期选择
  • Art具有 内存整理能力,减少内存空间

Low Memory killer

  • 进程分类
    前台进程,可见进程,服务进程,后台进程,空进程

内存抖动介绍

定义:内存频繁分配和回收导致内存不稳定
表现:频繁GC,内存曲线呈锯齿状
危害:导致卡顿,OOM异常

为什么内存抖动导致OOM

  • 频繁创建对象,导致内存不足及碎片
  • 不连续的内存无法被分配

内存抖动实战

首先使用Memory Profiler初步排查
使用Memory Profiler或CPU Profiler结合代码排查
模拟内存抖动代码

public class MemoryShakeActivity extends AppCompatActivity implements View.OnClickListener {

    @SuppressLint("HandlerLeak")
    private static Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 创造内存抖动
            for (int index = 0; index <= 100; index++){
                String arg[] = new String[100000];
            }
            mHandler.sendEmptyMessageDelayed(0,30);
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        findViewById(R.id.bt_memory).setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        mHandler.sendEmptyMessage(0);
    }

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

memory profiler出现以下情况


image.png

点击工具按钮的红色圆圈按钮,过一段时间再次点击停止记录


image.png

过一段时间下方会出现以下的情况
image.png

我们发现内存消耗最多的String[]点击String[].并选择其中的一个String[]


image.png

双击handleMessage就会跳转到相关代码

内存泄漏实战

定义:内存中存在已经没有用的对象
表现:内存抖动,可用内存逐渐变少
危害:内存不足,GC频繁,OOM异常

代码实战

public class MemoryLeakActivity extends AppCompatActivity implements CallBack{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memoryleak);
        ImageView imageView = findViewById(R.id.iv_memoryleak);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.avatar);
        imageView.setImageBitmap(bitmap);

        CallBackManager.addCallBack(this);
    }


    @Override
    public void dpOperate() {

    }
}
public class CallBackManager {

    public static ArrayList sCallBacks = new ArrayList<>();

    public static void addCallBack(CallBack callBack) {
        sCallBacks.add(callBack);
    }

    public static void removeCallBack(CallBack callBack) {
        sCallBacks.remove(callBack);
    }

}
public interface CallBack {
    void dpOperate();
}

从某个页面进去多进去几次之后会出现内存泄漏,memory profiler此时只能判断是否内存泄漏,解决还得使用MAT工具,
MAT下载地址https://www.eclipse.org/mat/

image.png

首先点击1然后点击2导出文件,但是导出的文件需要进行转换,我们需要找到自己的android sdk目录下的platform-tools目录中找到hprof-conv.exe文件,然后cmd命令找到platform-tools目录,执行命令

hprof-conv 源文件 输出文

我的是:

hprof-conv C:\Users\asus\Desktop\result.hprof C:\Users\asus\Desktop\result1.hprof

打开MAT->打开文件->Overview->Histogram->搜索MemoryLeakActivity


image.png

搜索MemoryLeakActivity之后的结果,此时有Objects有代表的确有内存泄漏8个


image.png

右击选择ListObjects->With incoming references
image.png

继续右击


image.png

结果如下


image.png

代表CallBackManager中sCallBacks持有了MemoryLeakActivity对象

解决办法MemoryLeakActivity中添加

   @Override
    protected void onDestroy() {
        super.onDestroy();
        CallBackManager.removeCallBack(this);
    }

检测不合理图片

Bitmap内存模型

  • API10之前Bitmap自身在Dalivk Heap中,像素在Native中
  • API10之后像素也放在Dalivk heap中
  • API26之后像素在Native中

获取Bitmap占用的内存

  • getByteCount
  • 一像素占用的内存

常规方式

  • 背景:图片的宽高大于控件宽高
  • 实现:继承ImageView,覆写实现计算大小

ARTHook

  • 挂钩,将额外的代码钩住原有的方法,修改执行逻辑
  • 框架:Epic(不能带到线上环境)
  • 代码
    ImageHook工具类
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.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.e(warnInfo);
    }
}

App中调用

       DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
            //setImageBitmap 方法名字 Bitmap 参数类型 
                DexposedBridge.findAndHookMethod(ImageView.class,"setImageBitmap",
                        Bitmap.class,new ImageHook());
            }
        });

线上内存泄漏监控

常规实现一

  • 设定场景线上Dump:Debug.dumpHprofData();
  • 实现流程
    超过最大内存的80%->内存Dump->回传文件->MAT手动分析
  • 缺点:上传失败率高,分析困难

常规实现二

  • leakCanary带到线上
  • 预设泄漏怀疑点
  • 发现泄漏回传
  • 缺点:不适合所有情况,必须预设怀疑点

LeakCanary原理(源码:后期我会单独写篇文章)

  • 监控生命周期,onDestory添加RefWatch检测
  • 二次确认断定发生内存泄漏
  • 分析泄漏,找引用链
  • 监控组件+分析组件
    image.png

    LeakCanary定制
  • 预设怀疑点->自动找怀疑点(找内存大的)
  • 分析泄漏链路慢(原因它会分析每个链路)->分析 Retain size大的对象
  • 分析OOM(leakcanary会把所有文件加载到内存)->对象裁剪,不全部加载到内存中

线上监控完整方案

  • 待机内存,重点模块内存,oom率
  • 整体及重点模块GC次数,GC时间
  • 增强的LeakCanary自动化内存泄漏分析

你可能感兴趣的:(新闻类App (MVP + RxJava + Retrofit+Dagger+ARouter)性能优化之内存优化)