Android学习笔记之异常与性能优化

一. ANR异常

  1. 什么是ANR: 在Android中,如果应用程序有一段时间响应不够灵敏,系统会向用户显示应用无响应对话框(ANR:Application Not Responding)。用户可以选择让程序继续运行或者关闭程序。
  2. ANR产生的原因:主线程中被耗时操作阻塞(网络请求、IO流读取等操作)。
  3. 怎样避免ANR
    • 使用Asynctask处理耗时IO操作。(灵活切换线程)
    • 使用Thread或者HandlerThread 提高优先级。
    • 使用Handler来处理工作线程的耗时任务。
    • Activity的onCreate和onResume回调中尽量避免耗时的代码。
  4. Android哪些操作是在主线程中
    • Activity的所有生命周期回调都是执行在主线程的。
    • Service默认是执行在主线程的。
    • BroadCastReceiver的onReceive回调是执行在主线程的。
    • 没有使用子线程的Looper的Handler的handleMessage、post(Runnable)是执行在主线程的。
    • AsyncTask的回调除了 doInBackground,其他都是执行在主线程。
    • ContentProvider的onCreate()是运行在UI线程的,而query(),insert(),delete()和update()是运行在线程池中的工作线程的。

二、OOM内存溢出异常

  1. 什么是OOM:当前占用的内存加上申请的内存资源超过了 Dalvik 虚拟机的最大内存限制就会抛出的 Out of Memory 异常。(堆内存上有些资源没被释放,从而失去控制,造成程序使用的内存越来越少,导致程序运行速度减慢,严重情况会导致程序的奔溃。)

  2. 为什么会OOM,如何解决

    1. 瞬时加载了大内存的资源,例如:视频、图片和音频等等的内存申请大小超过了App的额定内存值,解决方案:对资源进行压缩处理。
    2. Bitmap对象及时recycle()释放内存,解决方案:在Bitmap不在需要被加载到内存中的时候,做回收处理。
    3. 未关闭 InputStream/OutputStream , 解决方案:在使用到I/O 流的时候,及时关闭。
    4. 调用 registerRecevier() 动态注册后未调用 unregisterReceiver(), 解决方案:在每一次动态注册的时候,记得在onStop/onDestroy取消注册。
    5. 查询数据库后没有关闭游标cursor,解决方案:使用完cursor及时关闭。
    6. 构造Adapter没有使用缓存 contentview重用, 解决方案:在构造Adapter的时候,使用ContentView缓存页面,节省内存。
    7. 减少onDraw的执行次数
    8. Context泄露,内部类持有外部类的引用。 解决方案: 第一: 将线程的内部类,改为静态内部类 第二:在线程内部采用弱引用保存Context引用。
    9. static 原因。 解决方案:(1)应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。(2)Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。(3)使用WeakReference代替强引用。比如可以使用WeakReference mContextRef;(4)查找内存泄漏可以使用Android Profiler工具或者利用LeakCanary工具。
  3. 为什么Android会有APP的内存限制

    1. 要开发者使用内存更加合理。限制每个应用可用内存上限,避免恶意程序或单个程序使用过多内存导致其他程序的不可运行。有了限制,开发者就必须合理使用资源,优化资源使用
    2. 屏幕显示内容有限,内存足够即可。即使有万千图片千万数据需要使用到,但在特定时刻需要展示给用户看的总是有限的,因为屏幕显示就那么大,上面可以放的信息就是很有限的。大部分信息都是处于准备显示状态,所以没必要给予太多heap内存。必须一个ListView显示图片,打个比方这个ListView含有500个item,但是屏幕显示最多有10调item显示,其余数据是处于准备显示状态。
    3. Android多个虚拟机Davlik的限制需要。android设备上的APP运行,每打开一个应用就会打开至少一个独立虚拟机。这样可以避免系统崩溃,但代价是浪费更多内存。
  4. 一些容易混淆的概念

  • 内存溢出:当前占用的内存加上申请的内存资源超过了 Dalvik 虚拟机的最大内存限制就会抛出的 Out of Memory 异常。(非常恐怖)
  • 内存抖动:短时间内大量的对象被创建,然后又被马上释放,瞬间产生的对象会严重占用内存区域,随即触发GC回收。
  • 内存泄露:进程中某些对象,它没有被其他地方引用到了,但是他们可以直接或间接引用到其他还没被回收的对象,导致GC回收无法产生作用。一旦内存泄露达到一定的程度,就会造成内存溢出OOM。

三、Bitmap异常

  1. recycle:内存回收,释放C内存中的内存。
  2. Lru算法:最近最少使用的缓存给清理,主要是用put/get方法存入缓存,当缓存快要满的时候用trimToSize清理缓存。
  3. 计算inSampleSize:合适的缩放比例来加载图片到内存,避免OOM。
  4. 缩略图:inJustDecodeBounds。
  5. 三级缓存:网络、本地和内存,当App首次加载一张图片的时候,会从网络中请求图片加载,当图片请求成功后会把这个Bitmap保存到 SD卡和内存中各一份,当我们需要请求这张相同URL的图片的时候,就会直接从内存或SD卡中获取。而不用走网络,好处是减少了用户的使用流量 。

四、UI卡顿异常

  1. UI卡顿原理
    • 60fps -> 16ms
    • overdraw:过度绘制
  2. UI卡顿原因分析
    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主线程做耗时操作。
  3. UI卡顿避免方法
    1. 布局优化:优先使用Relativelayout,使用布局标签include(重复利用)、merge(继承父控件)、ViewStub(动态加载显示),使用最新的布局方式ConstaintLayout,利用Android Lint工具寻求可能优化布局的层次。
    2. 列表及Adapter优化:复用实例、滑动不要加载更新(监听滑动停止才加载数据,滑动的时候只显示默认值或缩略图)。
    3. 背景和图片等内存分配优化:减少布局中不必要的背景设置。图片要压缩处理。
    4. 避免ANR:不要在UI线程做耗时操作(Handler,AsyncTask,IntentService,HandlerThread等等)。

五、内存泄露异常

1.Java内存泄露

  • Java内存的分配策略
    - 静态存储区:主要存放一些静态数据、全局变量等等,生命周期是整个应用。
    - 栈区:在方法执行的时候,方法体内的局部变量会在栈创建内存空间,并在方法结束后内存会自动释放。(高效,内存大小有限)
    - 堆区:又称为动态内存分配,就是new出来的对象存放的地方,在不使用的时候由Java的垃圾回收器负责回收。

  • Java是如何管理内存的
    - 通过new为每个对象申请内存空间,所有对象都是在堆中分配空间。
    - 对象的释放是由GC垃圾回收器来决定和执行的。
    - 好处是GC简化了程序员的工作,能正确的释放对象。GC对每个对象进行监控(申请、引用、被引用和赋值),但这样也加重了虚拟机的工作。

  • Java中的内存泄露
    - 内存泄露就是指无用对象(不再使用的)持续占用内存,使内存无法得到及时的释放,从而造成内存空间的浪费称为内存泄露。

2.Android内存泄露

  1. 单例:应该使用Application Context。
  2. 匿名内部类:应该设为静态类。
  3. Handler:当Activity要销毁的时候,mHandler 中还有未处理或正在处理的消息,Message 持有 mHandler 的实例引用,mHandler 又是非静态内部类,持有外部类Activity引用,导致了Activity想回收时无法回收,造成内存泄露。应该把Handler改为静态内部类,同时mHandler持有外部类的弱引用,或者在onDestroy方法中mHandler.removeCallBackAndMessage(null)。
  4. 避免使用static变量:static变量的生命周期和App是一致的,无论在什么情况下,这部分内存都是不会释放的。在App内存管理机制占内存较大的后台App将会优先被回收,被回收的变量是不安全的。应该可以考虑懒加载,必须对static生命周期管理起来。
  5. 资源未关闭造成内存释放:广播、文件、Bitmap、游标cursor
  6. AsycnTask造成的内存泄露:在onDestroy方法中cancel任务。

六、内存管理

1.内存管理机制的概述

  1. 分配机制:系统会为每一个进程分配合理的大小,从而保证每个进程能正常运行,而不至于内存不够使用或占用太多。
  2. 回收机制:在系统内存不足的时候会有一个合理的回收再分配的机制,从而保证新的进程能正常运行。

2.Android内存管理机制

  1. 分配机制:弹性分配机制,初始化的时候分配较少的量,随着运行内存紧张的时候Android系统会额外分配内存大小(有限制)。目的是让更多进程存活在内存当中,当用户下一次启动App的时候就不用重新创建进程,只需要恢复已有的进程即可。
  2. 回收机制:App在每次退出的时候,依然保存在进程当中,为了能更快的再次启动。但当前内存不够用的时候,就会以“杀死最少的进程获得最大的内存”原则进行内存的回收,还遵循前台进程>可见进程>服务进程>后台进程>空进程的优先级进行回收。

3.内存管理机制的期望

  1. 更少的占用内存
  2. 在合适的时候,合理的释放系统资源。
  3. 在系统内存紧张的情况下,能释放掉大部分不重要的资源来为Android系统提供可用的内存。
  4. 能够很合理的在特殊生命周期中,保存或者还原重要数据,以至于系统能够正确的重新恢复该应用。

4.内存优化方法

  1. 当Service完成任务后,尽量停止它(IntentService替代)。
  2. 在UI不可见的时候,释放一些只用UI使用的资源。
  3. 在系统内存紧张的时候,尽可能多的释放掉一些非重要资源。
  4. 避免Bitmap导致的内存浪费(压缩、回收)。
  5. 使用针对内存优化过的数据容器。
  6. 避免使用依赖注入的框架(需要额外资源)。
  7. 使用ZIP对其的APK
  8. 使用多线程。

6.内存溢出VS内存泄露

  1. 使用第三方框架来解决泄露。

七、冷启动优化

1.什么是冷启动

  1. 冷启动/热启动的定义
    - 冷启动:在应用启动前,系统中没有该应用的任何进程信息。
    - 热启动:是指用户退出应用后,然后马上又重新启动应用。

  2. 冷启动/热启动的区别
    - 从定义上:冷启动启动的时候,系统会重新创建新的进程分配给该应用。热启动的时候,后台已经有了该应用的进程(由于内存不会马上释放上一次启动该进程,除非内存紧张)。
    - 从启动特点上:冷启动的时候,在重新创建的时候,先会创建和初始化MyApplication类,然后创建MainActivity类和进行一些测量布局等等。热启动的时候只需要创建MainActivity类和进行一些测量布局等等。

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

2.冷启动流程

  1. Zygote进程中fork创建出一个新的进程;
  2. 创建和初始化Application类、创建MainActivity类;
  3. inflate布局、当onCreate/onStart/onResume方法都走完;

3.如何对冷启动的时间进行优化

  1. 减少onCreate()方法的工作量;
  2. 不要让Application参与业务的操作,也不能做耗时操作;
  3. 不要以静态变量的方式在Application中保存数据;
  4. 尽量减少布局所耗费的时间、减少层数(ViewStab动态加载)。
  5. 主线程也会影响(懒加载解决)。

八、其他优化

1.Android不用静态变量存储数据

2.有关sp的安全问题

3.内存对象序列化

4.避免在UI线程中做繁重的操作。

你可能感兴趣的:(Android学习笔记之异常与性能优化)