Android性能优化--内存优化

2020-06-21  本文已影响0人  ModestStorm

转载自:Android性能优化--内存优化

上一篇文章关于Android性能优化--启动优化探讨了启动优化相关的知识点,在本篇将介绍内存优化的相关优化。主要大纲参照如下

image

2.常见问题

常见的Android内存相关问题,通常可以分为以下三种,内存抖动、内存泄露、内存溢出。

上述三者之间的是一个递进关系,内存抖动<内存泄露<内存溢出。对于一般应用主要是处理内存抖动和内存泄露两点,处理好这两点就会大大降低内存溢出的可能性。

3.内存管理

3.1 Java内存管理

JVM的内存回收对于大多数开发者来说接触的并不是很多。因为JVM本身是有一套内存回收的机制,对于开发者更多的是申请对象直接调用即可,其内部并不是很在意。下面主要通过内存存储和回收这两块介绍。

3.2 Android内存管理

Android 系统主要是在Art和Dalvik虚拟机中的托管环境中跟踪每个内存分配,当发现有可回收的对象,进行内存回收。回收有两个目标:在程序中查找将来无法访问的数据对象,并回收那些对象使用的资源 。

4.常见场景及解决方案

4.1 内存抖动

由于短时间内有大量对象进出Young Generiation区导致的,它伴随着频繁的GC。

如下面一部分代码就对应着内存抖动

Handler handler = new Handler(){ @Override        
public void handleMessage(@NonNull Message msg) {  
super.handleMessage(msg);          
for (int i =0;i<100;i++){   
   String string[] = new String[10000]; 
   }           
 handler.sendEmptyMessageDelayed(1,30);    
  }
    };

通过Profile查看其内存图

image

可以看到其内存图基本上是一个锯齿状,是因为这时候一直在创建对象和回收对象所致。

4.2 内存泄露

业内一般对内存泄露的原因总结为长生命周期对象引用短生命周期对象,导致短生命周期对象无法及时回收所致。

1、单例引起的内存泄漏

public static  FacebookAnalysis getInstance(Context context){
        if (facebookAnalysis == null){
      synchronized (FacebookAnalysis.class){  
 facebookAnalysis = new FacebookAnalysis(getAppEventsLoggerInstance(context));     
       }     
   }    
    return facebookAnalysis;    }

上面是一个常见的单例模式,如果参数引用ActivityContext,而单例模式的生命周期长于Activity。这里单例模式引用Activity的实例,当Activity被销毁,Activity无法被回收,造成内存泄露。如果这里引用的ApplicationContext,将无任何影响。因为Application的生命周期与单例模式同样长。

2、静态集合添加对象,在使用完之后未及时释放。

 for (int i = 0; i < 10; i++) {  
   Object obj = new Object();          
  list.add(obj);           
 obj = null;   
     }

此时list是一个静态的集合,obj单个对象,当list集合使用完毕,应当及时清除该集合,避免obj被静态对象引用。

3、 匿名内部类&非静态内部类

Android 中常见的是对ListView中各个元素设置点击事件,如果此时采用匿名内部类,会存在内存泄露的风险。常规做法是将该点击事件用接口和setTag()的方式往外传递。

同样Handler在使用过程中也会出现内存泄露的风险,一般则是采用弱引用的方式处理,或者在ActivityonDestroy方法中移除该Handler的所有消息`handler.removeCallbacksAndMessages(null)``。

4、线程泄露

在主线程Activity中出现如下代码:

    public void onCreate(Bundle icicle) {    
    super.onCreate(icicle);      
  mIntent = (icicle == null) ? getIntent() : null;       
 new Thread(new Runnable() {      
      @Override           
 public void run() {          
      doSomeThing();         
   }   
     }).start();}

当此时该Activity已经销毁,但是子线程中doSomeThing方法未执行完成,此时会造成内存泄露。一般做法是当Activity销毁时取消该线程或者采用其他方式实现,总之原则是线程不持有Activity的上下文,如果持有,就应及时取消。

5、数据库游标,文件资源未及时关闭,广播未反向注册,服务未解绑等行为。

6、Bitmap加载泄露

Bitmap的加载在Android一直是比较吃内存的,且容易出现内存泄露相关问题,一般都是采用统一的图片请求框架去处理图片加载缓存,这些框架都会从加载、压缩、缓存等策略对其做优化处理。同时Google 官方也是推荐使用统一库处理位图,具体可以在Glide官网查看。关于图片加载这块其实是很容易出现内存泄露的问题,在此暂时不作展开,后续会细说。

5.常用工具

5.1 Memory Profiler

Memory ProfilerAndroid Profiler 中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄漏和内存抖动。它显示一个应用内存使用量的实时图表,可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。从图中现象

image

可以看出应用此时存在内存抖动的现象,此时抓取红色部分,可以得到如下图:

image
5.2 Memory Analyzer

Memory Analyzer MAT是一个功能丰富的 JAVA 堆转储文件分析工具,可以帮助开发者发现内存漏洞和减少内存消耗。根据上面分析下面这段代码是存在内存泄露的问题:

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 class MemoryLeakActivity extends AppCompatActivity implements CallBack{    @Override    protected void onCreate( Bundle savedInstanceState) {     
   super.onCreate(savedInstanceState);        setContentView(R.layout.activity_memoryleak);       
 ImageView imageView = findViewById(R.id.iv_memoryleak);    
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.splash);        
imageView.setImageBitmap(bitmap);        CallBackManager.addCallBack(this);   
 }    @Override    protected void onDestroy() {        
super.onDestroy();
//        CallBackManager.removeCallBack(this);    
}    
@Override    public void doWork() {   
     // do work   
 }
}

连续进入退出MemoryLeakActivity,通过Android Studio 的Profile可以查看到当时的内存入下图

image

可以看到此时内存处于一个波动状态,保存该文件,生成memory-20191212T110510.hprof文件,通过命令转换

D:\>hprof-conv D:\Android\log\memory-20191212T110510.hprof D:\Android\log\2.hprof

此时通过MAT打开转换后的文件如下:

image

选择Histogram,输入正则表达".MemoryLeak. "可以搜索到具体包名,然后右键List objects->With incoming references然后选择Path to GC Roots->With all references(此处可以选择其他)。此时可以看到下面这张图

image

从图中即可看出内存泄露的位置,即该Activity被对象sCallbacks引用。在代码中添加方法

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

再次抓取内存信息,并未出现上述结果。

5.3 LeakCanary

可以通过LeakCanary在开发阶段检测到引用的内存情况。LeakCanary 主要是通过监听ActivityonDestory,手动调用GC,然后通过ReferenceQueue+WeakReference,来判断Activity对象是否被回收,然后结合dump Heaphpof文件,通过Haha开源库分析泄露的位置。具体使用可以参照leakcanary

6. 总结

关于内存优化知识点很多,很细。但究其根本我认为是监控内存泄露和优化内存泄漏,各大厂商都有提过相关的方案

这些都具备参考价值。同时我们也可以采用一些Hook黑科技相关方法进行部分内存性能消耗较大的业务进行监控,及时告知开发人员。例如:可以通过Epic监控项目中所有的setImageBitmap()方法,此时就可以知道传入的Bitmap是否有内存相关风险,一旦有风险,立马通知反馈。

以上为此次Android内存优化的总结,欢迎指正。

感谢:

---END---

推荐阅读:优雅保活方案,原来Android还可以这样保活!
终于等到你!官方版Android源码查看工具正式发布!
Flutter Interact 的 Flutter 1.12 大进化和回顾Android高仿QQ 发送图片时炫酷的加载效果

上一篇 下一篇

猜你喜欢

热点阅读