总体上来说,想要写出高效代码,我们要遵循两条基本的原则:
-不作没有必要的工作。
-尽量避免内存分配。
一、容易引发性能问题的点:
- 架构 算法 数据结构
- 布局
- 数据库
- 网络
- 线程同步改异步
- 提前或延时处理
- 缓存(IO,线程池,消息,View,图片)
- 耗电量
二、找出性能瓶颈的方法
1.Code Review
2.代码性能测量
2-1.通过性能Log记录函数调用时间
Log.d("Performance", "Load media info begin......");
long start = System.currentTimeMillis();
Log.d("Performance", "Load media info end, Total Time: " + (System.currentTimeMillis() - start) + " ms");
2-2.使用性能分析工具:
•Traceview(参考:Android性能调优工具TraceView介绍)
•Monkey
•MonkeyRunner
注:后两个我个人没有使用过,不做介绍,优先推荐使用TraceView工具
三、如何优化?
1.架构设计
使用合理的设计模式,如MVP,MVC,单例模式(对于创建开销较大的类可使用此方法,保证全局一个实例,在程序运行过程中该类不会因新建额外对象产生开销),针对接口编程,合理使用继承和多态,力求强内聚松耦合,比如Android intent机制 binder机制的使用
2.算法优化
用快速排序代替冒泡排序
用二分查找代替线性查找
尽量不用递归
3.数据结构
使用hashMap代替arrayList,时间复杂度降低一个数量级
对集合类的灵活使用,特别是HashMap的使用,极大的提高查找性能。
使用不要全部使用ArrayList,合理使用LinkedList等易于插入和删除的集合
使用SparseArray、SparseIntArray、SparseBooleanArray来替代某些特定的HashMap
字符串拼接用StringBuilder代替String,在非并发情况下用StringBuilder代替StringBuffer。如果你对字符串的长度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,减少空间不够时的再次分配。
使用SoftReference、WeakReference相对正常的强应用来说更有利于系统垃圾回收
final类型存储在常量区中读取效率更高
LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高
常见的数据结构选择如:
ArrayList和LinkedList的选择,ArrayList根据index取值更快,LinkedList更占内存、随机插入删除更快速、扩容效率更高。一般推荐ArrayList。
ArrayList、HashMap、LinkedHashMap、HashSet的选择,hash系列数据结构查询速度更优,ArrayList存储有序元素,HashMap为键值对数据结构,LinkedHashMap可以记住加入次序的hashMap,HashSet不允许重复元素。
HashMap、WeakHashMap选择,WeakHashMap中元素可在适当时候被系统垃圾回收器自动回收,所以适合在内存紧张型中使用。
Collections.synchronizedMap和ConcurrentHashMap的选择,ConcurrentHashMap为细分锁,锁粒度更小,并发性能更优。Collections.synchronizedMap为对象锁,自己添加函数进行锁控制更方便。
Android也提供了一些性能更优的数据类型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。
Sparse系列的数据结构是为key为int情况的特殊处理,采用二分查找及简单的数组存储,加上不需要泛型转换的开销,相对Map来说性能更优。不过我不太明白为啥默认的容量大小是10,是做过数据统计吗,还是说现在的内存优化不需要考虑这些东西,写16会死吗,还是建议大家根据自己可能的容量设置初始值。
4.Layout布局优化
a. 使用<merge>减少Android程序布局中View的层次,View层次越多,效率就越低
b. 使用<include>复用布局
c. 使用<viewstub>懒加载布局
5.数据库
使用事务
建立索引
读取数据库超过100ms会有卡顿感,使用异步单线程,既能消除卡顿也能避免数据库的并发操作
6.移动网络优化
一个网络请求可以简单分为连接服务器 -> 获取数据两个部分。
其中连接服务器前还包括 DNS 解析的过程;获取数据后可能会对数据进行缓存。
以下是网络优化中一些客户端和服务器端需要尽量遵守的准则:
a. 连接复用,节省连接建立时间,如开启 keep-alive
b. 所有http请求必须添加httptimeout,api接口服务器端响应时间不超过100ms
c. 减小请求数据大小,如开启gzip压缩
d. 减小返回数据大小,api接口数据以json格式返回,而不是xml或html,开启gzip
g. 减少网络请求次数,服务器端适当做请求合并。
h. 减少重定向次数 图片必须缓存,最好根据机型做图片做图片适配
7.多线程并发,同步改异步
耗时操作放在线程中执行防止占用主线程,一定程度上解决anr。但需要注意线程和service结合(防止activity被回收后线程也被回收)。
8.提前或延迟处理,错开时间段避免在同一时间干过多的事情
对于很多耗时逻辑没必要立即执行,这时候我们可以将其延迟执行。
线程延迟执行 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
消息延迟发送 handler.sendMessageDelayed(handler.obtainMessage(0), 1000);
(1) 延迟操作
不在Activity、Service、BroadcastReceiver的生命周期等对响应时间敏感函数中执行耗时操作,可适当delay。
Java中延迟操作可使用ScheduledExecutorService,不推荐使用Timer.schedule;
Android中除了支持ScheduledExecutorService之外,还有一些delay操作,如
handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定时等。
(2) 提前操作
对于第一次调用较耗时操作,可统一放到初始化中,将耗时提前。如得到壁纸wallpaperManager.getDrawable();
9.缓存 (图片缓存、线程池、View缓存、IO缓存、消息缓存、通知栏notification缓存)
a.图片缓存:
LruCache DiskLruCache
b.线程池:
使用Java的Executors类,通过newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool提供四种不同类型的线程池
c.View缓存:
通过convertView是否为null减少layout inflate次数,通过静态的ViewHolder减少findViewById的次数,这两个函数尤其是inflate是相当费时间的
d. IO缓存:
使用具有缓存策略的输入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.对文件、网络IO皆适用.
使用BufferedReader替代BufferedInputStream获取时间从100ms降低到3ms
HttpURLConnection con = (HttpURLConnection)url.openConnection();
InputStream input = con.getInputStream();
while (input.read(buffer, 0, 1024) != -1) {
}
--------------------------------------------------------------------------------------
HttpURLConnection con = (HttpURLConnection)url.openConnection();
BufferedReader input = new BufferedReader(new InputStreamReader(con.getInputStream()));
String s;
while ((s = input.readLine()) != null) {
}
e. 消息缓存:
通过 Handler 的 obtainMessage 回收 Message 对象,减少 Message 对象的创建开销 handler.sendMessage(handler.obtainMessage(1))
f. 通知栏notification缓存:
下载中需要不断改变通知栏进度条状态,如果不断新建Notification会导致通知栏很卡。这里我们可以使用最简单的缓存
Map<String, Notification> notificationMap = new HashMap<String, Notification>();如果notificationMap中不存在,则新建notification并且put into map.
g. 网络缓存
单例数据缓存:建立一个管理数据的类,管理所有数据,当主界面消失后,由于Application本身没有实际退出,因此,数据本身也没有释放掉,下次启动时,省去了加载数据的时间,当然,这并不是一个好的行为。
使用Message自身的缓存,避免重复创建Message实例
数据池(可参考Message Pool的实现方式)
10.电池优化
a. 在需要网络连接的程序中,首先检查网络连接是否正常,如果没有网络连接,那么就不需要执行相应的程序。
b. 为了减少电量的消耗,在蜂窝移动网络下,最好做到批量执行网络请求,尽量避免频繁的间隔网络请求Job Scheduler
c. 使用效率高的数据格式和解析方法JSON。
d. 在进行大数据量下载时,尽量使用GZIP方式下载。HttpUriConnection默认开启了GZIP
e. 很多人开发的程序后台都会一个service不停的去服务器上更新数据,在不更新数据的时候就让它sleep,这种方式是非常耗电的,通常情况下,我们可以使用AlarmManager来定时启动服务
11.JNI、逻辑优化、需求优化
(4). JNI
Android应用程序大都通过Java开发,需要Dalvik的JIT编译器将Java字节码转换成本地代码运行,而本地代码可以直接由设备管理器直接执行,节省了中间步骤,所以执行速度更快。不过需要注意从Java空间切换到本地空间需要开销,同时JIT编译器也能生成优化的本地代码,所以糟糕的本地代码不一定性能更优。
能创建基类解决问题就不用具体子类:除需要设置优先级的线程使用new Thread创建外,其余线程创建使用new Runnable。因为子类会有自己的属性创建需要更多开销。