Android 性能优化
一:介绍
Android 性能优化主要从以下4个方面:
1.稳定(内存溢出,崩溃)
2.流畅(卡顿)
3.损耗(耗电,流量)
4.安装包(APK瘦身)
影响稳定性的因素有很多,比如内存使用不合理,代码异常场景考虑不周全,代码逻辑不合理等,都会对应用的稳定性造成影响。
最常见的两个场景:Crash和ANR,这两个错误将会使得程序无法正常使用。
1.所以做好Crash全局监控,处理闪退同时把崩溃信息、异常信息收集记录起来,以便后续分析;
2.合理使用主线程处理业务,不要在主线程中做耗时操作,防止ANR程序无响应发生。
内存是Android运行性能至关重要的一项指标,每个进程能使用的内存是有限的。不合理的使用内存会导致频繁的GC、甚至发生OOM,过多GC会导致App卡顿,而内存泄漏或者内存抖动都可以导致OOM,这是无法接受的。
二:稳定(内存溢出,崩溃)
内存泄露:长时间持有对象的引用,会导致内存占用过大,导致应用卡顿;
指的是那些程序不再使用的对象无法被GC识别,这样就导致这个对象一直留在内存当中,占用了没来就不多的内存空间。
1.单例模式引起的内存泄露
由于单例模式的生命周期和app生命周期一致
public class Singleton {
private Context context;
private static Singleton mInstance;
private Singleton(Context context){
this.context=context;
}
public static Singleton getInstance(Context context){
if (mInstance==null){
mInstance=new Singleton(context);
}
return mInstance;
}
}
//这是一个单例模式的标准写法,表面上看没有任何问题,但是细心的同学会发现,构建该单例的一个实例时需要传入一个Context,此时传入的Context就非常关键,如果此时传入的是Activity,由于Context会被创建的实例一直持有,当Activity进入后台或者开启设置里面的不保留活动时,Activity会被销毁,但是单例持有它的Context引用,Activity又没法销毁,导致了内存泄漏。
//如果此时传入的Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏。但是我们不能指望使用这个单例的用户始终传入期望的Context,因此需要对这个单例设计进行调整,可以在构造函数中对mContext赋值改为this.mContext = context.getApplicationContext;当然,也可以直接不让用户传入context。
当我们在Activity里面使用这个的时候,把我们Acitivty的context传进去,那么,这个单例就持有这个Activity的引用,当这个Activity没有用了,需要销毁的时候,因为这个单例还持有Activity的引用,所以无法GC回收,所以就出现了内存泄漏,也就是生命周期长的持有了生命周期短的引用,造成了内存泄漏。
2.非静态内部类持有外部类的引用导致内存泄露
下图是静态内部类和非静态内部类的比较
非静态内部类他会持有他外部类的引用,从图我们可以看到非静态内部类的生命周期可能比外部类更长,这就是二楼的情况一致了,如果非静态内部类的周明周期长于外部类,在加上自动持有外部类的强引用,我的乖乖,想不泄漏都难啊。
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAscnyTask().execute();
}
//改了这里 注意一下 static
static class MyAscnyTask extends AsyncTask{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
3.Handler引起的内存泄露。
我们知道,Handler、Message、MessageQueue是相互关联在一起的,Handler通过发送消息Message与主线程进行交互,如果Handler发送的消息Message尚未被处理,该Message及发送它的Handler对象将被MessageQueue一直持有,这样就可能会导致Handler无法被回收。
将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,如果Handler里面需要context的话,可以通过弱引用方式引用外部类。
其实Handler也是非静态内部类的问题引起的,会持有Activity的引用,发送延迟消息,但activity已经关闭,导致一直持有该activity引用没法处理消息
public class ScanActivity extends AppCompatActivity {
//改成静态内部类,防止内存泄露
private static Handler mHandler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
String aa= (String) msg.obj;
Log.d("aa", aa);
System.out.println(aa);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_four);
Message message=Message.obtain();
message.obj="你好";
mHandler.sendMessage(message);
mHandler.sendMessageDelayed(message,10000);//这样要发送延迟消息的话,同时跳转到其他页面,会导致消息队列中一直持有该Activity引用导致内存泄露
//所有改进方式是Handler内部类改成静态内部类
}
}
4.资源对象没有关闭引起内存泄露
在android中,资源性对象比如Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源的时候,如果我们确保自己不再使用这些资源了,要及时关闭,否则可能引起内存泄漏。因为有些操作不仅仅只是涉及到Dalvik虚拟机,还涉及到底层C/C++等的内存管理,不能完全寄希望虚拟机帮我们完成内存管理
5.注册/反注册未成对使用引起的内存泄漏
在andorid开发中,我们经常会在Activity的onCreate中注册广播接受器、EventBus等,如果忘记成对的使用反注册,可能会引起内存泄漏。开发过程中应该养成良好的相关,在onCreate或onResume中注册,要记得相应的在onDestroy或onPause中反注册
内存泄漏就是持有对象的引用,那么引用分为强引用,软引用,弱引用,虚引用
强引用
我们平时不做特殊处理的一般都是强引用,如果一个对象具有强引用,GC宁可OOM也绝不会回收它。看出多强硬了吧。
软引用(SoftReference)
如果内存空间足够,GC就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
弱引用(WeakReference)
弱引用要比软引用,更弱一个级别,内存不够要回收他,GC的时候不管内存够不够也要回收他,简直是弱的一匹。不过GC是一个优先级很低的线程,也不是太频繁进行,所以弱引用的生活还过得去,没那么提心吊胆。
虚引用
用的甚少,我没有用过,如果想了解的朋友,可以自行谷歌百度。
内存泄露分析工具
1.leakcanary傻瓜式操作,哪里有泄漏自动给你显示出来,很直接很暴力。(待研究)
2.我们平时也要多使用Memory Monitor进行内存监控,这个分析就有些难度了,可以上网搜一下具体怎么使用。
三:快(流畅,卡顿)
1.布局优化
屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。
- 如果父控件有颜色,也是自己需要的颜色,那么就不必在子控件加背景颜色
- 如果每个自控件的颜色不太一样,而且可以完全覆盖父控件,那么就不需要再父控件上加背景颜色
- 尽量减少不必要的嵌套
- 能用LinearLayout和FrameLayout,就不要用RelativeLayout,因为RelativeLayout控件相对比较复杂,测绘也想要耗时。
- 使用include和merge增加复用,减少层级
- ViewStub按需加载,更加轻便
- 复杂界面可选择ConstraintLayout,可有效减少层级
2.绘制优化
这个是Android的渲染机制造成的,Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,但是渲染未必成功,如果成功了那么代表一切顺利,但是失败了可能就要延误时间,或者直接跳过去,给人视觉上的表现,就是要么卡了一会,要么跳帧。
View的绘制频率保证60fps是最佳的,这就要求每帧绘制时间不超过16ms(16ms = 1000/60),虽然程序很难保证16ms这个时间,但是尽量降低onDraw方法中的复杂度总是切实有效的。
那么ondraw()方法中绘制
onDraw中不要创建新的局部对象
onDraw方法中不要做耗时的任务
四:耗电优化
在定位精度要求不高的情况下,使用wifi或移动网络进行定位,没有必要开启GPS定位。
先验证网络的可用性,在发送网络请求,比如,当用户处于2G状态下,而此时的操作是查看一张大图,下载下来可能都200多K甚至更大,我们没必要去发送这个请求,让用户一直等待那个菊花吧。
适当的做本地缓存,避免频繁请求网络数据,这里,说起来容易,做起来并非三刀两斧就能搞定,要配合良好的缓存策略,区分哪些是一段时间不会变更的,哪些是绝对不能缓存的很重要。
五:安装包(Apk瘦身)
1.so库的优化
大多数情况下只需要支持 armabi 与 x86 的架构即可。如果 非必须,可以考虑拿掉 x86 的部分
ndk {
//设置支持的so库架构
abiFilters "armeabi"
}
2.去除无用资源
END:前路漫漫