最近一直想着本身之后的路如何走,Android的坑位愈来愈少,对于能力的要求也愈来愈高。曾想着换一个方向,可是最终都放弃了,毕竟这是本身喜欢的东西。因此,继续下去,不断的在Android方向发展吧。机会是给准备的人的,不断的充实本身,时刻准备着~html
进入正题。优化工做是一个开发工程师进阶必备的一种能力。包括内存优化,电量优化,网络优化等等。这些优化所须要的能力,实际上是对于各类知识的一种综合运用处理能力。java
内存优化,是对于应用程序的内存使用、空间占用进行必定的优化处理。android
经过内存优化,可以有效的避免内存泄漏、内存溢出、内存占用过大等问题,从而最大程度上保证应用的流畅性、稳定性、以及存活几率。算法
内存的管理,主要是对于进程、对象以及变量的分配以及回收。数据库
Android将内存分为了:堆区、方法区、栈区。其中堆区是内存优化的主要区域。c#
对象的生命周期以及大小都是有区别的。为了可以更合理的利用堆区,将其又按照生命周期的长短区分为了年轻代、老年代和持久代。缓存
Android应用在启动时,会经过Zygote进行来孵化应用所须要的进程。当进程建立以后,会交由Framework层来进行托管。性能优化
而对于对象和变量的内存分配,采用一种动态内存分配策略,可是并不会说能够无限的增加,会有一个上限的限制。而这个最大值则跟具体的设备有关。毕竟随着手机性能的增长,手机的处理能力更强了,从原来的512M到如今的6G内存,单个应用的可处理能力也在增长。网络
对于应用中建立的不一样对象的具体分配策略,则以下图所示ide
这里强调一下静态分配区域中的静态常量,这种常量会一直存活于程序的整个运行期间。后面咱们会讲到这种常量致使的问题。
对于Android系统,依赖gc来自动的执行对于内存的回收工做。而回收工做主要依赖于各类回收算法。在Android的ART虚拟机,用到的算法主要有4种:
ART虚拟机会自动的根据实际的状况,自动的选择回收算法来进行内存的回收工做。好比说,当咱们的应用处于前台的时候,显示速度对于咱们来讲是最重要的,这时候ART虚拟机会选择简单的标记清除算法。当应用处于后台的时候,对于速度要求就低一些,这时候可能会采用标记整理算法来进行垃圾的回收工做。
ART虚拟机还具有对于内存的整理能力,从而减小内存空洞的问题
应用进程建立之后,ActivityManagerService就会根据进程的状态计算一个其对应的OomAdj值,而后将这个值传递给Kernel中,kernel存在一个低内存回收机制(LMK)。当内存达到必定阈值时,触发清理OomAdj较高的进程。
对于一些后台占用过大的程序,其回收以后的效益最大,其OomAdj对应的值高一些,回收的几率更大。
经过LMK机制可以保证资源的合理利用,防止过大的后台应用影响到前台程序的正常使用。
在Java中,对象的引用主要分为四种。
强引用没法回收,当对象用完之后,不移除对应的引用关系,就会致使对象没法回收,而发生内存泄漏等状况。因此在Android中要注意这种问题。
对于对象的使用不当的话,可能会致使3种内存问题:内存抖动、内存泄漏,内存溢出。
内存抖动是指内存的频繁分配和回收会致使内存不稳定。在内存上一般呈现一种锯齿状,频繁进行GC。
内存抖动一般会致使应用的页面卡顿,长时间的内存抖动可能会致使应用的OOM。
实战解决
对于内存抖动的问题,通常可使用Memory Profiler来进行排查处理便可处理。
测试案例:
@SuppressLint("HandlerLeak")
private static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
for (int i = 0; i < 100; i++) {
String[] strings = new String[100000];
}
handler.sendEmptyMessageDelayed(0, 100);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
handler.sendEmptyMessage(0);
}
复制代码
这里咱们模拟了一建立大对象的操做。当程序运行起来之后,咱们点击Android Studio中的Profile按钮,就能够看到咱们的内存状况。这里看到内存呈现了锯齿状,也就是所谓的内存抖动。
当遇到这种状况的时候,就能够知道发生了内存的抖动,那么就须要查找哪些占用内存比较高。
如上图,经过顺序操做之后,在4这个位置咱们发现了建立大批量对象位置是在SplashActivity的handlerMessage的方法中。经过双击就能够跳转到咱们上面写的那一部分代码了。
对于内存抖动,应该重点关注:循环或者频繁调用的地方。由于这两个地方很容易形成对象的频繁分配会回收。
有一些常见的内存抖动的案例,在咱们进行代码编写的时候,应该尽可能避免的。
虚拟机进行GC时,会选择一些还存活的对象做为GC Roots,而后经过可达性分析来判断对象是否回收。而内存泄漏是指一些再也不使用的对象被长期持有,致使内存没法被释放,其本质是由于再也不使用的对象被其余生命周期更长的对象所持有。而在内存上的表现则是应用的内存逐渐上升,可用内存逐渐变小。
发生内存泄漏会致使咱们的内存不足,频繁GC,若是泄漏状况严重,会致使OOM。
实战解决
对于内存泄漏一般会使用Memory Profile+MAT来进行内存泄漏的分析。
这里咱们写一个简单的内存泄漏的案例,而后经过案例排查。
public class CallBackManager {
private static List<Activity> list=new ArrayList<Activity>();
public static void addCallBack(Activity activity){
list.add(activity);
}
}
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sedsd);
CallBackManager.addCallBack(this);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
findViewById(R.id.iv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(SecondActivity.class);
}
});
}
复制代码
这里咱们准备了两个页面,第二个页面注册回调管理。咱们看一下内存的状况。
当咱们进入第二个页面,而后退出。通过循环屡次的点击以后,内存慢慢的上升了~~~这种状况基本就是内存泄漏致使的。
按照上图的步骤点开之后,会看到内存中存在着18个对应的SecondActivity对象。也就是SecondActivity发生了内存泄漏。可是对于如何发生了泄漏,如今什么实例发生泄漏,在这里是没法看出来的,须要经过MAT来帮助咱们定位问题。
在AndroidStudio中,将dump的文件导出。而后经过Android SDK的自带转换工具hprof-conv.exe,将文件转化为可以被MAT识别的hprof文件。转化语句为:
./hprof-conv file.hprof converted.hprof
复制代码
而后经过MAT打开转化后的.hprof文件。
gif可能效果不太好,这里只是演示一下如何去使用MAT,咱们看一下最后找到的结果。
这里左边有小圆点的是咱们的gcRoot,也就是持有SecondActivity的实例,致使其没法回收的根源。能够看到持有实例的是CallBackManager的list对象
这时候咱们就能够回到项目中去找这个对象,而后进行一个处理了。
集合类
集合类添加元素后,会持有集合对象的引用信息,致使集合对象没法回收而内存泄漏。在程序退出,或者集合再也不使用的时候,先clear,再置为空。
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
// 释放objectList
objectList.clear();
objectList=null;
复制代码
Static修饰的成员变量
被Static修饰的成员变量的周期=应用的生命周期。若是static修饰的成员变量引用耗费资源的实例(例如Context),那么当实例结束生命周期的时候,由于静态变量的持有而没法被回收,从而出现内存泄漏。
解决方案:
典型:单例中的静态变量
对于单例中的,可能第二种弱引用并非很合适,这时候只能使用第一种方案。
非静态内部类/匿名内部类
非静态内部类或者匿名类都会持有外部类的引用。而静态的内部类则不会。
因此若是非静态内部类的实例若是没法回收的话,好比说用Static修饰,也会致使外部类没法释放而内存泄漏。
解决方案:
若是是Runnable,Thread等匿名类,若是内部耗时工做,那么工做线程实例持有外部类引用,也会形成实例没法回收的问题。
Handler临时性的内存泄漏
Handler在发送出去Message以后,会保存在MessageQueue中。因为Message中会保存着Handler的引用,因此若是是延迟处理的消息,那么极可能致使Handler没法回收,若是再使用了匿名内部类,那么所对应的的Activity实例也就没法回收,从而致使内存泄漏。
解决方案:
资源型文件未关闭
对于资源的使用(广播,文件流,数据库游标,图片资源BitMap等),若是不关闭或者注销,那么资源就不会回收,从而致使内存泄漏。应该在资源对象再也不使用的时候,调用其close()方法将其关闭,而后再置为null。
WebView
WebView因为其特性问题,只要在应用中使用一次,那么内存就不会进行释放。能够为WebView建立单独的进程,经过AIDL方式和主进程进行通讯,当WebView须要销毁时,直接销毁其进程来达到内存释放的目的。
Adapter
在快速滑动ListView或者RecyclerView的时候,会频繁的建立大量对象,不只浪费资源和时间,内存也会愈来愈大。可使用缓存的convertView和ViewHolder来进行缓存。
当内存使用过大的时候,会致使内存的溢出,也就是OOM。这种状况不必定发生在相对应的代码处。内存抖动和内存泄漏都有可能会致使最后的内存溢出。对于内存溢出的状况,除了要考虑内存抖动和内存泄漏的问题,还应该尽可能使用合适的类型来优化代码
使用Android优化事后的SparseArray,SparseInt等集合类。代替HashMap。
使用IntDef、StringDef等,来替代枚举。
经过LruCache方法来实现对大资源的缓存功能
BitMap优化。位图使用RGB_565或者ARGB_4444。
重写 onTrimMemory/onLowMemory 方法去释放掉图片缓存、静态缓存来自保
RecyclView和ListView不可见时,释放掉对图片的
ListView的3级缓存:在ImageView的DetachFromWindows时释放掉
RecyclerView的5级缓存:在放入到mRecyclerPool时回收(重写Adapter的onViewRecycled方法)
其余
使用基本数据类型
使用For加强
使用软引用和弱引用
采用内存缓存或者磁盘缓存
对于建立复杂的使用池技术
尽可能使用静态内部类
哪怕对于常见的内存泄漏案例了如指掌,可是确定仍是会不免出现内存泄漏现象。这时候咱们就须要借用工具来检测内存的泄漏状况了。最经常使用的工具分别
对于前两种工具,咱们在以前的案例中进行了一部分的功能展现,这里咱们作一些总结的信息。
实时的内存使用状况,方便直观
识别内存抖动、泄漏等,
提供堆转储、强制GC以及跟踪内存分配的能力
线下平时使用
强大的Java Heap 分析工具,查找内存泄漏以及内存占用
生成总体的报告,分析问题等等
线下的深刻使用工具
能够和Memory Profiler结合使用,一个定位问题,一个深刻分析
LeakCanary是一种自动内存泄漏检测的神器,仅推荐使用于线下集成。可是其缺点也是比较明显的。
这篇文章对于内存的常见问题以及处理方案进行了总结。
可是内存优化,若是深挖,会涉及到不少不少的知识点。
一个最须要优化的Bitmap,其内存占是如何计算的等等,为何就占用了那么多的内存;对于LMK机制,咱们如何保证程序在后台运行的时候可以保证最大程度不被杀掉;线上如何造成一套APM的机制,内存出现问题之后上报等等都是值得研究的地方。
Android性能优化:手把手带你全面了解 内存泄露 & 解决方案
本文由 开了肯 发布!
同步公众号[开了肯]