针对Android系统的设计我们还需要知道另一个常识;虚拟机在执行GC垃圾回收操作时所有线程(包括UI线程)都需要暂停,当GC垃圾回收完成之后所有线程才能够继续执行。也就是说当在16ms内进行渲染等操作时如果刚好遇上大量GC操作则会导致渲染时间明显不足,也就从而导致了丢帧卡顿问题。
参考网上的一些总结
1,在Activity,Fragment等生命周期方法中和Adapter重写类中,避免有些频繁触发的逻辑方法中存在大量对象分配
2,懒加载和缓存机制。访问网络的耗时操作启动一个新线程来做,而不要再UI线程来做,单例最好懒加载,Fragment也最好懒加载
3,UI线程不做耗时操作,耗时操作放在子线程处理
4,布局文件要尽可能的优化,减少布局的解析时间。尽量减少布局的嵌套层次,尽量使用include,merge,ViewStub
5,减少同一时刻的动画执行次数
6,自定义view时,减少onMeasure,onLayout,onDraw等的调用次数,注意避免有些频繁触发的逻辑方法中存在大量对象分配
7,对象引用之后要及时回收
8,减少冗余资源和代码逻辑的使用
9,减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等。
10,尽量避免在多次for循环中频繁分配对象
11,避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等)
12,对于并发下载等类似逻辑的实现尽量避免多次创建线程对象,而是交给线程池处理
13,使用foreach代替for i
14,尽量少的声明全局变量
15,声明全局静态变量,一定要加final声明
16,声明非静态的全局变量,最好不要初始化任何值,在使用到的地方,在进行初始化
17,函数中若干次使用全局变量,应该将全局变量赋值给本地变量,然后直接使用本地变量
18,能用Int,不要使用浮点数
19,能用乘法不用除法
20,尽量避免使用geter和setter方法
21,在Activity的onCreate函数中,尽量做少的事
22,在Activity中声明的静态数组或者静态代码块,重构到单独的一个类里
23,Activity启动后开始进行异步线程的加载,最好delay一下。再开启线程
24,对于存在于集合中的Bean对象,尽可能少的声明变量。能用int 就不要用long.声明的string等复杂变量,最好不要进行初始化
25,使用线程,一定要给它传一个名字,然后需要定义线程的优先级
26,在使用集合的时候,优先选择SparseArray
27,尽量避免使用枚举
28,工具方法尽量写成是静态方法
29,线程间同步尽量使用开销小的同步锁
30,在使用集合类的时候,如果已知数据的规模,在初始化的时候,就设定好默认大小
31,私有内部类访问外部类的私有变量,要将变量修改为包继承权限,在私有内部类中,考虑用包访问权限替代私有访问权限
32,对于开销大的算法,且不止是执行一次的,要使用缓存策略
33,避免在绘制或者解析布局的时候,分配对象。例如onDraw方法
34,不要给布局写无用的参数,例如RelativeLayout,写layout_weight属性
35,尽量减少布局的嵌套层数。例如包含一个ImageView和TextView的线性布局,可以用CompoundDrawable的TextView来代替
36,尽量用Android提供的SparseArray来代替HashMap
37,如果LinearLayout用于嵌套的layout空间计算,它的android:baselineAligned设置为false,可以加速layout计算
38,尽量避免嵌套的使用layout_weight,那样会影响执行效率
39,如果为rootView设置了背景,那么会先用Theme指定的背景绘制一遍,然后才用指定的背景绘制,这叫做"overdraw",可以通过theme的background为null来避免
40,不要有无用的任何资源,代码或者文件
41,一个Activity中使用同一个View.onClickListener()处理所有的业务逻辑
42,数据一定要校验,如用户填写的日期时间数据、电话号码数据等
43,不要随意的使用stingA=StringB+StringC的写法,有大量拼接操作的地方用StringBuilder代替
44,有些能用文件操作的,尽量采用文件操作,文件操作的速度比数据库的操作要快10倍左右
45,避免重复点击和快速点击
46,尽量避免static成员变量引用资源耗费过多的实例,比如Context
47,应用开发中自定义View的时候,交互部分,千万不要写成线程不断刷新界面显示,而是根据TouchListener事件主动触发界面的更新
48,如果ImageView的图片是来自网络,进行异步加载
49,.保证Cursor 占用的内存被及时的释放掉,而不是等待GC来处理。并且 Android明显是倾向于编 程者手动的将Cursor close掉
50,软键盘的弹出控制,不要让其覆盖输入框
51,使用styles,复用样式定义
52,复杂布局使用RelativeLayout
53,自适应屏幕,使用dp替代pix
54,使用animation-list制作动画效果
官网规范
1. 记得关闭启动的服务
当服务中的任务完成后,要记得停止该服务。可以考虑使用 IntentService,因为IntentService 在完成任务后会自动停止。
2. UI 不可见时释放资源
在 onStop 中关闭网络连接、注销广播接收器、释放传感器等资源;
在 onTrimMemory() 回调方法中监听TRIM_MEMORY_UI_HIDDEN 级别的信号,此时可在 Activity 中释放 UI 使用的资源,大符减少应用占用的内存,从而避免被系统清除出内存。
3. 内存紧张时释放资源
运行中的程序,如果内存紧张,会在 onTrimMemory(int level) 回调方法中接收到以下级别的信号:
TRIM_MEMORY_RUNNING_MODERATE:系统可用内存较低,正在杀掉 LRU缓存中的进程。你的进程正在运行,没有被杀掉的危险。
TRIM_MEMORY_RUNNING_LOW:系统可用内存更加紧张,程序虽然暂没有被杀死的危险,但是应该尽量释放一些资源,以提升系统的性能(这也会直接影响你程序的性能)。
TRIM_MEMORY_RUNNING_CRITICAL:系统内存极度紧张,而LRU缓存中的大部分进程已被杀死,如果仍然无法获得足够的资源的话,接下来会清理掉 LRU 中的所有进程,并且开始杀死一些系统通常会保留的进程,比如后台运行的服务等。
当程序未在运行,保留在 LRU 缓存中时, onTrimMemory(int level) 中会返回以下级别的信号:
TRIM_MEMORY_BACKGROUND:系统可用内存低,而你的程序处在 LRU的顶端,因此暂时不会被杀死,但是此时应释放一些程序再次打开时比较容易恢复的 UI 资源。
TRIM_MEMORY_MODERATE:系统可用内存低,程序处于 LRU的中部位置,如果内存状态得不到缓解,程序会有被杀死的可能。
TRIM_MEMORY_COMPLETE:系统可用内存低,你的程序处于 LRU尾部,如果系统仍然无法回收足够的内存资源,你的程序将首先被杀死。此时应释放无助于恢复程序状态的所有资源。
注:该 API 在版本 14 中加入。旧版本的onLowMemory() 方法,大致相当于 onTrimMemory(int level) 中接收到 TRIM_MEMORY_COMPLETE 级别的信号。
另:尽管系统主要按照 LRU 中顺序来杀进程,不过系统也会考虑程序占用的内存多少,那些占用内存高的进程有更高的可能性会被首先杀死。
4. 确定你的程序应该占用多少内存
可以通过 getMemoryClass()来获取你的程序被分配的可用内存,以 M 为单位。
你可以通过在 <application> 标签下将 largeHeap 属性设为 true 来要求更多的内存,这时通过 getLargeMemoryClass() 方法来获取可用内存。
大部分应用程序不需要使用此功能,因此使用该标签前,确认你的程序是否真的需要更多内存。使用更多内存会对整个系统的性能产生影响,而且当程序进入 LRU时会更容易首先被系统清理掉。
5. 正确使用 Bipmap,避免浪费内存
如果你的 ImageViwe 的尺寸只有 100 * 100,那么没有必要将一张 2560 * 1600 的图片整个加载入内存。
6. 使用 Android提供的优化过的数据结构
如 SparseArray, SparseBooleanArray, LongSparseArray 等,相比 Java 提供的 HashMap,这些结构更节省内存。
7. 始终对内存使用情况保持关注
枚举类型 Enum 会比静态常量占用更多的内存;
Java 中每个类(包括匿名内部类)都占用至少 500字节左右的代码;
每个类的实例会在 RAM 中占用大约 12 ~ 16 字节的内存;
每向 HashMap 中添加一个 Entry 时,新生成的 Entry 占用大约 32 个字节。
8. 谨慎使用第三方类库
这些外部类库可能原先并非针对移动平台,因此未进行过优化,在使用前应注意。另外尽量不要因为一两个特性而使用一个体积很大的类库。
9. 使用 ProGuard
使用 ProGuard 移除无用的代码并重命名一些类、字段、方法等,使你的代码更紧凑,节省内存空间。
10. 使用 zipalign
zipaligned 对最终打包的 apk进行字节对齐。
注:Google Play 不接受未对齐过的 apk。
11. 分析内存使用情况
如果已经获得一个相对稳定的版本,应对程序整个生命周期的内存使用状况进行分析。
12. 使用多个进程
如果程序需要执行大量的后台工作,可考虑将程序分为两个进程,一个进程负责 UI,另一个进程负责后台任务。比如音乐播放器。
代码示例:
<serviceandroid:name=".PlaybackService"android:process=":background"/>
android:process属性的值以“:”开头,名称可任意选取。
在决定是否使用多进程前,应注意,一个不执行任何任务的空进程至少也要占用 1.4 MB内存。
另外要注意进程的相互依赖性,比如如果将 ContentProvider 放在 UI 进程中,而后台任务进程也需要调用 ContentProvider,就会导致 UI 进程一直保留在 RAM 中。
参考网上的一些总结
1,在Activity,Fragment等生命周期方法中和Adapter重写类中,避免有些频繁触发的逻辑方法中存在大量对象分配
2,懒加载和缓存机制。访问网络的耗时操作启动一个新线程来做,而不要再UI线程来做,单例最好懒加载,Fragment也最好懒加载
3,UI线程不做耗时操作,耗时操作放在子线程处理
4,布局文件要尽可能的优化,减少布局的解析时间。尽量减少布局的嵌套层次,尽量使用include,merge,ViewStub
5,减少同一时刻的动画执行次数
6,自定义view时,减少onMeasure,onLayout,onDraw等的调用次数,注意避免有些频繁触发的逻辑方法中存在大量对象分配
7,对象引用之后要及时回收
8,减少冗余资源和代码逻辑的使用
9,减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域等。
10,尽量避免在多次for循环中频繁分配对象
11,避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等)
12,对于并发下载等类似逻辑的实现尽量避免多次创建线程对象,而是交给线程池处理
13,使用foreach代替for i
14,尽量少的声明全局变量
15,声明全局静态变量,一定要加final声明
16,声明非静态的全局变量,最好不要初始化任何值,在使用到的地方,在进行初始化
17,函数中若干次使用全局变量,应该将全局变量赋值给本地变量,然后直接使用本地变量
18,能用Int,不要使用浮点数
19,能用乘法不用除法
20,尽量避免使用geter和setter方法
21,在Activity的onCreate函数中,尽量做少的事
22,在Activity中声明的静态数组或者静态代码块,重构到单独的一个类里
23,Activity启动后开始进行异步线程的加载,最好delay一下。再开启线程
24,对于存在于集合中的Bean对象,尽可能少的声明变量。能用int 就不要用long.声明的string等复杂变量,最好不要进行初始化
25,使用线程,一定要给它传一个名字,然后需要定义线程的优先级
26,在使用集合的时候,优先选择SparseArray
27,尽量避免使用枚举
28,工具方法尽量写成是静态方法
29,线程间同步尽量使用开销小的同步锁
30,在使用集合类的时候,如果已知数据的规模,在初始化的时候,就设定好默认大小
31,私有内部类访问外部类的私有变量,要将变量修改为包继承权限,在私有内部类中,考虑用包访问权限替代私有访问权限
32,对于开销大的算法,且不止是执行一次的,要使用缓存策略
33,避免在绘制或者解析布局的时候,分配对象。例如onDraw方法
34,不要给布局写无用的参数,例如RelativeLayout,写layout_weight属性
35,尽量减少布局的嵌套层数。例如包含一个ImageView和TextView的线性布局,可以用CompoundDrawable的TextView来代替
36,尽量用Android提供的SparseArray来代替HashMap
37,如果LinearLayout用于嵌套的layout空间计算,它的android:baselineAligned设置为false,可以加速layout计算
38,尽量避免嵌套的使用layout_weight,那样会影响执行效率
39,如果为rootView设置了背景,那么会先用Theme指定的背景绘制一遍,然后才用指定的背景绘制,这叫做"overdraw",可以通过theme的background为null来避免
40,不要有无用的任何资源,代码或者文件
41,一个Activity中使用同一个View.onClickListener()处理所有的业务逻辑
42,数据一定要校验,如用户填写的日期时间数据、电话号码数据等
43,不要随意的使用stingA=StringB+StringC的写法,有大量拼接操作的地方用StringBuilder代替
44,有些能用文件操作的,尽量采用文件操作,文件操作的速度比数据库的操作要快10倍左右
45,避免重复点击和快速点击
46,尽量避免static成员变量引用资源耗费过多的实例,比如Context
47,应用开发中自定义View的时候,交互部分,千万不要写成线程不断刷新界面显示,而是根据TouchListener事件主动触发界面的更新
48,如果ImageView的图片是来自网络,进行异步加载
49,.保证Cursor 占用的内存被及时的释放掉,而不是等待GC来处理。并且 Android明显是倾向于编 程者手动的将Cursor close掉
50,软键盘的弹出控制,不要让其覆盖输入框
51,使用styles,复用样式定义
52,复杂布局使用RelativeLayout
53,自适应屏幕,使用dp替代pix
54,使用animation-list制作动画效果
官网规范
1. 记得关闭启动的服务
当服务中的任务完成后,要记得停止该服务。可以考虑使用 IntentService,因为IntentService 在完成任务后会自动停止。
2. UI 不可见时释放资源
在 onStop 中关闭网络连接、注销广播接收器、释放传感器等资源;
在 onTrimMemory() 回调方法中监听TRIM_MEMORY_UI_HIDDEN 级别的信号,此时可在 Activity 中释放 UI 使用的资源,大符减少应用占用的内存,从而避免被系统清除出内存。
3. 内存紧张时释放资源
运行中的程序,如果内存紧张,会在 onTrimMemory(int level) 回调方法中接收到以下级别的信号:
TRIM_MEMORY_RUNNING_MODERATE:系统可用内存较低,正在杀掉 LRU缓存中的进程。你的进程正在运行,没有被杀掉的危险。
TRIM_MEMORY_RUNNING_LOW:系统可用内存更加紧张,程序虽然暂没有被杀死的危险,但是应该尽量释放一些资源,以提升系统的性能(这也会直接影响你程序的性能)。
TRIM_MEMORY_RUNNING_CRITICAL:系统内存极度紧张,而LRU缓存中的大部分进程已被杀死,如果仍然无法获得足够的资源的话,接下来会清理掉 LRU 中的所有进程,并且开始杀死一些系统通常会保留的进程,比如后台运行的服务等。
当程序未在运行,保留在 LRU 缓存中时, onTrimMemory(int level) 中会返回以下级别的信号:
TRIM_MEMORY_BACKGROUND:系统可用内存低,而你的程序处在 LRU的顶端,因此暂时不会被杀死,但是此时应释放一些程序再次打开时比较容易恢复的 UI 资源。
TRIM_MEMORY_MODERATE:系统可用内存低,程序处于 LRU的中部位置,如果内存状态得不到缓解,程序会有被杀死的可能。
TRIM_MEMORY_COMPLETE:系统可用内存低,你的程序处于 LRU尾部,如果系统仍然无法回收足够的内存资源,你的程序将首先被杀死。此时应释放无助于恢复程序状态的所有资源。
注:该 API 在版本 14 中加入。旧版本的onLowMemory() 方法,大致相当于 onTrimMemory(int level) 中接收到 TRIM_MEMORY_COMPLETE 级别的信号。
另:尽管系统主要按照 LRU 中顺序来杀进程,不过系统也会考虑程序占用的内存多少,那些占用内存高的进程有更高的可能性会被首先杀死。
4. 确定你的程序应该占用多少内存
可以通过 getMemoryClass()来获取你的程序被分配的可用内存,以 M 为单位。
你可以通过在 <application> 标签下将 largeHeap 属性设为 true 来要求更多的内存,这时通过 getLargeMemoryClass() 方法来获取可用内存。
大部分应用程序不需要使用此功能,因此使用该标签前,确认你的程序是否真的需要更多内存。使用更多内存会对整个系统的性能产生影响,而且当程序进入 LRU时会更容易首先被系统清理掉。
5. 正确使用 Bipmap,避免浪费内存
如果你的 ImageViwe 的尺寸只有 100 * 100,那么没有必要将一张 2560 * 1600 的图片整个加载入内存。
6. 使用 Android提供的优化过的数据结构
如 SparseArray, SparseBooleanArray, LongSparseArray 等,相比 Java 提供的 HashMap,这些结构更节省内存。
7. 始终对内存使用情况保持关注
枚举类型 Enum 会比静态常量占用更多的内存;
Java 中每个类(包括匿名内部类)都占用至少 500字节左右的代码;
每个类的实例会在 RAM 中占用大约 12 ~ 16 字节的内存;
每向 HashMap 中添加一个 Entry 时,新生成的 Entry 占用大约 32 个字节。
8. 谨慎使用第三方类库
这些外部类库可能原先并非针对移动平台,因此未进行过优化,在使用前应注意。另外尽量不要因为一两个特性而使用一个体积很大的类库。
9. 使用 ProGuard
使用 ProGuard 移除无用的代码并重命名一些类、字段、方法等,使你的代码更紧凑,节省内存空间。
10. 使用 zipalign
zipaligned 对最终打包的 apk进行字节对齐。
注:Google Play 不接受未对齐过的 apk。
11. 分析内存使用情况
如果已经获得一个相对稳定的版本,应对程序整个生命周期的内存使用状况进行分析。
12. 使用多个进程
如果程序需要执行大量的后台工作,可考虑将程序分为两个进程,一个进程负责 UI,另一个进程负责后台任务。比如音乐播放器。
代码示例:
<serviceandroid:name=".PlaybackService"android:process=":background"/>
android:process属性的值以“:”开头,名称可任意选取。
在决定是否使用多进程前,应注意,一个不执行任何任务的空进程至少也要占用 1.4 MB内存。
另外要注意进程的相互依赖性,比如如果将 ContentProvider 放在 UI 进程中,而后台任务进程也需要调用 ContentProvider,就会导致 UI 进程一直保留在 RAM 中。