一. 布局优化
核心思想:减少布局文件层级
布局层级减少 -> 绘制工作量减少 ->性能提高
- 删除布局中无用控件和层级
- 有选择的使用ViewGroup
- 优先使用 LinearLayout 和 FrameLayout
- 需要嵌套时使用 RelativeLayout(减少层级)
- <include> 重用布局
- <merge> + <include> 减少层级
- <ViewStub> 按需加载,提高初始化性能
二. 绘制优化
核心思想:View 的 onDraw 方法避免执行大量操作,因为 onDraw 方法可能会被频繁调用。
- 避免创建新的局部对象:大量临时对象占用内存且导致频繁 gc
- 不要做耗时任务和循环:造成 View 绘制过程不流畅
三. 内存泄漏优化
核心思想:经验 + 工具
- 开发中避免写出有内存泄漏的代码
- 通过分析工具找出潜在内存泄漏并解决
避免写出有内存泄漏的代码
-
Activity 被静态变量、静态集合、单例对象引用导致其无法及时销毁
-
属性动画:
无限循环动画持有 View,而View持有 Activity导致其无法释放(Activity 销毁时候调用animator.cancel()停止动画
-
Handler 导致的内存泄漏:
被延时处理的 Message 持有 Handler 的引用,非静态Handler 隐式持有对 Activity 的引用,形成了message – handler – activity 这样一条引用链,使得引用关系会保持至消息得到处理,导致 Activity 的泄漏。
解决方案:- 静态内部类Handler + WeakReference(弱引用Activity)
- Activity 销毁时候清空所有消息:handler.removeCallbacksAndMessages(null);
-
尽量用ApplicationContext 而不是 Activity Context
-
线程导致的内存泄漏
Thread/AsyncTask 这类线程任务,若以内部类的方式存在,隐式持有Activity,当Activity销毁时线程任务还未执行完毕(run/doInbackground)则内部类不会被销毁,因此它引用的Activity也不会销毁导致泄露。
解决方案:- 线程内部类使用静态内部类
- 在线程内部采用弱引用保存 Activity 引用
-
及时关闭/释放资源
- BraodcastReceiver 及时调用 unRegister()注销
- Cursor、File、Stream 及时调用 close ()关闭
工具
- MAT
- LeakCanary
四. 响应速度优化
核心思想:避免在主线程执行耗时操作
- 异步执行耗时任务
- ANR 日志分析:/data/anr/traces.txt
五. ListView优化
核心思想:复用+减少耗时操作
- 复用 convertView:减少加载布局文件的次数(耗时)
- 使用 ViewHolder:减少 findViewById的次数(耗时)
- 避免在 getView 中执行耗时操作
- 列表滑动时暂停加载、空闲时执行加载
六. Bitmap优化
核心思想:降低图片占用内存大小
-
BitmapFactory根据实际控件大小对图片进行采样,用到 BitmapFactory.Options 的 insimpleSize 和 inJustDecodeBounds 参数
-
纯色尽量使用color;规则图形尽量用shape;稍微复杂使用9patch图;如果不能使用9patch针对几种主流分辨率机型进行切图。
七. 线程优化
核心思想:线程池
ThreadPoolExecutor: 实现了 Executor 接口
执行规则: 核心线程->任务队列->非核心线程->拒绝执行异常
- 线程池中线程数量未达到核心线程数时,会直接启动一个核心线程来执行任务
- 若已达到或超出核心线程数量,那么任务会被插入到任务队列中排队等待执行
- 若任务队列中无法插入(已满),且线程数量未达到线程池规定的最大值,会立即启动一个非核心线程来执行
- 若步骤3中线程数量已达到线程池规定的最大值会拒绝执行此任务,会调用RejectedExecutionHandler的rejectExecution方法来通知调用者
构造参数含义:
- corePoolSize:核心线程数,默认核心线程会一直存活即使空闲
- maximumPoolSize:最大线程数,当活动线程数达到这个数值后,后续的新任务会被阻塞
- keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收
- unit:用于指定 keepAliveTime 参数的时间单位
- workQueue:线程池中的任务队列,通过线程池的 execute 方法提交的 Runnable 对象 会存储在这个参数中
- threadFactory:线程工厂,为线程池提供创建新线程的功能。该接口只有一个方法:newThread(Runnable r)
- RejectedExecutionHandler:当线程池无法执行执行新任务时(任务队列已满或无法成功执行任务)这时 ThreadPoolExecutor 会调用 handler 的 rejectedExecution 方法来通知调用者,默认该方法会直接抛出一个 RejectedExecutionException。
分类:
-
SingleThreadExecutor
只有一个核心线程,可确保任务都在同一个线程中按顺序执行,不需要处理线程同步问题 -
FixedThreadPool
线程数量固定,只有核心线程且线程空闲时不会被回收(除非线程池被关闭),因此可更快响应外界请求。 -
CacheThreadPool
线程数量不定,只有非核心线程(最大线程数为Integer.MAX_VALUE),空闲线程超时回收机制;适合执行大量耗时较少的任务。 -
ScheduledThreadPool
核心线程数量固定,非核心线程数量无限制空闲时立即回收;主要用于执行定时任务和固定周期的重复任务。
八. 一些优化建议
- 避免创建过多的对象
- 整型优先于枚举
- 常量使用 static final 修饰
- 使用一些 Android 特有的数据结构,如 SparseArray和Pair等
- 适当使用软引用和弱引用
- 采用内存缓存和磁盘缓存
- 尽量采用静态内部类,避免潜在的内存泄漏
- 频繁字符串拼接用StringBuilder
- 合理使用 static
static声明变量的生命周期和 App 的生命周期一样的,大量的使用,会占据内存空间不释放,积少成多会造成内存的不断开销,直至挂掉;
一般用来修饰基本数据类型或者轻量级对象,尽量避免修复集合或者大对象,常用作修饰全局配置项、工具类方法、内部类。
网友评论