美文网首页Android开发学习Android项目性能优化
新闻类App (MVP + RxJava + Retrofit+

新闻类App (MVP + RxJava + Retrofit+

作者: Peakmain | 来源:发表于2019-04-16 15:30 被阅读83次

    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<CallBack> 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检测
    • 二次确认断定发生内存泄漏
    • 分析泄漏,找引用链
    • 监控组件+分析组件

    LeakCanary定制

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

    线上监控完整方案

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

    相关文章

      网友评论

        本文标题:新闻类App (MVP + RxJava + Retrofit+

        本文链接:https://www.haomeiwen.com/subject/zhxpwqtx.html