1. anr异常
1.1 anr是什么
Application Not Responding ,应用程序无响应的弹框
1.2 造成anr的原因
应用程序的响应性是由Activity Manager和WindowManager系统服务监视的。
当检测到Activity或者broadcastReceiver中,5秒或10秒没有执行完任务之后,安卓就会弹出ANR对话框,具体是两种情况:
造成anr的主要原因就是:主线程中做了耗时操作。
- 主线程被IO操作阻塞(从4.0之后,网络IO不允许在主线程中)
- 主线程中存在耗时的计算
哪些操作是在主线程呢?
- Activity所有生命周期回调,执行在主线程
- Service,执行在主线程(想做耗时操作可在IntentService中)
- broadcastReceiver的onReceive回调,执行在主线程
- 没有使用子线程的looper的Handler的handleMessage,post(Runnable),执行在主线程
- AsyncTask的回调中,除了doInBackground,其他都是执行在主线程
1.3 如何解决anr
- 使用Asynctask处理耗时IO操作
- 使用Thread或HandlerThread提高优先级
- 使用handler来处理工作线程的耗时任务
- Activity的onCreate和onResume中不要做耗时操作
2. oom异常
2.1 oom是什么?
Out of meory
当前占用的内存,加上我们申请的内存资源,超过leDalvik虚拟机的最大内存限制,就会抛出oom异常。
2.2 易混淆的概念
内存溢出:oom
内存抖动:短时间内大量对象被创建和释放,会触发gc垃圾回收
内存泄漏:无用的量被有用的量引用,导致无用的量无法被垃圾回收,太多的内存泄漏也会导致oom
2.3 如何解决oom?
2.3.1 有关bitmap优化
- 图片显示:加载合适尺寸的图片,如果只需要缩略图就不去加载大图;在RecycleView滑动的时候不调用网络请求加载图片,监听到滑动停止的时候再加载
- 及时释放内存:java内存回收机制会不定期地回收垃圾。bitmap是通过bitmapFactory实例化bitmap的,bitmapFactory通过JNI生成bitmap对象,加载bitmap到内存后,包含java和c两部分内存区域,gc是java内存区域的垃圾回收机制,只能回收java的垃圾,不能回收c中的垃圾,所以及时释放内存,释放的是c中的内存。
- 图片压缩:将图片加载到内存之前,先计算一个合适的缩放比例,将图片压缩,避免加载大图
- inBitmap属性:就算有上千张要显示的图片,也只会占用系统能显示的数量的bitmap内存,类似于内存池
- 捕获异常:实例化bitmap时要对oom异常进行捕获。平时捕获的都是Exception,是异常,而oom是Error,此处要捕获Error。
2.3.2 其他优化
- listView:convertview / lru 。 对于包含大图的控件,要使用lru机制缓存bitmap。 lru是最近最少使用的机制,三级缓存机制。
- 避免在onDraw方法里,执行对象的创建。会引起大量内存抖动
- 谨慎使用多进程
3. bitmap的问题
- recycle
- LRU
- 计算inSampleSize
- 缩略图
- 三级缓存
3.1 recycle 回收
回收,分为两部分,回收java内存,回收native内存
源码注释对于该方法的建议是,如无特殊需要可不调用recycle方法,因为当没有任何引用指向这个bitmap的时候,系统gc会自动回收这部分内存。
3.2. LRU
LRU(Least Recently Used)算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
那就是利用链表和hashmap。
当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。
在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。
可以利用双向链表,并提供head指针和tail指针,这样一来,所有的操作都是O(1)时间复杂度。
Android中:
LruCache,是通过范型类,并用LinkedHashMap来实现的,同时提供给我们get和put方法来拿到和添加缓存对象,LruCache类中最重要的方法是trimToSize方法,会将历史最久且使用频率最低的移出,取而代之是将新的缓存对象添加进去。
利用trimToSize(int),将最老的(历史久远)最不常用(频率低)的bitmap对象从队列中删除,将对象数量调整为指定值;
利用put(K, V),将bitmap对象,添加到队列中;
利用remove(K),将bitmap对象,从队列中删除。
3.3. 计算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) {
final int halfheight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfheight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
官网链接:https://developer.android.com/topic/performance/graphics/load-bitmap?hl=zh-cn
3.4. 缩略图
先options.inJustDecodeBounds = true;加载,但是不加载到内存;
再options.inJustDecodeBounds = false;加载缩略图到内存。
public static Bitmap decodeSampledBitmapFromresource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
3.5. 三级缓存
网络缓存:速度慢,不是优先加载
本地缓存
内存缓存:速度最快,优先加载
app首次缓存图片时,会从网络中请求这个图片资源(网络缓存)。
当图片加载成功后,会在本地和内存各缓存一份(本地缓存+内存缓存)。
再次通过app请求相同图片时(url相同),会直接从内存缓存或本地缓存中去找,而不会走网络缓存,减少流量耗费。
4. UI卡顿
4.1 UI卡顿原理
卡顿,说明渲染性能没跟上。
所有卡顿的根源,在于安卓系统的渲染性能。做了太多耗时操作,有可能是layout太复杂,也有可能是层叠了太多layout,动画执行次数过多。
问题1:
60fps——》16ms
安卓系统,每隔16ms会触发对界面进行渲染,如果每次渲染都成功,就能达到每秒60帧刷新,就很流畅。如果某些地方很复杂,产生丢帧现象,就会卡顿。
问题2:
overdraw 过度绘制。
某个像素,在同一帧的时间内,被绘制了很多次,常出现在多层次UI结构里。
在开发者设置中,可以查看GPU绘制情况,颜色越红说明同一个像素点绘制次数越多,尽量减少红色,增加蓝色。
4.2 原因分析
- 人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
- 布局layout过于复杂,无法在16ms内完成渲染;
- 同一时间动画执行的次数过多,导致cpu和gpu负载过重;
- View过度绘制,导致某些像素在同一帧时间内被绘制很多次,从而导致cpu和gpu负载过重;
- View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
- 内存频繁触发GC过多,导致暂时阻塞渲染操作;
- 冗余资源及逻辑等导致加载和执行缓慢;
- ANR。
4.3 如何优化
4.3.1 布局优化
4.3.2 列表和Adapter优化
滑动时不加载图片,不更新item,可以只显示图片的默认值或缩略图
只有在滑动停止后,才加载和更新item
4.3.3 背景和图片等内存分配优化
不要设置过多背景
图片压缩处理
4.3.4 避免ANR
耗时任务放在子线程去处理
用android中的异步消息处理框架
5. 内存泄漏
内存泄漏检测工具:LeakCanary、MAT
内存溢出:(自身大小+申请大小)超出了虚拟机分配的内存大小
内存泄漏:某个不再使用的对象,由于被其他正在使用着的实例引用着,而无法被垃圾回收器回收,导致内存无法释放。 内存泄漏,是无用的量被有用的量引用着,而不是无用的量引用着有用的量,因为无用的量如果还引用着有用的量,那么它就不是个无用的量
为什么会内存泄漏:该被回收的对象不能被回收, 永远存在堆内存中。
android常见的内存泄漏:
- 单例
- handler 强引用
- 线程引起
如AsyncTask、Runnable等,如果以匿名内部类等形式创建的话,会持有当前外部类Activity的引用,如果Runnable的耗时操作没有完成,Runnable就不会被gc回收,同时Runnable所在的Activity也无法被gc回收,导致内存泄漏。
解决办法:同handler。1 使用静态内部类;2在activity被销毁的时候,取消runnable任务。 - webview
打开2个网页,为了能快速回退,打开第二个网页的时候,第一个网页也不会被回收。
最容易的处理方法,就是将webview单独放到一个进程中,当监测到某个进程占用内存过多的时候,系统会kill掉这个进程,同时回收这个进程中的内存。
单例传context不当引发的内存泄漏:
public class SingleInstance {
private Context mContext;
private static SingleInstance instance;
构造函数是private的
private SingleInstance(Context context) {
this.mContext = context;
}
全局通过getInstance方法才能拿到单例对象
public static SingleInstance getInstance(Context context) {
if (instance == null) {
instance = new SingleInstance(context);
}
return instance;
}
}
传入SingleActivity参数获取单例对象,那么当这个SingleActivity退出的时候,SingleActivity仍无法被回收,因为这个SingleActivity被单例持有(长生命周期的对象,持有了短生命周期的对象的引用,导致短生命周期对象无法被回收),单例的生命周期和app相同:
public class SingleActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SingleInstance instance = SingleInstance.getInstance(SingleActivity.this);
}
}
为了解决这个问题,只需传入一个getApplicationContext,getApplicationContext的生命周期和整个app相同,单例也是。以后传入context可以传递getApplicationContext:
public class Single1Activity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SingleInstance instance = SingleInstance.getInstance(getApplicationContext());
}
}
handler引发的内存泄漏:
Handler是非静态内部类,定义在这里,会持有外部类HandlerLeakActivity的引用。
handler每隔10分钟发送一次消息,会一直存活,这就影响了gc对HandlerLeakActivity的回收,从而导致内存泄漏。
public class HandlerLeakActivity extends Activity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,10*60*1000);
}
}
如果handler中有尚未被处理和发送的message,那么handler就会一直存活,而Message会持有handler的引用,handler会持有外部类Activity的引用,所以,内存泄漏。
解决:
// 1 避免使用handler的非静态内部类,声明成static的
// 2 重写handler,通过弱引用的方式使用activity
// 3 被延时处理的message持有了handler的引用,handler又持有了activity的引用。在activity被摧毁的时候尝试移除message
public class HandlerLeak1Activity extends Activity {
// 1 避免使用handler的非静态内部类,声明成static的
private static NoLeakHandler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,10*60*1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 3 被延时处理的message持有了handler的引用,handler又持有了activity的引用
// 在activity被摧毁的时候尝试移除message
mHandler.removeCallbacksAndMessages(null);
}
// 2 重写handler,通过弱引用的方式使用activity
private static class NoLeakHandler extends Handler{
private WeakReference<HandlerLeak1Activity> mActivity;
public NoLeakHandler(HandlerLeak1Activity activity){
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
6 内存管理
6.1 内存管理机制概述
- 分配机制 :操作系统会为每个进程分配合理的内存大小,保证每个进程能够正常地运行,避免内存不够用或内存占用过多的现象。
- 回收机制:系统内存不足时,有合理的回收和再分配内存的机制。回收时,就要杀死那些正在占有内存的进程,操作系统需要提供一个合理的杀死进程的机制,将副作用降到最低。
6.2 安卓的内存管理机制
-
分配机制:弹性分配方式,为每个app的进程分配一个小额的量(根据移动端设备的实际room大小决定)。随着app运行,当前内存可能不够用了,Android会分配额外的内存大小(这是有限制的)。
原则是:让更多进程存活在内存中。这样下次打开app,不需要重新创建进程,只要恢复已有进程即可。这样能减少应用加载时间,提高用户体验。 -
回收机制:
进程分类:
优先级越低的进程,被杀死的概率越大。
前台进程、可见进程、服务进程,正常情况下不会被杀死。
后台进程,被存放在缓存列表中(LRU,最近最少使用),处于列表尾部的进程会先被杀死。
空进程,空进程存在的意义是为了平衡整个系统的性能。
回收效益:android更倾向于杀死一个能回收更多内存的进程,杀死的进程越少对用户影响越少。
6.3 内存管理机制的目标
- 更少的占用内存
- 在合适的时候,合理释放系统资源。(不能频繁释放对象,内存抖动)
- 在系统内存紧张时,能释放掉大部分不重要的资源,为系统提供可用内存
- 能合理的在特殊的生命周期中,保存或还原重要数据,以至于系统能正确恢复应用
6.4 内存优化的方法
- service完成任务后,尽量停止它。可尽量用IntentService替代service,intentservice有两个好处:1可执行耗时操作;2执行完成后会自己退出
- 在UI不可见时,释放一些只有UI使用的资源
- 内存紧张时,尽可能多的释放一些非重要资源
- 避免滥用bitmap导致的内存浪费
- 使用针对内存优化过的数据容器
- 避免使用依赖注入的框架
- 使用zip对齐的apk
- 使用多进程(把消耗过大的模块,和长期在后台执行的模块,移入单独的进程中。开启定位进程,开启消息推送进程)
7. 冷启动优化
7.1 什么是冷启动
1 冷启动定义:
在启动app前,系统中没有该应用的任何进程信息。
2 冷启动和热启动区别:
热启动:用户使用返回键退出应用,然后又进入应用。
启动特点:冷启动,会先创建和初始化Application类,再创建和初始化MainActivity类,开始测量、布局、绘制等,最后显示在界面上;热启动,从已有的进程来启动,所以不会创建和初始化Application类,而是直接创建和初始化MainActivity,开始测量、布局、绘制等,最后显示在界面上。
3 冷启动时间计算:
从应用启动(创建进程)开始计算,到完成视图的第一次绘制(即Activity内容对用户可见)为止。
7.2 冷启动流程
7.3 对冷启动时间的优化
- 减少onCreate方法的工作量
- 不要让Application参与业务操作
- 不要让Application进行耗时操作
- 不要以静态变量的方式在Application中保存数据
- 布局(减少布局深度)/ mainThread
8. 其他优化
8.1 在Android中,不要用静态变量存储数据
- 静态变量等数据,由于进程已经被杀死而被初始化(数据不安全)
- 使用其他数据传输方式:文件/sp/contentProvider
8.2 sharedpreference
- 不能跨进程同步(每个进程都会维护一份自己的sp副本,只有在进程结束的时候才能写到文件系统中)
- 存储sp的文件过大问题(从sp获取值的时候,可能会阻塞主线程,甚至可能UI卡顿;读写频繁的key和不易变动的key,最好也不要放在一起,容易影响速度;key-value的值永远存在内存中,很耗内存。)
Android五大存储方式:网络,数据库,文件,sharedpreference,contentProvider。
8.3 内存对象序列化
序列化:将对象的状态信息,转化为可以存储或传输的形式的过程。
让对象实现Serializable:java中的序列化接口,用Serializable序列化对象的时候,会产生大量的临时对象,触发大量的垃圾回收,引发内存抖动和UI卡顿,甚至导致oom。
让对象实现Parcelable:Android中的序列化接口,性能更好。不能使用Parcelable去序列化保存在磁盘上的对象,它不是通用的序列化机制,它的本质是为了更好的让对象在进程间通信。
对比:
8.4 避免在UI线程中做繁重的操作
读取数据库,网络信息,都放到子线程中去。