Android设备作为一种移动设备,无论是内存还是CPU的性能都受到了很大的限制,这导致Android程序的性能问题异常突出,对于性能优化提出了更高的要求。本文就跟大家一起探索一下我们日常开发APP有什么可以注意的点,让我们的APP可以拥有更流畅,更好的用户体验.
1.布局优化
- 布局复用,使用<include>标签重用layout;
- 提高显示速度,使用<ViewStub>延迟View加载,有一些比如错误页面不一定要显示的就可以使用这个,有需要的时候在加载出来;
- 减少层级,使用<merge>标签替换父级布局;
- 注意使用wrap_content,会增加measure计算成本;
- 删除控件中无用属性;
- 注意使用合理的控件,当一样层级可以实现的时候效率FrameLayout>LinearLayout>RelativeLayout,当然如果使用RelativeLayout可以减少层级嵌套那也是值得的.尽量使用ConstraintLayout这个约束布局,基本可以保证层级只有一层
- 减少重绘,移除window的背景,减少布局中不必要的背景设置
对于检查布局层级,Android提供了一个很好用的工具HierarchyViewer,位于Android SDK/tools/hierarchyviewer.bat 可以很方便的看我们布局的层级结构
2.内存优化
内存优化应该是Android优化中最重要的点了,我们着重讲这块
讲内存优化之前我们先来简单的看看JVM中的垃圾回收机制:
我们知道JVM的内存模型主要分为五块
我们一般说的内存优化一般是优化堆内存,JVM特有的垃圾回收机制GC,当堆内存不足的时候就会触发垃圾回收,将无用对象给回收掉,执行GC操作的时候,所有线程的任何操作都会需要暂停,等待GC操作完成之 后,其他操作才能够继续运行。内存泄漏 会导致应用剩余可用Heap Size越来越小,这样会导致频繁触发GC。通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染)(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了,从而导致丢帧,用户也会发现应用卡顿。
常见的内存泄露:
(1)单例造成的内存泄漏
public class SingleInstance {
private Context mContext;
private static SingleInstance instance;
private SingleInstance(Context context) {
this.mContext = context;
}
public static SingleInstance getInstance(Context context) {
if (instance == null) {
instance = new SingleInstance(context);
}
return instance;
}
public void say() {
Log.i("tag", "this is single instance");
Log.i("tag", ":code:" + instance.hashCode());
}
}
这是一个很普通的单例模式,但是这会造成内存泄露,如果在一个Activity中直接调用SingInstance.getInstance(this)就会造成内存泄露,这是因为生命周期长的对象引用了生命周期短的对象,导致这个activity的对象无法被回收,我们应该改为使用全局的上下文来获取单例对象
(2)Handler造成的内存泄漏
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
直接在activity中使用Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,消息队列是在一 个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏
应该改为:
private MyHandler myHandler = new MyHandler(this);
class MyHandler extends Handler{
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity context = (MainActivity) reference.get();
if (context !=null){
....
}
}
}
@Override
protected void onDestroy() {
myHandler.removeCallbacksAndMessages(null);
}
(3)线程造成的内存泄漏
new AsyncTask<Void,Void,Void>(){
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(1000);
return null;
}
};
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(1000);
}
});
这类会因为activity销毁的时候定时任务还在执行会造成内存泄露,解决方法就是在Activity销毁时候也应该取消相应的任务,避免任务在后台执行浪费资源;采用rxJava处理异步事件,然后将rxJava的生命周期与Activity的生命周期进行绑定
(4)资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏,应该在activity销毁的时候及时回收资源
(5)注册了系统的服务,但onDestory未注销
一些广播,比如EventBus,BroadcastReceiver,传感器之类的,注册的时候记得在onDestory的时候解除注册.
对于内存泄露我们主要有这些工具可以排查:
- android studio自带的Moniters
- 项目引入leakcanary
- 利用mat(Memory Analysis Tools)工具分析
内存优化另一个重要点的是OOM即(Out Of Memoery),顾名思义就是指内存溢出了内存溢出是指APP向系统申请超过最大阀值的内存请求,系统不会再分配多余的空间,就会造成OOM error。在我们Android平台下,多数情况是出现在图片不当处理加载的时候.
有人说Android的APP所能申请到的内存是16M有的说是32M,其实这都是不一定的,Java为我们提供了单例获取的方式Runtime.getRuntime()就可以获取到内存空间的大小
我们知道图片的所占内存计算: 内存 = 图片长度 * 图片宽度 * 每个像素所占的内存
1. ALPHA_8:每个像素点占用1byte内存
2. ARGB_4444:每个像素点占用2byte内存
3. ARGB_8888: 每个像素点占用4byte内存
4. RGB_565: 每个像素点占用2byte内存
如果一张3000*3000的ARGB_8888的图片所占的内存即:3000*3000*4/1024/1024 ≈ 34.33M,这个内存就很有可能会造成内存溢出了,而一张3000*3000的RGB_565的图片所占的内存即:3000*3000*2/1024/1024 ≈ 17.16M,所以相同的情况下如果对图片的要求不是特别高的话建议使用RGB_565,效果差别不是很明显,能有效减少内存.
加载本地图片的时候使用流的方式加载会比较省内存
public static Bitmap readBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
//获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
对于网络图片还不知道尺寸大小的可以先采用BitmapFactory.Options的inJustDecodeBounds属性来获取图片的属性,然后在根据控件的大小对图片进行压缩到我们所需要的大小.最后在加载网络图片的时候最好是采用缓存,常见的内存缓存LruCache,磁盘缓存DisLruCache,底层都是基于LinkedHashMap,LinkedHashMap采用的双向链表实现近期最少使用的算法,当内存满的时候优先删除近期少使用的.现在市面上的第三方加载图片的框架大多都是基于此算法
关于图片的优化还有一个比较重要的点,那就是加载巨图长图的时候,又不能压缩,要能滑动观看全部,这个时候怎么办呢?
这个时候就要用到:BitmapRegionDecoder这个类了
这个类可以根据设置的区域加载图片,实现按照所需的范围加载图片
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
关于内存优化还有几个要注意的点:
- 自定义View的时候如果有重叠的就使用canvas.clipRect显示绘制区域,只显示看得到的那部分
- 自定义View的时候不要在onDrow执行耗时操作,不要重复创建大量对象,因为onDrow方法会多次执行
- 使用ListView,RecyclerView的时候注意复用,滑动的时候停止图片的加载已到达流畅滑动的效果
3.APK体积优化
(1) 使用Lint清理无用资源
(2) 包体分析
(3)开启Gradle去除无用资源
(4)使用tinypng有损压缩
(5)使用微信资源压缩打包工具
(6)
(7) FaceBook的redex优化字节码方案
好了,今天就讲到这里了,希望对大家有所帮助.