Android异常与性能优化
ANR
什么是ANR
Application Not Responding
Activity:5s
BroadcastReceiver:10s
Service:20S
造成ANR的主要原因
应用程序的响应性是由Activity Manager和WindowManager系统服务监视的
- 主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞
- 主线程中存在耗时的计算
造成anr的主要原因-Android中哪些操作是在主线程呢?
1. Activity的所有生命周期回调都是执行在主线程的
2. Service默认是执行在主线程的
3. BroadcastReceiver的onReceive()回调是执行在主线程的
4. 没有使用子线程的looper的Handler的handleMessage,post(Runnable)是执行在主线程的。
5. AsyncTask的回调中除了doInBackground,其他都是执行在主线程的
如何解决ANR
- 使用AsyncTask处理耗时IO操作
- 使用Thread或者HandlerThread提高优先级
- 使用handler来处理工作线程的耗时任务
- Activity的onCreate()和onResume()回调中尽量避免耗时的代码
OOM
什么是OOM
当前占用的内存加上我们申请的内存资源超过了DVM的最大内存限制就会抛出Out Of Memory异常。
一些容易混淆的概念
内存溢出(OOM)/内存抖动(频繁的gc,积累到一定程度,也会造成OOM)/内存泄漏(垃圾对象没有被引用,却还持有未被回收对象的引用,严重到一定程度,会造成OOM)
如何解决OOM
有关bitmap
- 图片显示(例如ListView滑动停止时才去加载)
- 及时释放内存(bitmap产生两块内存,一块是Java内存区域,一块是C内存区域,前者通过GC回收,C的应由调用recycle()回收)
- 图片压缩
- inBitmap属性(提高分配与释放效率,复用之前bitmap占用的堆内存,即有成百上千的图片,也只会占用屏幕能放下图片的内存)
- 捕获异常(是Error,不是Exception)
其他方法
- listview:convertview/lru
- 避免在onDraw方法里面执行对象的创建,否则容易造成内存抖动
- 谨慎使用多进程,业务多的时候才使用,否则容易造成开发难度
Bitmap
recycle
bitmap产生两块内存,一块是Java内存区域,一块是C内存区域,前者通过GC回收,C的应由调用recycle()回收
LRU
内部用一个HashMap实现,trimToSize():删除最老最不常用的对象,同时把最新的加入HashMap中,LRU的核心方法
计算inSampleSize
`// 根据maxWidth, maxHeight计算最合适的inSampleSize
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}`
缩略图
//缩略图
public static Bitmap thumbnail(String path,
int maxWidth, int maxHeight, boolean autoRotate) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 获取这个图片的宽和高信息到options中, 此时返回bm为空
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
options.inJustDecodeBounds = false;
// 计算缩放比
int sampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
options.inInputShareable = true;
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = BitmapFactory.decodeFile(path, options);
return bitmap;
}
三级缓存
保存:网络-本地-内存
读取:内存-本地-网络
UI卡顿
UI卡顿原理
60fps -> 16ms(Android每隔16ms绘制UI,达到60fps的要求)
overdraw(每个像素区域被绘制多次,布局中有太多重叠的部分,eg子父类有自己的background)
原因分析
- 人为在UI线程中做轻微耗时操作,导致UI线程卡顿
- 布局Layout过于复杂,无法在16ms内完成渲染
- 同一时间动画执行的次数过多,导致CPU或GPU负载过重
- View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或者GPU负载过重
- View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染
- 内存频繁触发GC过多,导致暂时阻塞渲染操作
- 冗余资源以及逻辑等导致加载和执行缓慢
- ANR
总结
- 布局优化。尽量使用include、merge标签、尽量不要多层嵌套、使用gone代替invisible、使用weight替代长和宽、item若存在复杂的嵌套时使用自定义View替代。
- 列表以Adapter优化,列表停止滑动时,才去加载
- 背景和图片等内存分配优化,减少不必要的背景设置,图片要压缩
- 避免ANR
内存泄漏
Java内存泄漏基础知识
内存泄漏其实就是该被释放的对象,没有被释放。
1. Java内存的分配策略
- 静态存储区(也叫方法区)。存储静态数据,全局变量等
- 栈区。方法的局部变量,容量有限
- 堆区。存储new出来的对象、数组,gc会回收
Java是如何管理内存的
不再使用的,用gc回收Java中的内存泄漏
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存控件的浪费。
Android内存泄漏
单例
因为单例的static属性,造成它的生命周期与App一样长,如果单例中持有不需要再使用对象的引用,那么该对象将无法被回收,造成内存泄漏。例如单例的方法中传入Context,修改方法是使用AppcationContext。
匿名内部类
非静态内部类会持有外部类的引用,若该内部类中有static的成员,会导致跟App生命周期一样长。正确的做法是改为静态内部类
handler
非静态内部类
避免使用static变量
导致跟App生命周期一样长。在类设计的时候,要充分考虑是否设置为static
资源未关闭造成的内存泄漏
AsyncTask造成的内存泄漏
与handler的原因类似
内存管理
内存管理机制概述
- 分配机制
- 回收机制
Android内存管理机制
分配机制
最大限度的让更多进程存活在内存中
回收机制
根据进程优先级进行回收
内存管理机制的特点
开发的目标:
- 更少的占用内存
- 在合适的时候,合理的释放系统资源
- 在系统内存紧张的情况下,能释放掉大部分不重要的资源,来为Android系统提供可用的内存。
- 能够很合理的在特殊生命周期中,保存或者还原重要数据,以致于系统能够正确的重新回复该应用
内存优化方法
- 当Service完成任务后,尽量停止它。(能用IntentService急用)
- 当UI不可用的时候,释放掉一些只有UI使用的资源
- 在系统内存紧张的时候,尽可能多的释放掉一些非重要的资源
- 避免滥用Bitmap导致的内存浪费
- 使用针对内存优化过的数据容器和数据结构(如避免使用枚举,ArrayMap代替HashMap)
- 避免使用依赖注入框架
- 使用ZIP对齐的APK
- 使用多进程,如使用WebView、地图、推送,开启单独的进程
内存溢出 VS 内存泄漏
内存泄漏积累会导致内存溢出。内存泄漏可以使用LeakCanary
冷启动优化
什么是冷启动
定义
冷启动就是在启动应用前,系统中没有该应用的任何进程信息
冷启动/热启动的区别
热启动:用户使用返回键退出应用,然后马上又重新启动应用。不会走Application,冷启动会走。
冷启动时间的计算
这个时间值从应用启动(创建进程)开始计算,到完成视图的第一次绘制(即Activity内容对用户可见)为止
冷启动流程
- Zygote进程中fork创建出一个新的进程
- 创建和初始化Application类、创建MainActivity类
- inflate布局、当onCreate/onStart/onResume方法都走完
- contentView的measure/layout/draw显示在界面上
Application的构造方法 –> attachBaseContext() –> onCreate() –> Activity的构造方法 –> onCreate() –>配置主题中背景等属性 –> onStart() –> onResume() –> 测量布局绘制显示在界面上
如何对冷启动的时间进行优化
- 减少Application onCreate()方法的工作量,减少第三方类库的初始化工作,懒加载
- 不要让Application参与业务的操作
- 不在Application进行耗时操作
- 不要以静态变量的方式在Application中保存数据
- 减少布局的复杂性,尽量ViewStub/mainThread
其他优化
android不用静态变量存储数据
- 静态变量等数据由于进程已经被杀死而被初始化
- 使用其他数据传输方式:文件/sp/contentProvider
有关sp的安全问题
- 不能跨进程同步
- 存储SP的文件过大问题(存储配置型的数据),可能造成卡顿
内存对象序列化
序列化:将对象的状态信息转换为可以存储或传输的形式的过程
1. Serializeble:会产生大量的临时变量,引起频繁的gc,影响UI性能
2. Parcelable:性能比Serializeble高,不能使用在要将数据存储在磁盘上的情况
避免在UI线程中做繁重的操作
老生常谈啦