【Android】android异常与性能优化深度、高质量学习

1. anr异常

1.1 anr是什么

Application Not Responding ,应用程序无响应的弹框

1.2 造成anr的原因

应用程序的响应性是由Activity Manager和WindowManager系统服务监视的。
当检测到Activity或者BroadcastReceiver中,5秒或10秒没有执行完任务之后,安卓就会弹出ANR对话框,具体是两种情况:

  1. 在Activity或Service中,5秒内无法相应用户的输入事件;
  2. 在BroadcastReceiver中,10秒内无法执行完任务。

造成anr的主要原因就是:主线程中做了耗时操作。

  1. 主线程被IO操作阻塞(从4.0之后,网络IO不允许在主线程中)
  2. 主线程中存在耗时的计算

哪些操作是在主线程呢?

  1. Activity所有生命周期回调,执行在主线程
  2. Service,执行在主线程(想做耗时操作可在IntentService中)
  3. BroadcastReceiver的onReceive回调,执行在主线程
  4. 没有使用子线程的looper的Handler的handleMessage,post(Runnable),执行在主线程
  5. AsyncTask的回调中,除了doInBackground,其他都是执行在主线程

1.3 如何解决anr

  1. 使用Asynctask处理耗时IO操作
  2. 使用Thread或HandlerThread提高优先级
  3. 使用handler来处理工作线程的耗时任务
  4. 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优化

  1. 图片显示:加载合适尺寸的图片,如果只需要缩略图就不去加载大图;在RecycleView滑动的时候不调用网络请求加载图片,监听到滑动停止的时候再加载
  2. 及时释放内存:java内存回收机制会不定期地回收垃圾。bitmap是通过bitmapFactory实例化bitmap的,bitmapFactory通过JNI生成bitmap对象,加载bitmap到内存后,包含java和c两部分内存区域,gc是java内存区域的垃圾回收机制,只能回收java的垃圾,不能回收c中的垃圾,所以及时释放内存,释放的是c中的内存。
  3. 图片压缩:将图片加载到内存之前,先计算一个合适的缩放比例,将图片压缩,避免加载大图
  4. inBitmap属性:就算有上千张要显示的图片,也只会占用系统能显示的数量的bitmap内存,类似于内存池
  5. 捕获异常:实例化bitmap时要对oom异常进行捕获。平时捕获的都是Exception,是异常,而oom是Error,此处要捕获Error。

2.3.2 其他优化

  1. listView:convertview / lru 。 对于包含大图的控件,要使用lru机制缓存bitmap。 lru是最近最少使用的机制,三级缓存机制。
  2. 避免在onDraw方法里,执行对象的创建。会引起大量内存抖动
  3. 谨慎使用多进程

3. bitmap的问题

  1. recycle
  2. LRU
  3. 计算inSampleSize
  4. 缩略图
  5. 三级缓存

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 原因分析

  1. 人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
  2. 布局layout过于复杂,无法在16ms内完成渲染;
  3. 同一时间动画执行的次数过多,导致cpu和gpu负载过重;
  4. View过度绘制,导致某些像素在同一帧时间内被绘制很多次,从而导致cpu和gpu负载过重;
  5. View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
  6. 内存频繁触发GC过多,导致暂时阻塞渲染操作;
  7. 冗余资源及逻辑等导致加载和执行缓慢;
  8. ANR。

4.3 如何优化

4.3.1 布局优化

  1. 尽量不要布局嵌套
  2. 把invisible换成gone
  3. 尽量使用weight替代长宽,来减少运算
  4. item存在非常复杂的嵌套时,可以考虑使用自定义的View来取代,可以减少measure和layout的次数

4.3.2 列表和Adapter优化

滑动时不加载图片,不更新item,可以只显示图片的默认值或缩略图
只有在滑动停止后,才加载和更新item

4.3.3 背景和图片等内存分配优化

不要设置过多背景
图片压缩处理

4.3.4 避免ANR

耗时任务放在子线程去处理
用android中的异步消息处理框架

5. 内存泄漏

内存泄漏检测工具:LeakCanary、MAT

内存溢出:(自身大小+申请大小)超出了虚拟机分配的内存大小

内存泄漏:某个不再使用的对象,由于被其他正在使用着的实例引用着,而无法被垃圾回收器回收,导致内存无法释放。 内存泄漏,是无用的量被有用的量引用着,而不是无用的量引用着有用的量,因为无用的量如果还引用着有用的量,那么它就不是个无用的量

为什么会内存泄漏:该被回收的对象不能被回收, 永远存在堆内存中。

android常见的内存泄漏:

  1. 单例
  2. handler 强引用
  3. 线程引起
    如AsyncTask、Runnable等,如果以匿名内部类等形式创建的话,会持有当前外部类Activity的引用,如果Runnable的耗时操作没有完成,Runnable就不会被gc回收,同时Runnable所在的Activity也无法被gc回收,导致内存泄漏。
    解决办法:同handler。1 使用静态内部类;2在activity被销毁的时候,取消runnable任务。
  4. webview
    打开2个网页,为了能快速回退,打开第二个网页的时候,第一个网页也不会被回收。
    最容易的处理方法,就是将webview单独放到一个进程中,当监测到某个进程占用内存过多的时候,系统会kill掉这个进程,同时回收这个进程中的内存。

单例传context不当引发的内存泄漏:

public class SingleInstance {
    private Context mContext;
    private static SingleInstance instance;

	构造函数是privateprivate 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 内存管理机制概述

  1. 分配机制 :操作系统会为每个进程分配合理的内存大小,保证每个进程能够正常地运行,避免内存不够用或内存占用过多的现象。
  2. 回收机制:系统内存不足时,有合理的回收和再分配内存的机制。回收时,就要杀死那些正在占有内存的进程,操作系统需要提供一个合理的杀死进程的机制,将副作用降到最低。

6.2 安卓的内存管理机制

  1. 分配机制:弹性分配方式,为每个app的进程分配一个小额的量(根据移动端设备的实际room大小决定)。随着app运行,当前内存可能不够用了,Android会分配额外的内存大小(这是有限制的)。
    原则是:让更多进程存活在内存中。这样下次打开app,不需要重新创建进程,只要恢复已有进程即可。这样能减少应用加载时间,提高用户体验。

  2. 回收机制:

进程分类:

  1. 前台进程:可见可操作
  2. 可见进程:可见不可操作
  3. 服务进程
  4. 后台进程
  5. 空进程

优先级越低的进程,被杀死的概率越大。

前台进程、可见进程、服务进程,正常情况下不会被杀死。

后台进程,被存放在缓存列表中(LRU,最近最少使用),处于列表尾部的进程会先被杀死。

空进程,空进程存在的意义是为了平衡整个系统的性能。

回收效益:android更倾向于杀死一个能回收更多内存的进程,杀死的进程越少对用户影响越少。

6.3 内存管理机制的目标

  1. 更少的占用内存
  2. 在合适的时候,合理释放系统资源。(不能频繁释放对象,内存抖动)
  3. 在系统内存紧张时,能释放掉大部分不重要的资源,为系统提供可用内存
  4. 能合理的在特殊的生命周期中,保存或还原重要数据,以至于系统能正确恢复应用

6.4 内存优化的方法

  1. service完成任务后,尽量停止它。可尽量用IntentService替代service,intentservice有两个好处:1可执行耗时操作;2执行完成后会自己退出
  2. 在UI不可见时,释放一些只有UI使用的资源
  3. 内存紧张时,尽可能多的释放一些非重要资源
  4. 避免滥用bitmap导致的内存浪费
  5. 使用针对内存优化过的数据容器
  6. 避免使用依赖注入的框架
  7. 使用zip对齐的apk
  8. 使用多进程(把消耗过大的模块,和长期在后台执行的模块,移入单独的进程中。开启定位进程,开启消息推送进程)

7. 冷启动优化

7.1 什么是冷启动

1 冷启动定义:
在启动app前,系统中没有该应用的任何进程信息。

2 冷启动和热启动区别:
热启动:用户使用返回键退出应用,然后又进入应用。

启动特点:冷启动,会先创建和初始化Application类,再创建和初始化MainActivity类,开始测量、布局、绘制等,最后显示在界面上;热启动,从已有的进程来启动,所以不会创建和初始化Application类,而是直接创建和初始化MainActivity,开始测量、布局、绘制等,最后显示在界面上。

3 冷启动时间计算:
从应用启动(创建进程)开始计算,到完成视图的第一次绘制(即Activity内容对用户可见)为止。

7.2 冷启动流程

在这里插入图片描述
在这里插入图片描述

7.3 对冷启动时间的优化

  1. 减少onCreate方法的工作量
  2. 不要让Application参与业务操作
  3. 不要让Application进行耗时操作
  4. 不要以静态变量的方式在Application中保存数据
  5. 布局(减少布局深度)/ mainThread

8. 其他优化

8.1 在Android中,不要用静态变量存储数据

  1. 静态变量等数据,由于进程已经被杀死而被初始化(数据不安全)
  2. 使用其他数据传输方式:文件/sp/contentProvider

8.2 sharedpreference

  1. 不能跨进程同步(每个进程都会维护一份自己的sp副本,只有在进程结束的时候才能写到文件系统中)
  2. 存储sp的文件过大问题(从sp获取值的时候,可能会阻塞主线程,甚至可能UI卡顿;读写频繁的key和不易变动的key,最好也不要放在一起,容易影响速度;key-value的值永远存在内存中,很耗内存。)

Android五大存储方式:网络,数据库,文件,sharedpreference,contentProvider。

8.3 内存对象序列化

序列化:将对象的状态信息,转化为可以存储或传输的形式的过程。

让对象实现Serializable:java中的序列化接口,用Serializable序列化对象的时候,会产生大量的临时对象,触发大量的垃圾回收,引发内存抖动和UI卡顿,甚至导致oom。

让对象实现Parcelable:Android中的序列化接口,性能更好。不能使用Parcelable去序列化保存在磁盘上的对象,它不是通用的序列化机制,它的本质是为了更好的让对象在进程间通信。

对比:
在这里插入图片描述

8.4 避免在UI线程中做繁重的操作

读取数据库,网络信息,都放到子线程中去。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值