【转自:http://www.trinea.cn/】

一 什么是性能问题

当我们谈到性能问题时,一般是指如下两个概念

1 响应时间

指从用户操作开始到系统给用户以正确反馈的时间。

一般包括逻辑处理时间 + 网络传输时间 + 展现时间。

对于非网络类应用不包括网络传输时间。
展现时间即网页或 App 界面渲染时间。

响应时间是用户对性能最直接的感受。

2 TPS(Transaction Per Second)

TPS指每秒处理的事务数,是系统吞吐量的指标。在搜索系统中,也用QPS(Query Per Second)每秒查询数来衡量。
当TPS过低,就是系统吞吐量过低,用户的直观感受就是响应时间过长。

3 内存、电量

对于后台开发,常将高并发下的内存泄露归为性能问题。
对移动开发来说,性能问题还包括电量和内存使用这两内特殊情况。

二 性能调优方式

根据性能问题的定义,我们可以定向提高性能。就是优化系统响应时间,提高系统吞吐量,减少内存占用。而方式又不外乎下面三大类:

1 降低执行时间

a 利用多线程并发或分布式

b 缓存

包括对象缓存,IO缓存,网络缓存等,比如Fresco等图片框架的网络,本地,内存多级缓存。

c 数据结构和算法优化

利用合适的数据结构,降低算法复杂度。

d 性能更优的底层接口调用

如JNI实现。

e 逻辑优化

整理代码逻辑,去除无用逻辑。

f 需求优化

2 同步改为异步

利用多线程提高TPS。
比如利用Retrofit异步执行网络请求。
AsyncTask内部就是使用线程池方式,可以并发完成任务。

3 提前或延迟操作

提前初始化,当真正需要用到数据的时候就可以直接获得。

延迟初始化,比如程序启动时将可以延后执行的逻辑延后执行,给用户流畅的启动。

三 Android布局优化

主要介绍使用抽象布局标签(include, viewstub, merge)、去除不必要的嵌套和View节点、减少不必要的infalte及其他Layout方面可调优点,顺带提及布局调优相关工具(hierarchy viewer和lint)

1 抽象布局标签

(1) <include>标签

include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,使布局编写更加方便。

下面以在一个布局main.xml中用include引入另一个布局foot.xml为例。main.mxl代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" ><ListView
        android:id="@+id/simple_list_view"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="@dimen/dp_80" /><include layout="@layout/foot.xml" /></RelativeLayout>

其中include引入的foot.xml为公用的页面底部,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" ><Button
        android:id="@+id/button"android:layout_width="match_parent"android:layout_height="@dimen/dp_40"android:layout_above="@+id/text"/><TextView
        android:id="@+id/text"android:layout_width="match_parent"android:layout_height="@dimen/dp_40"android:layout_alignParentBottom="true"android:text="@string/app_name" /></RelativeLayout>

标签唯一需要的属性是layout属性,指定需要包含的布局文件。可以定义android:id和android:layout_*属性来覆盖被引入布局根节点的对应属性值。注意重新定义android:id后,子布局的顶结点i就变化了。

(2) <viewstub>标签

viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。

viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局,联系人详细情况布局等。

下面以在一个布局main.xml中加入网络错误时的提示页面network_error.xml为例。main.mxl代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" >……<ViewStub
        android:id="@+id/network_error_layout"android:layout_width="match_parent"android:layout_height="match_parent"android:layout="@layout/network_error" /></RelativeLayout>

其中network_error.xml为只有在网络错误时才需要显示的布局,默认不会被解析,示例代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" ><Button
        android:id="@+id/network_setting"android:layout_width="@dimen/dp_160"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:text="@string/network_setting" /><Button
        android:id="@+id/network_refresh"android:layout_width="@dimen/dp_160"android:layout_height="wrap_content"android:layout_below="@+id/network_setting"android:layout_centerHorizontal="true"android:layout_marginTop="@dimen/dp_10"android:text="@string/network_refresh" /></RelativeLayout>

在java中通过(ViewStub)findViewById(id)找到ViewStub,通过stub.inflate()展开ViewStub,然后得到子View,如下:

private View networkErrorView;private void showNetError() {// not repeated infalteif (networkErrorView != null) {networkErrorView.setVisibility(View.VISIBLE);return;}ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);networkErrorView = stub.inflate();Button networkSetting = (Button)networkErrorView.findViewById(R.id.network_setting);Button refresh = (Button)findViewById(R.id.network_refresh);
}private void showNormal() {if (networkErrorView != null) {networkErrorView.setVisibility(View.GONE);}
}

在上面showNetError()中展开了ViewStub,同时我们对networkErrorView进行了保存,这样下次不用继续inflate。这就是后面提到的减少不必要的infalte。

viewstub标签大部分属性同include标签类似。

上面代码中展开ViewStub的部分代码如下:

ViewStub stub = (ViewStub)findViewById(R.id.network_error_layout);
networkErrorView = stub.inflate();

也可以写成下面的形式:

View viewStub = findViewById(R.id.network_error_layout);
viewStub.setVisibility(View.VISIBLE);   // ViewStub被展开后的布局所替换
networkErrorView =  findViewById(R.id.network_error_layout); // 获取展开后的布局

效果一致,只是不用显示的转换为ViewStub。通过viewstub的原理我们可以知道将一个view设置为GONE不会被解析,从而提高layout解析速度,而VISIBLE和INVISIBLE这两个可见性属性会被正常解析。

(3) <merge>标签

在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢,不必要的节点和嵌套可通过hierarchy viewer(下面布局调优工具中有具体介绍)或设置->开发者选项->显示布局边界查看。

merge标签可用于两种典型情况:
a. 布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容视图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。
b. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。
以(1) 标签的示例为例,用hierarchy viewer查看main.xml布局如下图:

可以发现多了一层没必要的RelativeLayout,将foot.xml中RelativeLayout改为merge,如下:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" ><Button
        android:id="@+id/button"android:layout_width="match_parent"android:layout_height="@dimen/dp_40"android:layout_above="@+id/text"/><TextView
        android:id="@+id/text"android:layout_width="match_parent"android:layout_height="@dimen/dp_40"android:layout_alignParentBottom="true"android:text="@string/app_name" /></merge>

运行后再次用hierarchy viewer查看main.xml布局如下图,这样就不会有多余的RelativeLayout节点了。

2 去除不必要的嵌套和View节点

(1) 首次不需要使用的节点设置为GONE或使用viewstub

(2) 使用RelativeLayout代替LinearLayout

大约在Android4.0之前,新建工程的默认main.xml中顶节点是LinearLayout,而在之后已经改为RelativeLayout,因为RelativeLayout性能更优,且可以简单实现LinearLayout嵌套才能实现的布局。

4.0及以上Android版本可通过设置->开发者选项->显示布局边界打开页面布局显示,看看是否有不必要的节点和嵌套。4.0以下版本可通过hierarchy viewer查看。

3 减少不必要的infalte

(1) 对于inflate的布局可以直接缓存,用全部变量代替局部变量,避免下次需再次inflate
如上面ViewStub示例中的

if (networkErrorView != null) {networkErrorView.setVisibility(View.VISIBLE);return;
}

(2) ListView提供了item缓存,adapter getView的标准写法,如下:

@Override
public View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;if (convertView == null) {convertView = inflater.inflate(R.layout.list_item, null);holder = new ViewHolder();……convertView.setTag(holder);} else {holder = (ViewHolder)convertView.getTag();}
}/*** ViewHolder* * @author trinea@trinea.cn 2013-08-01*/
private static class ViewHolder {ImageView appIcon;TextView  appName;TextView  appInfo;
}

关于ListView缓存原理可见Android ListView缓存机制。

4、其他点

(1) 用SurfaceView或TextureView代替普通View

SurfaceView或TextureView可以通过将绘图操作移动到另一个单独线程上提高性能。

普通View的绘制过程都是在主线程(UI线程)中完成,如果某些绘图操作影响性能就不好优化了,这时我们可以考虑使用SurfaceView和TextureView,他们的绘图操作发生在UI线程之外的另一个线程上。

因为SurfaceView在常规视图系统之外,所以无法像常规试图一样移动、缩放或旋转一个SurfaceView。TextureView是Android4.0引入的,除了与SurfaceView一样在单独线程绘制外,还可以像常规视图一样被改变。

(2) 使用RenderJavascript

RenderScript是Adnroid3.0引进的用来在Android上写高性能代码的一种语言,语法给予C语言的C99标准,他的结构是独立的,所以不需要为不同的CPU或者GPU定制代码代码。

(3) 使用OpenGL绘图

Android支持使用OpenGL API的高性能绘图,这是Android可用的最高级的绘图机制,在游戏类对性能要求较高的应用中得到广泛使用。

Android 4.3最大的改变,就是支持OpenGL ES 3.0。相比2.0,3.0有更多的缓冲区对象、增加了新的着色语言、增加多纹理支持等等,将为Android游戏带来更出色的视觉体验。

(4) 尽量为所有分辨率创建资源

减少不必要的硬件缩放,这会降低UI的绘制速度,可借助Android asset studio。

5 布局调优工具

(1) hierarchy viewer

hierarchy viewer可以方便的查看Activity的布局,各个View的属性、measure、layout、draw的时间,如果耗时较多会用红色标记,否则显示绿色。
hierarchy viewer.bat位于<sdk>/tools/目录下。使用可见:Using Hierarchy Viewer , 示例图如下:

(2) layoutopt

layoutopt是一个可以提供layout及其层级优化提示的命令行,在sdk16以后已经被lint取代,在Windows->Show View->Other->Android->Lint Warnings查看lint优化提示,lint具体介绍可见Improving Your Code with lint。

四 代码优化

主要介绍Java代码中性能优化方式及网络优化,包括缓存、异步、延迟、数据存储、算法、JNI、逻辑等优化方式。

1 降低执行时间

这部分包括:缓存、数据存储优化、算法优化、JNI、逻辑优化、需求优化几种优化方式。

(1). 缓存

缓存主要包括对象缓存、IO缓存、网络缓存、DB缓存,对象缓存能减少内存的分配,IO缓存减少磁盘的读写次数,网络缓存减少网络传输,DB缓存减少Database的访问次数。

在内存、文件、数据库、网络的读写速度中,内存都是最优的,且速度呈数量级差别,所以尽量将需要频繁访问或访问一次消耗较大的数据存储在缓存中。

Android中常使用缓存:
a. 线程池
b. Android图片缓存,Android图片Sdcard缓存,数据预取缓存

c. 消息缓存
通过handler.obtainMessage复用之前的message,如下:
handler.sendMessage(handler.obtainMessage(0, object));

d. ListView缓存

e. 网络缓存

数据库缓存http response,根据http头信息中的Cache-Control域确定缓存过期时间。

f. 文件IO缓存
使用具有缓存策略的输入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.对文件、网络IO皆适用。

g. layout缓存

h. 其他需要频繁访问或访问一次消耗较大的数据缓存

(2). 数据存储优化

包括数据类型、数据结构的选择。

a. 数据类型选择

字符串拼接用StringBuilder代替String,在非并发情况下用StringBuilder代替StringBuffer。如果你对字符串的长度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,减少空间不够时的再次分配。
64位类型如long double的处理比32位如int慢
使用SoftReference、WeakReference相对正常的强应用来说更有利于系统垃圾回收
final类型存储在常量区中读取效率更高
LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高

b. 数据结构选择

常见的数据结构选择如:
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会死吗,还是建议大家根据自己可能的容量设置初始值。

(3). 算法优化

这个主题比较大,需要具体问题具体分析,尽量不用O(n*n)时间复杂度以上的算法,必要时候可用空间换时间。
查询考虑hash和二分,尽量不用递归。可以从结构之法 算法之道 或微软、Google等面试题学习。

(4). JNI

Android应用程序大都通过Java开发,需要Dalvik的JIT编译器将Java字节码转换成本地代码运行,而本地代码可以直接由设备管理器直接执行,节省了中间步骤,所以执行速度更快。不过需要注意从Java空间切换到本地空间需要开销,同时JIT编译器也能生成优化的本地代码,所以糟糕的本地代码不一定性能更优。
这个优化点会在后面单独用一片博客介绍。

(5). 逻辑优化

这个不同于算法,主要是理清程序逻辑,减少不必要的操作。

(6). 需求优化

这个就不说了,对于sb的需求可能带来的性能问题,只能说做为一个合格的程序员不能只是执行者,要学会说NO。不过不能拿这种接口敷衍产品经理哦。

2、异步,利用多线程提高TPS

充分利用多核Cpu优势,利用线程解决密集型计算、IO、网络等操作。
关于多线程可参考:Java线程池
在Android应用程序中由于系统ANR的限制,将可能造成主线程超时操作放入另外的工作线程中。在工作线程中可以通过handler和主线程交互。

3、提前或延迟操作,错开时间段提高TPS

(1) 延迟操作

不在Activity、Service、BroadcastReceiver的生命周期等对响应时间敏感函数中执行耗时操作,可适当delay。
Java中延迟操作可使用ScheduledExecutorService,不推荐使用Timer.schedule;
Android中除了支持ScheduledExecutorService之外,还有一些delay操作,如:
handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定时等。

(2) 提前操作

对于第一次调用较耗时操作,可统一放到初始化中,将耗时提前。如得到壁纸wallpaperManager.getDrawable();

4、网络优化

更多见 下一章
以下是网络优化中一些客户端和服务器端需要尽量遵守的准则:
a. 图片必须缓存,最好根据机型做图片适配
b. 所有http请求必须添加httptimeout
c. 开启gzip压缩
d. api接口数据以json格式返回,而不是xml或html
e. 根据http头信息中的Cache-Control及expires域确定是否缓存请求结果。
f. 确定网络请求的connection是否keep-alive
g. 减少网络请求次数,服务器端适当做请求合并。
h. 减少重定向次数
i. api接口服务器端响应时间不超过100ms
google正在做将移动端网页速度降至1秒的项目,关注中https://developers.google.com/speed/docs/insights/mobile

五 移动网络优化

介绍针对移动端的网络优化,不限于 Android,同样适用于 iOS 和 H5。

一个网络请求可以简单分为连接服务器 -> 获取数据两个部分。
其中连接服务器前还包括 DNS 解析的过程;获取数据后可能会对数据进行缓存。

1 连接服务器优化策略

(1). 不用域名,用 IP 直连

省去 DNS 解析过程,DNS 全名 Domain Name System,解析意指根据域名得到其对应的 IP 地址。 如 http://www.codekk.com 的域名解析结果就是 104.236.147.76。

首次域名解析一般需要几百毫秒,可通过直接向 IP 而非域名请求,节省掉这部分时间,同时可以预防域名劫持等带来的风险。

当然为了安全和扩展考虑,这个 IP 可能是一个动态更新的 IP 列表,并在 IP 不可用情况下通过域名访问。

(2). 服务器合理部署

服务器多运营商多地部署,一般至少含三大运营商、南中北三地部署。

配合上面说到的动态 IP 列表,支持优先级,每次根据地域、网络类型等选择最优的服务器 IP 进行连接。

对于服务器端还可以调优服务器的 TCP 拥塞窗口大小、重传超时时间(RTO)、最大传输单元(MTU)等。

2 获取数据优化策略

(1). 连接复用

节省连接建立时间,如开启 keep-alive。

Http 1.1 默认启动了 keep-alive。对于 Android 来说默认情况下 HttpURLConnection 和 HttpClient 都开启了 keep-alive。只是 2.2 之前 HttpURLConnection 存在影响连接池的 Bug,具体可见:Android HttpURLConnection 及 HttpClient 选择

(2). 请求合并

即将多个请求合并为一个进行请求,比较常见的就是网页中的 CSS Image Sprites。 如果某个页面内请求过多,也可以考虑做一定的请求合并。

(3). 减小请求数据大小

(3.1) 对于 POST 请求,Body 可以做 Gzip 压缩,如日志。

(3.2) 对请求头进行压缩
这个 Http 1.1 不支持,SPDY 及 Http 2.0 支持。 Http 1.1 可以通过服务端对前一个请求的请求头进行缓存,后面相同请求头用 md5 之类的 id 来表示即可。

(4). CDN 缓存静态资源

缓存常见的图片、JS、CSS 等静态资源。

(5). 减小返回数据大小

(5.1) 压缩
一般 API 数据使用 Gzip 压缩,下图是之前测试的 Gzip 压缩前后对比图。

(5.2) 精简数据格式
如 JSON 代替 XML,WebP 代替其他图片格式。关注公众号 codekk,回复 20 查看关于 WebP 的介绍。

(5.3) 对于不同的设备不同网络返回不同的内容 如不同分辨率图片大小。

(5.4) 增量更新
需要数据更新时,可考虑增量更新。如常见的服务端进行 bsdiff,客户端进行 bspatch。

(5.5) 大文件下载
支持断点续传,并缓存 Http Resonse 的 ETag 标识,下次请求时带上,从而确定是否数据改变过,未改变则直接返回 304。

(6). 数据缓存

缓存获取到的数据,在一定的有效时间内再次请求可以直接从缓存读取数据。

关于 Http 缓存规则 Grumoon 在 Volley 源码解析最后杂谈中有详细介绍。

3 其他优化手段

这类优化方式在性能优化系列总篇中已经有过完整介绍

(1). 预取

包括预连接、预取数据。

(2). 分优先级、延迟部分请求

将不重要的请求延迟,这样既可以削峰减少并发、又可以和后面类似的请求做合并。

(3). 多连接

对于较大文件,如大图片、文件下载可考虑多连接。 需要控制请求的最大并发量,毕竟移动端网络受限。

4 监控

优化需要通过数据对比才能看出效果,所以监控系统必不可少,通过前后端的数据监控确定调优效果。

注:服务器部署方面的优化有参考手 Q 和 QZone 去年的技术分享。

六 数据库性能优化

原理适用于大部分数据库包括Sqlite、Mysql、Oracle、Sql Server,详细介绍了索引(优缺点、分类、场景、规则)和事务,最后介绍了部分单独针对Sqlite的优化。

1 索引

简单的说,索引就像书本的目录,目录可以快速找到所在页数,数据库中索引可以帮助快速找到数据,而不用全表扫描,合适的索引可以大大提高数据库查询的效率。

(1). 优点

大大加快了数据库检索的速度,包括对单表查询、连表查询、分组查询、排序查询。经常是一到两个数量级的性能提升,且随着数据数量级增长。

(2). 缺点

索引的创建和维护存在消耗,索引会占用物理空间,且随着数据量的增加而增加。

在对数据库进行增删改时需要维护索引,所以会对增删改的性能存在影响。

(3). 分类

a. 直接创建索引和间接创建索引

直接创建: 使用sql语句创建,Android中可以在SQLiteOpenHelper的onCreate或是onUpgrade中直接excuSql创建语句,语句如

CREATE INDEX mycolumn_index ON mytable (myclumn)

间接创建: 定义主键约束或者唯一性键约束,可以间接创建索引,主键默认为唯一索引。

b. 普通索引和唯一性索引

普通索引:

CREATE INDEX mycolumn_index ON mytable (myclumn)

唯一性索引:保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使用,语句为

CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)

c. 单个索引和复合索引

单个索引:索引建立语句中仅包含单个字段,如上面的普通索引和唯一性索引创建示例。
复合索引:又叫组合索引,在索引建立语句中同时包含多个字段,语句如:

CREATE INDEX name_index ON username(firstname, lastname)

其中firstname为前导列。

d. 聚簇索引和非聚簇索引(聚集索引,群集索引)

聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列,语句为:

CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH ALLOW_DUP_ROW

其中WITH ALLOW_DUP_ROW表示允许有重复记录的聚簇索引。

非聚簇索引:

CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)

索引默认为非聚簇索引。

(4). 使用场景

在上面讲到了优缺点,那么肯定会对何时使用索引既有点明白又有点糊涂吧,那么下面总结下:
a. 当某字段数据更新频率较低,查询频率较高,经常有范围查询(>, <, =, >=, <=)或order by、group by发生时建议使用索引。并且选择度越大,建索引越有优势,这里选择度指一个字段中唯一值的数量/总的数量。
b. 经常同时存取多列,且每列都含有重复值可考虑建立复合索引

(5). 索引使用规则

a. 对于复合索引,把使用最频繁的列做为前导列(索引中第一个字段)。如果查询时前导列不在查询条件中则该复合索引不会被使用。
create unique index PK_GRADE_CLASS on student (grade, class)
select * from student where class = 2未使用到索引
select * from dept where grade = 3使用到了索引

b. 避免对索引列进行计算,对where子句列的任何计算如果不能被编译优化,都会导致查询时索引失效
select * from student where tochar(grade)='2'

c. 比较值避免使用NULL

d. 多表查询时要注意是选择合适的表做为内表。连接条件要充份考虑带有索引的表、行数多的表,内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘积最小为最佳方案。实际多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。

e. 查询列与索引列次序一致

f. 用多表连接代替EXISTS子句

g. 把过滤记录数最多的条件放在最前面

h. 善于使用存储过程,它使sql变得更加灵活和高效(Sqlite不支持存储过程::>_<:: )

(6)索引检验
建立了索引,对于某条sql语句是否使用到了索引可以通过执行计划查看是否用到了索引。

2 使用事务

使用事务的两大好处是原子提交和更优性能。

(1) 原子提交

原则提交意味着同一事务内的所有修改要么都完成要么都不做,如果某个修改失败,会自动回滚使得所有修改不生效。

(2) 更优性能

Sqlite默认会为每个插入、更新操作创建一个事务,并且在每次插入、更新后立即提交。
这样如果连续插入100次数据实际是创建事务->执行语句->提交这个过程被重复执行了100次。如果我们显示的创建事务->执行100条语句->提交会使得这个创建事务和提交这个过程只做一次,通过这种一次性事务可以使得性能大幅提升。尤其当数据库位于sd卡时,时间上能节省两个数量级左右。
Sqlte显示使用事务,示例代码如下:

public void insertWithOneTransaction() {
//sqliteOpenHelper.getWritableDatabase()表示得到写表权限。SQLiteDatabase db = sqliteOpenHelper.getWritableDatabase();// Begins a transactiondb.beginTransaction();try {// your sqlsfor (int i = 0; i < 100; i++) {db.insert(yourTableName, null, value);}// marks the current transaction as successfuldb.setTransactionSuccessful();} catch (Exception e) {// process ite.printStackTrace();} finally {// end a transactiondb.endTransaction();}
}

3、其他针对Sqlite的优化

(1) 语句的拼接使用StringBuilder代替String

这个就不多说了,简单的string相加会导致创建多个临时对象消耗性能。StringBuilder的空间预分配性能好得多。如果你对字符串的长度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,减少空间不够时的再次分配。

(2) 查询时返回更少的结果集及更少的字段。

查询时只取需要的字段和结果集,更多的结果集会消耗更多的时间及内存,更多的字段会导致更多的内存消耗。

(3) 少用cursor.getColumnIndex

根据性能调优过程中的观察cursor.getColumnIndex的时间消耗跟cursor.getInt相差无几。可以在建表的时候用static变量记住某列的index,直接调用相应index而不是每次查询。

public static final String       HTTP_RESPONSE_TABLE_ID                  = android.provider.BaseColumns._ID;
public static final String       HTTP_RESPONSE_TABLE_RESPONSE            = "response";
public List<Object> getData() {……cursor.getString(cursor.getColumnIndex(HTTP_RESPONSE_TABLE_RESPONSE));……
}

优化为:

public static final String       HTTP_RESPONSE_TABLE_ID                  = android.provider.BaseColumns._ID;
public static final String       HTTP_RESPONSE_TABLE_RESPONSE            = "response";
public static final int          HTTP_RESPONSE_TABLE_ID_INDEX            = 0;
public static final int          HTTP_RESPONSE_TABLE_URL_INDEX           = 1;
public List<Object> getData() {……cursor.getString(HTTP_RESPONSE_TABLE_RESPONSE_INDEX);……
}

4、异步线程

Sqlite是常用于嵌入式开发中的关系型数据库,完全开源。
与Web常用的数据库Mysql、Oracle db、sql server不同,Sqlite是一个内嵌式的数据库,数据库服务器就在你的程序中,无需网络配置和管理,数据库服务器端和客户端运行在同一进程内,减少了网络访问的消耗,简化了数据库管理。不过Sqlite在并发、数据库大小、网络方面存在局限性,并且为表级锁,所以也没必要多线程操作。

Android中数据不多时表查询可能耗时不多,不会导致anr(application not response),不过大于100ms时同样会让用户感觉到延时和卡顿,可以放在线程中运行,但sqlite在并发方面存在局限,多线程控制较麻烦,这时候可使用单线程池,在任务中执行db操作,通过handler返回结果和ui线程交互,既不会影响UI线程,同时也能防止并发带来的异常。
可使用Android提供的AsyncQueryHandler或类似如下代码完成:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(new Runnable() {@Overridepublic void run() {// db operetions, u can use handler to send message afterdb.insert(yourTableName, null, value);handler.sendEmptyMessage(xx);}
});

七 性能优化实例

本文主要分享自己在appstore项目中的性能调优点,包括同步改异步、缓存、Layout优化、数据库优化、算法优化、延迟执行等。

1 性能瓶颈点

整个页面主要由6个Page的ViewPager,每个Page为一个GridView,GridView一屏大概显示4*4的item信息(本文最后有附图)。由于网络数据获取较多且随时需要保持页面内app下载进度及状态,所以出现以下性能问题
a. ViewPager左右滑动明显卡顿
b. GridView上下滚动明显卡顿
c. 其他Activity返回ViewPager Activity较慢
d. 网络获取到展现速度较慢

2 性能瓶颈点

主要使用Traceview、monkey、monkey runner调试,traceview类似java web调优的visualvm,使用方法如下:
在需要调优的activity onCreate函数中添加
android.os.debug.startMethodTracing("Entertainment");
onDestrory函数中添加:
android.os.debug.stopMethodTracing();
程序退出后会在sd卡根目录下生成Entertainment.trace这个文件,cmd到android sdk的tools目录下运行traceview.bat Entertainment.trace即可,截图如下:

从中可以看出各个函数的调用时间、调用次数、平均调用时间、时间占用百分比等从而定位到耗时的操作。monkey、monkey runner更详细的见后面博客介绍

3、性能调优点

主要包括同步改异步、缓存、Layout优化、数据库优化、算法优化、延迟执行。

(1). 同步改异步

这个就不用多讲了,耗时操作放在线程中执行防止占用主线程,一定程度上解决anr。
但需要注意线程和service结合(防止activity被回收后线程也被回收)以及线程的数量
线程池使用可见java的线程池

(2). 缓存

java的对象创建需要分配资源较耗费时间,加上创建的对象越多会造成越频繁的gc影响系统响应。主要使用单例模式、缓存(图片缓存、线程池、View缓存、IO缓存、消息缓存、通知栏notification缓存)及其他方式减少对象创建。
(2.1). 单例模式
对于创建开销较大的类可使用此方法,保证全局一个实例,在程序运行过程中该类不会因新建额外对象产生开销。示例代码如下:

public class Singleton {private static Object    obj      = new Object();private static volatile Singleton instance = null;private Singleton(){}public static Singleton getInstance() {// if already inited, no need to get lock everytimeif (instance == null) {synchronized (obj) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

(2.2). 缓存
程序中用到了图片缓存、线程池、View缓存、IO缓存、消息缓存、通知栏notification缓存等。

a. 图片缓存:见ImageCache和ImageSdCache

b. 线程池:使用Java的Executors类,通过newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool提供四种不同类型的线程池

c. View缓存:
可见ListView缓存机制:

@Override
public View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder;//通过convertView是否为null减少layout inflate次数,通过静态的ViewHolder减少findViewById的次数,这两个函数尤其是inflate是相当费时间的if (convertView == null) {convertView = inflater.inflate(R.layout.type_item, null);holder = new ViewHolder();holder.imageView = (ImageView)convertView.findViewById(R.id.app_icon);holder.textView = (TextView)convertView.findViewById(R.id.app_name);convertView.setTag(holder);} else {holder = (ViewHolder)convertView.getTag();}holder.imageView.setImageResource(R.drawable.index_default_image);holder.textView.setText("");return convertView;
}/*** ViewHolder*/
static class ViewHolder {ImageView imageView;TextView  textView;
}

d. IO缓存:
使用具有缓存策略的输入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.对文件、网络IO皆适用。

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.

(2.3). 其他
能创建基类解决问题就不用具体子类:除需要设置优先级的线程使用new Thread创建外,其余线程创建使用new Runnable。因为子类会有自己的属性创建需要更多开销。
控制最大并发数量:使用Java的Executors类,通过Executors.newFixedThreadPool(nThreads)控制线程池最大线程并发
对于http请求增加timeout

(3). Layout优化

使用抽象布局标签(include, viewstub, merge)、去除不必要的嵌套和View节点、减少不必要的infalte及其他Layout方面可调优点,顺带提及布局调优相关工具(hierarchy viewer和lint)。具体可见上述的布局优化
TextView属性优化:TextView的android:ellipsize=”marquee”跑马灯效果极耗性能,具体原因还在深入源码中

(4). 数据库优化

主要包括索引和事务及针对Sqlite的优化。具体可见上述的数据库优化

(5). 算法优化

这个就是个博大精深的话题了,只介绍本应用中使用的。
使用hashMap代替arrayList,时间复杂度降低一个数量级

(6). 延迟执行

对于很多耗时逻辑没必要立即执行,这时候我们可以将其延迟执行。
线程延迟执行 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
消息延迟发送 handler.sendMessageDelayed(handler.obtainMessage(0), 1000);

4、本程序性能调优结果

(1). ViewPager左右滑动明显卡顿和GridView上下滚动明显卡顿

(1.1). 去掉TextView的android:ellipsize=”marquee”
(1.2). 修改图片缓存的最大线程数,增加http timeout
(1.3). 修改设置app是否已安装的状态,具体代码修改如下:

List<PackageInfo> installedPackageList = getPackageManager().getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
List<App> installedAppList = function(installedAppList)
for (App app : appList) {for (App installedApp : installedAppList) {}
}

修改为:

for (App app : appList) {Pair<Integer, String> versionInfo = INSTALLED_APP_MAP.get(app.getPackageName());if (versionInfo != null) {} else {}
}

从每次获取List installedAppList = getPackageManager().getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);修改为只在有应用安装或卸载广播时获取应用列表,并且用hashMap代替installedAppList减少查询时间。
将平均执行时间从201ms降低到1ms。

(2).其他Activity返回ViewPager Activity较慢

定位:在onStart函数
解决:使用延迟策略,具体代码修改如下:

@Override
public void onStart() {super.onStart();appUpdateListAdapter.notifyDataSetChanged();
}

修改为:

public void onStart() {super.onStart();// delay send messagehandler.sendMessageDelayed(handler.obtainMessage(MessageConstants.WHAT_NOTIFY_DATA_CHANGED), 100);
}private class MyHandler extends Handler {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case MessageConstants.WHAT_NOTIFY_DATA_CHANGED:if (appUpdateListAdapter != null) {appUpdateListAdapter.notifyDataSetChanged();}break;}}
}

(3).网络获取到展现速度较慢

定位:在HttpURLConnection.getInputStream()之后的处理
解决:使用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) {}

上述内容来自: http://www.trinea.cn/android/android-performance-demo/
http://www.trinea.cn/android/performance/
http://www.trinea.cn/android/mobile-performance-optimization/
http://www.trinea.cn/android/java-android-performance/
http://www.trinea.cn/android/layout-performance/
http://www.trinea.cn/android/database-performance/

Android 和 Java 性能优化最佳实践相关推荐

  1. Android应用性能优化最佳实践.

    移动开发 Android应用性能优化最佳实践 罗彧成 著 图书在版编目(CIP)数据 Android应用性能优化最佳实践 / 罗彧成著. -北京:机械工业出版社,2017.1 (移动开发) ISBN ...

  2. 前端性能优化最佳实践(转)

    转载请注明: 转载自WEB前端开发(www.css119.com)-关注常见的WEB前端开发问题.最新的WEB前端开发技术(webApp开发.移动网站开发).最好的WEB前端开发工具和最全的WEB前端 ...

  3. 高性能网站建设进阶指南:Web开发者性能优化最佳实践 pdf扫描版

    高性能网站建设进阶指南:Web开发者性能优化最佳实践是<高性能网站建设指南>姊妹篇.作者Steve Souders是Google Web性能布道者和Yahoo!前首席性能工程师.在本书中, ...

  4. 前端性能优化最佳实践

    本文主要考量客户端性能.服务器端和网络性能,内容框架来自 Yahoo Developer Network,包含 7 个类别共 35 条前端性能优化最佳实践,在此基础上补充了一些相关或者更符合主流技术的 ...

  5. Java性能优化实践:分享Java性能优化的实践经验

    Java性能优化是一个非常重要的话题,在现代软件开发中扮演着至关重要的角色.在本篇博客中,我将分享一些Java性能优化的实践经验,包括使用JavaProfiler分析程序性能.使用JMH进行基准测试. ...

  6. Android 性能优化最佳实践

    本文由玉刚说写作平台提供写作赞助 原作者:Mr.S 版权声明:本文版权归微信公众号玉刚说所有,未经许可,不得以任何形式转载 什么是性能 快,稳,省,小,这四点很形象的代表了性能的四个方面,同时也让我们 ...

  7. Android性能优化最佳实践,源码+原理+手写框架

    前言 众所周知,Android是一个基于Linux实现的操作系统.但对于Linux内核来说,Android也仅仅只是一个运行在内核之上的应用程序,与其他运行在内核之上的应用程序没有任何区别. 所以An ...

  8. 何崚谈阿里巴巴前端性能优化最佳实践

    转载:http://www.infoq.com/cn/interviews/hl-alibaba-front-end-performance-optimization 大家好,我现在在阿里巴巴园区采访 ...

  9. ASP.NET Core 性能优化最佳实践

    本文提供了 ASP.NET Core 的性能最佳实践指南. 译文原文地址:https://docs.microsoft.com/en-us/aspnet/core/performance/perfor ...

  10. 何俊谈阿里巴巴前端性能优化最佳实践-笔记

    网站页面前端优化对网站核心页面基于Wise load的原则做定点性能优化,减少HTTP请求,减少DNS请求时间,减少页面DOM的数量,做一些图片.JS压缩等.减少HTTP请求方案:阿里开发了自动合并C ...

最新文章

  1. POJ1990:MooFest——题解
  2. Mozilla Prism v0.9 For Windows/Linux/Mac
  3. python spangt_python怎么爬去spanlt;/span中间标签的内容
  4. C语言九十五之实现经典的反转数组(通过指针或数组下标操作)
  5. opencv配置_Opencv在vs2012下的配置
  6. 西安下雪了,做了一个室内温度计
  7. python logger设置信息取得_shell 脚本中如何获取 python logging 打印的信息?
  8. dojo 加载自定义module的路径问题
  9. Centos7.0安装 Lets encrypt 的SSL证书
  10. Bootstrap 和 LESS
  11. linqto 多个关键字模糊查询_查询函数Choose、Lookup、Hlookup、Vlookup应用技巧解读
  12. 项目四管理计算机中的资源,第十七章-计算机在项目管理中的应用PPT课件.ppt
  13. Tor 正在开发匿名即时聊天工具
  14. mac自带的词典不能用
  15. 2022,一名85后程序猿之感慨,加油
  16. python md5解密方法与技巧_python ---- 爬取 md5解密结果 的小脚本
  17. Markdown表格合并单元格
  18. 用python求解一元二次方程组
  19. html5 sandbox,”基情“无限的IE10和HTML5
  20. 15---Mysql的多表+事务

热门文章

  1. vue页面加载时闪现_Vue 闪现解决
  2. 天气 经纬度 高德_高德获取经纬度与对应地区(思路:先获取经纬度 ---再通过经纬度获取地址)...
  3. 03-平方矩阵的快速解法
  4. python 矩阵元素平方_NumPy之计算两个矩阵的成对平方欧氏距离
  5. 洛谷P1512伊甸园的日历游戏题解
  6. pygame基本实现塔防游戏
  7. 跟计算机断层扫描相关的技术,数字化X线摄影设备、计算机断层扫描设备和相关方法与流程...
  8. 安全事故 没有“高级失误”
  9. Excel查询A列中的数据是否在B中存在
  10. Validation参数校验注解