标签:
【在Android项目过程中,为了使用户体验越来越良好,这时我们就需要对代码及各方面进行性能的优化了。由于做项目很忙,几乎没什么时间写博客。下面我就仅以我自己的个人理解和亲身项目经历来叙述Android性能优化方面的知识和理解吧。希望对大家有帮助!
当然,对于Android的性能优化,是各方各面的(比如Android系统本身的各种库类相对于iOS系统来说都是有些差距的,相同内存下,Android可能会更卡顿一些....),可能我也写得不是很深入,而且排版比较凌乱,希望大家能多多理解。】
响应时间是指用户从开始操作一直到系统给用户以正确反馈之间的时间差。一般包括 逻辑处理时间 + 网络传输时间 + 展现时间。对于非网络类应用不包括网络传输时间。展现时即网页或 App 界面渲染时间。
响应时间是用户对性能最直接的感受。
TPS(Transaction Per Second)
TPS为每秒处理的事务数,是系统吞吐量的指标,在搜索系统中也用QPS(Query Per Second)衡量。TPS一般与响应时间反相关。通常所说的性能问题就是指响应时间过长、系统吞吐量过低。对于后台开发来说,也常将高并发下内存泄漏归为性能问题。而对于移动开发来说,性能问题还包括电量、内存使用这两类较特殊情况。
a.利用多线程并发或分布式提高 TPS
多线程并发或采用分布式来编程自然是比使用单线程要快得多。
b. 缓存(包括对象缓存、IO 缓存、网络缓存等)采用缓存可以比直接从网络上加载数据快捷方便,而且可以让用户第二次及之后加载内容数据时
不需要使用流量(因为第一次加载后,直接把数据存储在了设备上,这大大提升了用户体验),并且能大大加速Android设备显示内容,缓存
的话,我们可以使用三级缓存机制(这里就暂时不细说三级缓存了,有时间的时候再写篇博客,希望大家多多关注。)
c. 数据结构和算法优化
对于数据结构和算法优化这方面,相信不用我多说,大家也都可以理解的。记得大学老师告诉我们说,大学毕业时最好多去查看一下我们的
数据结构和算法分析这块的内容,熟悉这一块,至少能让你找的工作多1k,这句话是很有道理的。
(来个小插曲。大家都知道程序员是薪资相比于其他行业来说是相当高的了。然后就有下边的例子!)
1.对于普通的程序员来说,就是每天敲代码敲代码,然后薪资可能在13k左右。这个时候,专攻技术的程序员是很累的。而且几乎没什么属于
自己个人的业余时间。
2.对于进阶之后并且有了管理经验的程序员来说,这时候他可能是项目经理啊或者技术大牛之类的,这时候薪资比较让人仰望啦,薪资每月30k,
而人家对于数据结构啊算法优化这块已经做得挺牛逼的了。这个时候,有技术又有管理经验的程序员就比较轻松了,而且业余也是有蛮多个人时间的。
3.然后继续进阶上升好几个档次,数据结构啊算法分析啊,那都是小菜一碟,这时候被应聘进入天宫一号啊神州十号的研发,这个时候呢,
别人就基本不用敲代码了,当需要修改一行代码时可能就需要开一两个月的会议了,然后每天基本就是开会开会......(虽然有点夸张,但很现实)
所以说呢,数据结构和算法优化在编程的优化过程中还是相当重要的。打个比方,如果别人能用100行代码做到你1000行代码的功能,
你说谁的程序效率会更高?
d. 性能更优的底层接口调用,如 JNI 实现
JNI的引入,大大的提升了性能而且降低了很多时间,JNI的实现主要是通过底层的C或C++语言来编写一些重要的类库(一般里边可以写一
些实现功能的核心代码,然后生成.so文件,相比于反编译Java的.class文件,反编译.so文件可以说是几乎不可能完成的事。比如:
引入百度地图的一些.so文件,这就是通过JNI来实现的),我们知道,对比Java代码,使用C或C++编写出来的代码执行速度显然要快得多。
e. 逻辑优化
(略)
f. 需求优化
(略)
利用多线程提高TPS
提前操作或延迟操作
提前操作:这是很好理解的,比如说当要加载大量的数据时,我们可以通过第一次用户登录使用时,在静态代码块中把所有数据加载出来,然后虽然第一次需要耗费大量的时间,可是之后的操作会顺畅好多,几乎可以很快速的显示出数据。这会给用户一个良好的体验。
延迟操作:延迟操作相对来说,从字面意思来说就比较难理解了,“延迟”是什么意思呢?等我一说,你就懂了。比如:我们要让后半段程序慢一些执行,Java中我们直接调用System.sleep(3000)(延迟3秒,注:这样是会报异常的),对于我们的Android编程来说,这样就有点low了,所以在Android中我们通常使用SystemClock.sleep(3000)。很好理解延迟操作吧?
通过索引优化概念
简单的说,索引就像书本的目录,目录可以快速找到所在页数,数据库中索引可以帮助快速找到数据,而不用全表扫描,合适的索引可以
大大提高数据库查询的效率。
优点
大大加快了数据库检索的速度,包括对单表查询、连表查询、分组查询、排序查询。经常是一到两个数量级的性能提升,且随着数据数量级增长。
缺点
索引的创建和维护存在消耗,索引会占用物理空间,且随着数据量的增加而增加。在对数据库进行增删改时需要维护索引,所以会对增删改的性能存在影响。
使用场景
当某字段数据更新频率较低,查询频率较高,经常有范围查询(>, <, =, >=, <=)或order by、group by发生时建议使用索引。并且选择度越大,
建索引越有优势,这里选择度指一个字段中唯一值的数量/总的数量。
经常同时存取多列,且每列都含有重复值可考虑建立复合索引
索引分类
直接创建索引和间接创建索引
直接创建: 使用sql语句创建,Android中可以在SQLiteOpenHelper的onCreate或是onUpgrade中直接excuSql创建语句,语句如
CREATE INDEX mycolumn_index ON mytable (myclumn)
间接创建: 定义主键约束或者唯一性键约束,可以间接创建索引,主键默认为唯一索引。
普通索引和唯一性索引
普通索引:CREATE INDEX mycolumn_index ON mytable (myclumn)
唯一性索引:保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使用,语句为
CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)
单个索引和复合索引
单个索引:索引建立语句中仅包含单个字段,如上面的普通索引和唯一性索引创建示例。
复合索引:又叫组合索引,在索引建立语句中同时包含多个字段,语句如:CREATE INDEX name_index ON username(firstname, lastname)其中firstname为前导列。
聚簇索引和非聚簇索引(聚集索引,群集索引)
聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列,语句为:
CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITH ALLOW_DUP_ROW,
其中WITH ALLOW_DUP_ROW表示允许有重复记录的聚簇索引
非聚簇索引:CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn),索引默认为非聚簇索引
少用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);
……
}
Sqlite是常用于嵌入式开发中的关系型数据库,完全开源。
与Web常用的数据库Mysql、Oracle db、sql server不同,Sqlite是一个内嵌式的数据库,数据库服务器就在你的程序中,无需网络配置和管理,
数据库服务器端和客户端运行在同一进程内,减少了网络访问的消耗,简化了数据库管理。不过Sqlite在并发、数据库大小、网络方面存在局限性,
并且为表级锁,所以也没必要多线程操作。
Android中数据不多时表查询可能耗时不多,不会导致anr,不过大于100ms时同样会让用户感觉到延时和卡顿,可以放在线程中运行,
但sqlite在并发方面存在局限,多线程控制较麻烦,这时候可使用单线程池,在任务中执行db操作,通过handler返回结果和ui线程交互,
既不会影响UI线程,同时也能防止并发带来的异常。
可使用Android提供的AsyncQueryHandler或类似如下代码完成:
Java
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
// db operetions, u can use handler to send message after
db.insert(yourTableName, null, value);
handler.sendEmptyMessage(xx);
}
});
(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>
<include>标签唯一需要的属性是layout属性,指定需要包含的布局文件。可以定义android:id和android:layout_*属性来覆盖被引入布局根节点的对应属性值。注意重新定义android:id后,子布局的顶结点i就变化了。
<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 infalte
if (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这两个可见性属性会被正常解析。
c.<merge>标签
在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢,不必要的节点和嵌套可通过hierarchy viewer(下面布局调优工具中有具体介绍)或设置->开发者选项->显示布局边界查看。
使用场景
a. 布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容视图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。
b. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。
(1) 首次不需要使用的节点设置为GONE或使用viewstub
(2) 使用RelativeLayout代替LinearLayout
大约在Android4.0之前,新建工程的默认main.xml中顶节点是LinearLayout,而在之后已经改为RelativeLayout,因为RelativeLayout性能更优,且可以简单实现LinearLayout嵌套才能实现的布局。
4.0及以上Android版本可通过设置->开发者选项->显示布局边界打开页面布局显示,看看是否有不必要的节点和嵌套。4.0以下版本可通过hierarchy viewer查看。
(1) 对于inflate的布局可以直接缓存,用全局变量代替局部变量,避免下次需再次inflate
(2) ListView提供了item缓存,adapter getView的标准写法
(1) 用SurfaceView或TextureView代替普通View
SurfaceView或TextureView可以通过将绘图操作移动到另一个单独线程上提高性能。
普通View的绘制过程都是在主线程(UI线程)中完成,如果某些绘图操作影响性能就不好优化了,这时我们可以考虑使用SurfaceView和TextureView,他们的绘图操作发生在UI线程之外的另一个线程上。
因为SurfaceView在常规视图系统之外,所以无法像常规试图一样移动、缩放或旋转一个SurfaceView。
TextureView是Android4.0引入的,除了与SurfaceView一样在单独线程绘制外,还可以像常规视图一样被改变。
(2) 使用RenderScript
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
(1) hierarchy viewer
hierarchy viewer可以方便的查看Activity的布局,各个View的属性、measure、layout、draw的时间,如果耗时较多会用红色标记,否则显示绿色。
hierarchy viewer.bat位于<sdk>/tools/目录下。使用可见:Using Hierarchy Viewer , 示例图如下:
为什么使用HierarchyViewer
不合理的布局会使我们的应用程序UI性能变慢,HierarchyViewer能够可视化的角度直观地获得UI布局设计结构和各种属性的信息,帮助我们优化布局设计。HierarchyViewer是我们优化程序的工具之一,它是Android自带的非常有用的工具,可以帮助我们更好地检视和设计用户界面(UI),绝对是UI检视的利器。
(2) lint
Android Lint是在ADT 16(和 Tools 16)引入的一个新工具,可以扫描Android 项目源码中潜在的bug 。它可同时作为一个命令行工具,以及集??成在Eclipse(如下所述),和IntelliJ(详细信息)中。这个架构是有意独立于IDE的,因此它有希望与其他的IDE,其他的构建工具和持续集成系统集成。
以下是它进行扫描的一些错误类型的例子:
缺少翻译(和未使用的翻译)
布局性能问题(老的layoutopt工具会用于查找所有这样的问题,和除此之外更多的问题)
未使用的资源
不一致的数组大小(当在多个配置中定义数组)
可访问性和国际化问题(硬编码字符串,缺少contentDescription等)
图标问题 (如丢失密度、 重复图标、 错误尺寸等)
可用性问题 (如不在文本字段上指定输入的类型)
清单错误
Android Studio中集成了Lint检查,提供了一套静态代码分析工具,它可以帮助我们检查项目中存在的问题,让我们更有规范性的开发App。它可以检查出:xml文件中是否存在hardcode硬编码、unused resources没有使用到的资源、probable bug可能的bug等等
此外,还有Java代码的优化、移动网络优化(如直接使用ip地址而不是使用域名来加载数据等)。
标签:
原文地址:http://blog.csdn.net/jimy___fight/article/details/51498087