一. ANR异常
- 什么是ANR: 在Android中,如果应用程序有一段时间响应不够灵敏,系统会向用户显示应用无响应对话框(ANR:Application Not Responding)。用户可以选择让程序继续运行或者关闭程序。
- ANR产生的原因:主线程中被耗时操作阻塞(网络请求、IO流读取等操作)。
-
怎样避免ANR:
- 使用Asynctask处理耗时IO操作。(灵活切换线程)
- 使用Thread或者HandlerThread 提高优先级。
- 使用Handler来处理工作线程的耗时任务。
- Activity的onCreate和onResume回调中尽量避免耗时的代码。
-
Android哪些操作是在主线程中:
- Activity的所有生命周期回调都是执行在主线程的。
- Service默认是执行在主线程的。
- BroadCastReceiver的onReceive回调是执行在主线程的。
- 没有使用子线程的Looper的Handler的handleMessage、post(Runnable)是执行在主线程的。
- AsyncTask的回调除了 doInBackground,其他都是执行在主线程。
- ContentProvider的onCreate()是运行在UI线程的,而query(),insert(),delete()和update()是运行在线程池中的工作线程的。
二、OOM内存溢出异常
什么是OOM:当前占用的内存加上申请的内存资源超过了 Dalvik 虚拟机的最大内存限制就会抛出的 Out of Memory 异常。(堆内存上有些资源没被释放,从而失去控制,造成程序使用的内存越来越少,导致程序运行速度减慢,严重情况会导致程序的奔溃。)
-
为什么会OOM,如何解决:
- 瞬时加载了大内存的资源,例如:视频、图片和音频等等的内存申请大小超过了App的额定内存值,解决方案:对资源进行压缩处理。
- Bitmap对象及时recycle()释放内存,解决方案:在Bitmap不在需要被加载到内存中的时候,做回收处理。
- 未关闭 InputStream/OutputStream , 解决方案:在使用到I/O 流的时候,及时关闭。
- 调用 registerRecevier() 动态注册后未调用 unregisterReceiver(), 解决方案:在每一次动态注册的时候,记得在onStop/onDestroy取消注册。
- 查询数据库后没有关闭游标cursor,解决方案:使用完cursor及时关闭。
- 构造Adapter没有使用缓存 contentview重用, 解决方案:在构造Adapter的时候,使用ContentView缓存页面,节省内存。
- 减少onDraw的执行次数。
- Context泄露,内部类持有外部类的引用。 解决方案: 第一: 将线程的内部类,改为静态内部类 第二:在线程内部采用弱引用保存Context引用。
- static 原因。 解决方案:(1)应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。(2)Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。(3)使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;(4)查找内存泄漏可以使用Android Profiler工具或者利用LeakCanary工具。
-
为什么Android会有APP的内存限制:
- 要开发者使用内存更加合理。限制每个应用可用内存上限,避免恶意程序或单个程序使用过多内存导致其他程序的不可运行。有了限制,开发者就必须合理使用资源,优化资源使用
- 屏幕显示内容有限,内存足够即可。即使有万千图片千万数据需要使用到,但在特定时刻需要展示给用户看的总是有限的,因为屏幕显示就那么大,上面可以放的信息就是很有限的。大部分信息都是处于准备显示状态,所以没必要给予太多heap内存。必须一个ListView显示图片,打个比方这个ListView含有500个item,但是屏幕显示最多有10调item显示,其余数据是处于准备显示状态。
- Android多个虚拟机Davlik的限制需要。android设备上的APP运行,每打开一个应用就会打开至少一个独立虚拟机。这样可以避免系统崩溃,但代价是浪费更多内存。
一些容易混淆的概念:
- 内存溢出:当前占用的内存加上申请的内存资源超过了 Dalvik 虚拟机的最大内存限制就会抛出的 Out of Memory 异常。(非常恐怖)
- 内存抖动:短时间内大量的对象被创建,然后又被马上释放,瞬间产生的对象会严重占用内存区域,随即触发GC回收。
- 内存泄露:进程中某些对象,它没有被其他地方引用到了,但是他们可以直接或间接引用到其他还没被回收的对象,导致GC回收无法产生作用。一旦内存泄露达到一定的程度,就会造成内存溢出OOM。
三、Bitmap异常
- recycle:内存回收,释放C内存中的内存。
- Lru算法:最近最少使用的缓存给清理,主要是用put/get方法存入缓存,当缓存快要满的时候用trimToSize清理缓存。
- 计算inSampleSize:合适的缩放比例来加载图片到内存,避免OOM。
- 缩略图:inJustDecodeBounds。
- 三级缓存:网络、本地和内存,当App首次加载一张图片的时候,会从网络中请求图片加载,当图片请求成功后会把这个Bitmap保存到 SD卡和内存中各一份,当我们需要请求这张相同URL的图片的时候,就会直接从内存或SD卡中获取。而不用走网络,好处是减少了用户的使用流量 。
四、UI卡顿异常
-
UI卡顿原理
- 60fps -> 16ms
- overdraw:过度绘制
-
UI卡顿原因分析
- 人为在UI线程中做了轻微的耗时操作,导致UI线程卡顿。
- 布局Layout过于复杂,无法在16ms内完成渲染。
- 同一时间动画执行的次数过多,导致CPU或GPU负载过重。
- View过度绘制,导致某些像素在同一帧时间内被多次绘制,从而使CPU或GPU负载过重。
- View频繁的出发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染。
- 内存频繁触发GC过多,导致暂时阻塞渲染操作。
- 冗余资源及逻辑等导致加载和执行缓慢。
- ANR主线程做耗时操作。
-
UI卡顿避免方法
- 布局优化:优先使用Relativelayout,使用布局标签include(重复利用)、merge(继承父控件)、ViewStub(动态加载显示),使用最新的布局方式ConstaintLayout,利用Android Lint工具寻求可能优化布局的层次。
- 列表及Adapter优化:复用实例、滑动不要加载更新(监听滑动停止才加载数据,滑动的时候只显示默认值或缩略图)。
- 背景和图片等内存分配优化:减少布局中不必要的背景设置。图片要压缩处理。
- 避免ANR:不要在UI线程做耗时操作(Handler,AsyncTask,IntentService,HandlerThread等等)。
五、内存泄露异常
1.Java内存泄露
Java内存的分配策略:
- 静态存储区:主要存放一些静态数据、全局变量等等,生命周期是整个应用。
- 栈区:在方法执行的时候,方法体内的局部变量会在栈创建内存空间,并在方法结束后内存会自动释放。(高效,内存大小有限)
- 堆区:又称为动态内存分配,就是new出来的对象存放的地方,在不使用的时候由Java的垃圾回收器负责回收。Java是如何管理内存的
- 通过new为每个对象申请内存空间,所有对象都是在堆中分配空间。
- 对象的释放是由GC垃圾回收器来决定和执行的。
- 好处是GC简化了程序员的工作,能正确的释放对象。GC对每个对象进行监控(申请、引用、被引用和赋值),但这样也加重了虚拟机的工作。Java中的内存泄露
- 内存泄露就是指无用对象(不再使用的)持续占用内存,使内存无法得到及时的释放,从而造成内存空间的浪费称为内存泄露。
2.Android内存泄露
- 单例:应该使用Application Context。
- 匿名内部类:应该设为静态类。
- Handler:当Activity要销毁的时候,mHandler 中还有未处理或正在处理的消息,Message 持有 mHandler 的实例引用,mHandler 又是非静态内部类,持有外部类Activity引用,导致了Activity想回收时无法回收,造成内存泄露。应该把Handler改为静态内部类,同时mHandler持有外部类的弱引用,或者在onDestroy方法中mHandler.removeCallBackAndMessage(null)。
- 避免使用static变量:static变量的生命周期和App是一致的,无论在什么情况下,这部分内存都是不会释放的。在App内存管理机制占内存较大的后台App将会优先被回收,被回收的变量是不安全的。应该可以考虑懒加载,必须对static生命周期管理起来。
- 资源未关闭造成内存释放:广播、文件、Bitmap、游标cursor
- AsycnTask造成的内存泄露:在onDestroy方法中cancel任务。
六、内存管理
1.内存管理机制的概述
- 分配机制:系统会为每一个进程分配合理的大小,从而保证每个进程能正常运行,而不至于内存不够使用或占用太多。
- 回收机制:在系统内存不足的时候会有一个合理的回收再分配的机制,从而保证新的进程能正常运行。
2.Android内存管理机制
- 分配机制:弹性分配机制,初始化的时候分配较少的量,随着运行内存紧张的时候Android系统会额外分配内存大小(有限制)。目的是让更多进程存活在内存当中,当用户下一次启动App的时候就不用重新创建进程,只需要恢复已有的进程即可。
- 回收机制:App在每次退出的时候,依然保存在进程当中,为了能更快的再次启动。但当前内存不够用的时候,就会以“杀死最少的进程获得最大的内存”原则进行内存的回收,还遵循前台进程>可见进程>服务进程>后台进程>空进程的优先级进行回收。
3.内存管理机制的期望
- 更少的占用内存
- 在合适的时候,合理的释放系统资源。
- 在系统内存紧张的情况下,能释放掉大部分不重要的资源来为Android系统提供可用的内存。
- 能够很合理的在特殊生命周期中,保存或者还原重要数据,以至于系统能够正确的重新恢复该应用。
4.内存优化方法
- 当Service完成任务后,尽量停止它(IntentService替代)。
- 在UI不可见的时候,释放一些只用UI使用的资源。
- 在系统内存紧张的时候,尽可能多的释放掉一些非重要资源。
- 避免Bitmap导致的内存浪费(压缩、回收)。
- 使用针对内存优化过的数据容器。
- 避免使用依赖注入的框架(需要额外资源)。
- 使用ZIP对其的APK
- 使用多线程。
6.内存溢出VS内存泄露
- 使用第三方框架来解决泄露。
七、冷启动优化
1.什么是冷启动
冷启动/热启动的定义:
- 冷启动:在应用启动前,系统中没有该应用的任何进程信息。
- 热启动:是指用户退出应用后,然后马上又重新启动应用。冷启动/热启动的区别:
- 从定义上:冷启动启动的时候,系统会重新创建新的进程分配给该应用。热启动的时候,后台已经有了该应用的进程(由于内存不会马上释放上一次启动该进程,除非内存紧张)。
- 从启动特点上:冷启动的时候,在重新创建的时候,先会创建和初始化MyApplication类,然后创建MainActivity类和进行一些测量布局等等。热启动的时候只需要创建MainActivity类和进行一些测量布局等等。冷启动时间的计算:这个时间值从应用启动(创建进程)开始计算,到完成视图的第一次绘制(即Activity内容可见)为止。
2.冷启动流程
- Zygote进程中fork创建出一个新的进程;
- 创建和初始化Application类、创建MainActivity类;
- inflate布局、当onCreate/onStart/onResume方法都走完;
3.如何对冷启动的时间进行优化
- 减少onCreate()方法的工作量;
- 不要让Application参与业务的操作,也不能做耗时操作;
- 不要以静态变量的方式在Application中保存数据;
- 尽量减少布局所耗费的时间、减少层数(ViewStab动态加载)。
- 主线程也会影响(懒加载解决)。