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

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

概述1.anr异常1.1anr是什么ApplicationNotResponding,应用程序无响应的d框1.2造成anr的原因应用程序的响应性是由ActivityManager和WindowManager系统服务监视的。当检测到Activity或者BroadcastReceiver中,5秒或10秒没有执行完任务之后,安卓就会d出ANR对话框,具体是两 1. anr异常1.1 anr是什么

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

1.2 造成anr的原因

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

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

造成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的问题recycleLRU计算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 布局优化尽量不要布局嵌套把invisible换成gone尽量使用weight替代长宽,来减少运算item存在非常复杂的嵌套时,可以考虑使用自定义的VIEw来取代,可以减少measure和layout的次数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 安卓的内存管理机制

分配机制:d性分配方式,为每个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中保存数据布局(减少布局深度)/ mainThread8. 其他优化8.1 在AndroID中,不要用静态变量存储数据静态变量等数据,由于进程已经被杀死而被初始化(数据不安全)使用其他数据传输方式:文件/sp/contentProvIDer8.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线程中做繁重的 *** 作

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

总结

以上是内存溢出为你收集整理的【Android】android异常与性能优化深度、高质量学习全部内容,希望文章能够帮你解决【Android】android异常与性能优化深度、高质量学习所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1024565.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存