目录
一.布局优化
1.产生卡顿主要原因
(1)布局Layout 过于复杂,无法在16ms 内完成渲染。
(2)同一时间动画执行的次数过多,导致 CPU或GPU负载过重。
(3)View 过度绘制,导致某些像素在同一帧时间内被绘制多次。
(4)View频繁的触发measure、layout,导致measure、layout累计耗时过多以及整个View频繁的重新渲染
(5)在UI线程中做了稍微耗时的操作,产生ANR。
(6)GC回收时暂停时间过长或者频繁的GC产生大量的暂停时间。
(7)加载冗余资源过多执行缓慢
2.布局优化方法:
(1)合理运用布局,减少布局层数。
- LinearLayout(线性布局)、 RelativeLayout(相对布局)、 TableLayout(表格布局)、FrameLayout(帧布局)、GridLayout(网格布局)、AbsoluteLayout(绝对布局)
(2)多个布局需要复用一个相同的布局时,使用 Include 标签来进行布局复用
(3) 用 Merge标签去除多余层级,Merge 意味着合并,在合适的场景使用Merge 标签可以减少多余的层级。
(4)使用ViewStub 来提高加载速度。
- ViewStub 是轻量级的View,不可见并且不占布局位置。
- ViewStub调用用 inflate方法或者设置可见时,系统加载ViewStub指定的布局,然后将这个布局添加到ViewStub中。
- 在对ViewStub调用inflate方法或者设置可见之前,它是不占布局空间和系统资源的,它要的目的就是为目标视图占用1个位。
- 因此,使用ViewStub可以提高界面初始化的性能,从而提升界面的加载速度。
(5)列表如ListView及Adapter优化
- 使用View的重用机制。在列表滑动的时候,不要去加载资源,显示默认的资源或者图片的缩略图,在列表滑动结束的时候再加载。
(6)背景图片内存分配的优化
- 尽量减少一些背景中不必要的设置,图片尽量进行压缩处理,不要直接显示。
(7)避免ANR ,在主线程(UI线程)不要进行耗时操作。
二、内存泄漏
1.什么是内存泄漏
指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
2.Java虚拟机内存的划分
(1)程序计数器
为了保证程序能够连续地执行下去,处理器必须具有某些手段来确定下一条指令的地址,而程序计数器正是起到这种作用 。
程序计数器( Program Counter Register )也叫作 PC寄存器,是一块较小的内存空间。
Java 虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在一个确定的时刻只有一个处理器执行一条线
程中的指令,为了在线程切换后能恢复到正确的执行位 ,每个线程都会有一个独立的程序计数器,因此,程序计数器是线程私有的。
如果线程执行的方也不是 Native 方泣,则程序计数器保存正在执行的字节码指令地址,如果是 Native 方怯则程序计数器的值为空
(2)Java栈区
每一条Java虚拟机线程都有一个线程私有的Java虚拟机栈区。它的生命周期与线程相同,与线程是同时创建的。
栈区存储线程中Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。
(3)本地方法栈区
它与Java栈区类似,只不过本地方法栈是用来支持 Native方法的。如果Java虚拟机不支持Native方法,并且也不依赖于C Stacks ,
可以无须支持本地方法栈。
(4)Java堆区
Java堆 (Java Heap )是被所有线程共享的运行时内存区域。Java 堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。
堆区存储的对象被垃圾收集器管理,这些受管理的对象无法显式地销毁。
(5)方法区
方住区(Method Area )是被所有线程共享的运行时内存区域,用来存储已经被Java虚拟机加载的类的结构信息,
包括运行时常量地、字段和方法信息、静态变量等数据。
(6)运行时常量池
运行时常量地(Runtim Constant Pool )并不是运行时数据区域的其中一份子,而是方法区的一部分。
它用来存放编译时期生成的字面量和符号引用,这些内容会在类加载后存放在方法区的运行时常量池中 。
运行时常量池可以理解为是类或接口的运行时表现形式。
3.内存泄漏的场景
(1)非静态内部类的静态实例
- 原因:非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的(静态对象变量指向非静态实例:private static Object a ; a = new A //A是非静态内部类),
就会间接地长期维持着外部类的引用,阻止被系统回收。 - 解决办法:非静态内部类的实例改成静态的
(2)多线程相关的匿名内部类/非静态内部类
- 原因:匿名内部类也会持有外部类实例的引用。多线程相关的类有AsyncTask 类、Thread 类和实现 Runnable 接口的类等,
它们的匿名内部类/非静态内部类如果做耗时操作就可能发生内存泄漏。 - 解决办法:自定义一个静态的类如AsyncTask类
(3)Handler的内存泄漏
- 原因:Handler Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue存在的时间就会很长,这就会导致 Handler 无法被回收。
如果Handler是非静态的,则Handler也会导致引用它的Activity或Service不能被回收。 - 解决办法:1.使用一个静态的Handler内部类, 将Handler持有的对象要使用弱引用;2.在Activity的onDestroy方法中将Handler的Callbacks Messages全部清除掉。
(4)未正确使用 Context
- 原因:单例模式创建静态对象时传入Activity的Context,使得Activity被一个单例持有。
当Activity被销毁时,由于单例对象生命周期会长于Activity,导致垃圾收集器无法回收该activity,进而产生了内存泄漏。 - 解决办法:对于不是必须使用 Activity的Context 的情况( Dialog的Context 必须使用Activity的Context),可以考虑使用 Application Context 来代替 Activ町的 Context,这样避免内存泄漏。
(5)静态View
- 原因:使用静态View可以避免每次启动Activity都去读取并渲染View ,但是静态View对象会持有Activity的引用,导致Activity无法被回收,导致内存泄漏。
- 解决办法:在Activity的onDestory方法中将静态View对象设置为null
(6)资源对象未关闭
- 原因:资源对象比如Cursor、File 等,往往都使用了缓冲,会造成内存泄漏。
- 解决办法:在资源对象不使用时, 一定要确保它们已经关闭并将它们的引用置为 null ,通常在 finally 语句中进行关闭,防止出现异常肘,资源未被释放的问题
(7)集合中的对象没清理
- 原因:通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。
如果这个集合是static的话,那情况就会更加严重。 - 解决办法:当保存到集合的对象如果不使用时要及时清理。
(8)Bitmap对象
- 原因:临时创建静态变量持有比较大的 Bitmap对象,在经过变换得到新的Bitmap 对象之后,导致没法释放原始Bitmap所占用的空间。
- 解决办法:避免静态变量持有比较大的 Bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。
(9)监听器未关闭
- 原因:很多系统服务(比如 TelephonyMannager 、Binder)需要register和unregister监听器,当不需要时未进行unregister 那些监听器
- 解决办法:自己手动添加的 Listener,要记得在合适的时候及时移除这个Listener。
三、内存溢出
1.什么是内存溢出
当前占用的内存加上我们申请的内存资源超过了Dalvik虚拟机的最大内存限制就会抛出Out Of Memory异常(OOM异常)
2.如何解决OOM
(1)Bitmap的优化
- 图片显示:加载合适尺寸的图片。
- 及时释放内存:Bitmap的内存分为java区和C(native)区,,由于C区无法自动回收,所以需要调用Bitmap的recycle()方法对C区的内存进行回收。
- 图片压缩:适当的时候对比例比较大的图片进行压缩,以节约内存。
- inBitmap属性:此属性提高Android系统在Bitmap分配和释放的执行效率。
- 捕获异常:对OOM异常进行捕获。从而作出合适的措施。注意OOM是Error,不是Exception。
(2)ListView的优化
- 在Adapter中的getView方法中使用ConvertView,即ConvertView的复用,不需要每次都inflate一个View出来,这样既浪费时间,又浪费内存。
- 使用ViewHolder,不要在getView方法中写findViewById方法,因为getView方法会执行很多遍,这样也可以节省时间,节约内存。
- 使用分页加载,一次加载几条或者十几条,每加载一页的时候可以覆盖前一页的数据。
- 如果数据当中有图片的话,使用第三方库来加载(也就是缓存)
- 当你手指在滑动列表的时候,尽可能的不加载图片,这样的话滑动就会更加流畅。
(3)避免在onDraw方法里面执行对象的创建
四、ANR异常
1.什么是ANR
ANR(Application Not Responding):程序无响应,会造成ANR弹框。
2.造成ANR的主要原因
(1)主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞。
(2)主线程存在耗时的操作:
- Activity的所有生命周期都是执行在主线程的。/5s–>ANR
- Service默认是执行在主线程的。/5s–>ANR
- BroadcastReceiver的onReceiver回调是执行在主线程的/10s–>ANR
- 没有使用子线程的Looper的Handler的handleMessage,post(Runnable)是执行在主线程。
- AsyncTask的回调除了doInBackground,其他都是在主线程。
3.如何解决ANR
(1)将所有耗时操作,比如访问网络,Socket通信,查询大量SQL 语句,复杂逻辑计算等都放在子线程中去,然后通过handler、runonUIThread、AsyncTask 等方式更新UI。
(2)使用AsyncTask处理耗时IO操作。
(3)使用Handler处理工作线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程。
(4)Activity的onCreate和onResume回调中尽量避免耗时的代码
(5)BroadcastReceiver中onReceive代码也要尽量减少耗时,
(6)Service中使用StartService方法向服务传递消息时,建议使用IntentService处理。
(7)各个组件的生命周期函数都不应该有太耗时的操作,即使对于后台Service或者ContentProvider来讲,应用在后台运行时候其onCreate()时候不会有用户输入引起事件无响应ANR,但其执行时间过长也会引起Service的ANR和ContentProvider的ANR