Android性能优化
内存泄漏和性能优化方式
Android性能优化(上)
数据库优化和网络优化
Android性能优化(中)
我们继续说说Android布局和代码优化
布局优化
抽象布局标签
< 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后,子布局的顶结点id就变化了。
< 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() {
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。
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这两个可见性属性会被正常解析。
< merge >标签
在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢,不必要的节点和嵌套可通过hierarchy viewer(下面布局调优工具中有具体介绍)或设置->开发者选项->显示布局边界查看。
使用场景:
a. 布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容视图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。
b. 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。
示例:
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布局
这样就少了一层嵌套
去除不必要的嵌套和View节点
- 首次不需要使用的节点设置为GONE或使用viewstub
- 使用RelativeLayout代替LinearLayout
大约在Android4.0之前,新建工程的默认main.xml中顶节点是LinearLayout,而在之后已经改为RelativeLayout,因为RelativeLayout性能更优,且可以简单实现LinearLayout嵌套才能实现的布局,在studio2.3中已经改为ConstraintLayout约束布局,几乎可以不嵌套。
4.0及以上Android版本可通过设置->开发者选项->显示布局边界打开页面布局显示,看看是否有不必要的节点和嵌套。4.0以下版本可通过hierarchy viewer查看。
减少不必要的infalte
- 对于inflate的布局可以直接缓存,用全局变量代替局部变量,避免下次需再次inflate
- 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
*/
private static class ViewHolder {
ImageView appIcon;
TextView appName;
TextView appInfo;
}
不过现在几乎都用RecyclerView,它的灵活性与可替代性比listview更好,建议使用RecyclerView。
其他点
用SurfaceView或TextureView代替普通View
- SurfaceView或TextureView可以通过将绘图操作移动到另一个单独线程上提高性能。
- 普通View的绘制过程都是在主线程(UI线程)中完成,如果某些绘图操作影响性能就不好优化了,这时我们可以考虑使用SurfaceView和TextureView,他们的绘图操作发生在UI线程之外的另一个线程上。
- 因为SurfaceView在常规视图系统之外,所以无法像常规试图一样移动、缩放或旋转一个SurfaceView。
- TextureView是Android4.0引入的,除了与SurfaceView一样在单独线程绘制外,还可以像常规视图一样被改变。
使用RenderScript
RenderScript是Adnroid3.0引进的用来在Android上写高性能代码的一种语言,语法给予C语言的C99标准,他的结构是独立的,所以不需要为不同的CPU或者GPU定制代码代码。
稍微了解了一下,没有深入去研究,不做分析 O(∩_∩)O哈哈~
使用OpenGL绘图
Android支持使用OpenGL API的高性能绘图,这是Android可用的最高级的绘图机制,在游戏类对性能要求较高的应用中得到广泛使用。
Android 4.3最大的改变,就是支持OpenGL ES 3.0。相比2.0,3.0有更多的缓冲区对象、增加了新的着色语言、增加多纹理支持等等,将为Android游戏带来更出色的视觉体验。
尽量为所有分辨率创建资源
减少不必要的硬件缩放,这会降低UI的绘制速度,可借助Android asset studio
布局调优工具
hierarchy viewer
在上面说merge标签时用到的工具
hierarchy viewer可以方便的查看Activity的布局,各个View的属性、measure、layout、draw的时间,如果耗时较多会用红色标记,否则显示绿色。
hierarchy viewer.bat位于sdk/tools/目录下。
为什么使用HierarchyViewer
不合理的布局会使我们的应用程序UI性能变慢,HierarchyViewer能够可视化的角度直观地获得UI布局设计结构和各种属性的信息,帮助我们优化布局设计。HierarchyViewer是我们优化程序的工具之一,它是Android自带的非常有用的工具,可以帮助我们更好地检视和设计用户界面(UI),绝对是UI检视的利器。
使用HierarchyViewer
Hierarchy Viewer是随Android SDK发布的工具,位于Android SDK/tools/hierarchyviewer.bat (Windows操作系统,mac上我也不知道(没用过)),使用起来也是超级简单,通过此工具可以详细的理解当前界面的控件布局以及某个控件的属性(name、id、height等)。
studio打开Hierarchyviewer
1)连接设备真机或者模拟器
添加Hierarchyviewer面板
2)启动你要观察的应用。
启动你要观察的应用
3)打开Hierarchyviewer,点击hierarchyviewer文件即可
lint
Android Studio中集成了Lint检查,提供了一套静态代码分析工具,它可以帮助我们检查项目中存在的问题,让我们更有规范性的开发App。它可以检查出:xml文件中是否存在hardcode硬编码、unused resources没有使用到的资源、probable bug可能的bug等等
1.找到Analyze目录下的Inspect Code检查代码选项点击
2.选择Inspect Code的范围
3.待分析完毕后,我们可以在Inspection栏目中看到检查的结果
从检查结果可知,它会给出所有在这个项目中不规范的编码、多余的资源、可能的bug、或者其它的一些问题,然后会给出修改的建议供我们参考,虽然这些问题并不会影响App的正常运行,不过这对于项目的规范性和维护性来说是非常重要的。所以这个工具可以让项目具有规范性的开发。
Java代码优化
降低执行时间
缓存
缓存主要包括对象缓存、IO缓存、网络缓存、DB缓存,对象缓存能减少内存的分配,IO缓存减少磁盘的读写次数,网络缓存减少网络传输,DB缓存较少Database的访问次数。
在内存、文件、数据库、网络的读写速度中,内存都是最优的,且速度数量级差别,所以尽量将需要频繁访问或访问一次消耗较大的数据存储在缓存中。
Android中常用缓存
- 线程池
-
new Thread的弊端
a.每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。 -
Java 线程池
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
参考下面四种线程池
-
new Thread的弊端
- Android内存缓存(LruCache),Android文件缓存(DiskLruCache)
- 消息缓存
通过handler.obtainMessage复用之前的message
handler.sendMessage(handler.obtainMessage(0, object)); - ListView缓存,也就是convertView复用(建议RecyclerView)
- 网络缓存
- 文件IO缓存
使用具有缓存策略的输入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.对文件、网络IO皆适用。 - layout缓存
参考上面的抽象布局标签
数据存储优化
数据类型选择
字符串拼接用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来说性能更优。根据自己可能的容量设置初始值。
算法优化
这个主题比较大,需要具体问题具体分析,尽量不用时间复杂度以上的算法,必要时候可用空间换时间。例如:查询考虑hash和二分,尽量不用递归。
JNI
Android应用程序大都通过Java开发,需要Dalvik的JIT编译器将Java字节码转换成本地代码运行,而本地代码可以直接由设备管理器直接执行,节省了中间步骤,所以执行速度更快。不过需要注意从Java空间切换到本地空间需要开销,同时JIT编译器也能生成优化的本地代码,所以糟糕的本地代码不一定性能更优。
逻辑优化
这个不同于算法,主要是理清程序逻辑,减少不必要的操作。
异步,利用多线程提高TPS
充分利用多核Cpu优势,利用线程解决密集型计算、IO、网络等操作。
可参考:Java线程池
在Android应用程序中由于系统ANR的限制,将可能造成主线程超时操作放入另外的工作线程中。在工作线程中可以通过handler和主线程交互。
提前或延迟操作,错开时间段提高TPS
延迟操作
不在Activity、Service、BroadcastReceiver的生命周期等对响应时间敏感函数中执行耗时操作,可适当delay。
Java中延迟操作可使用ScheduledExecutorService,不推荐使用Timer.schedule;
Android中除了支持ScheduledExecutorService之外,还有一些delay操作,如
handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定时等。
提前操作
对于第一次调用较耗时操作,可统一放到初始化中,将耗时提前。如得到壁纸wallpaperManager.getDrawable();
网络优化
客户端和服务器端需要尽量遵守的准则:
- 图片进行缓存
- 所有http请求添加httptimeout
- 开启gzip压缩
- api接口数据以json格式返回,而不是xml或html
- 根据http头信息中的Cache-Control及expires域确定是否缓存请求结果。
- 确定网络请求的connection是否keep-alive
- 减少网络请求次数,服务器端适当做请求合并。
- 减少重定向次数
- api接口服务器端响应时间尽量不超过100ms
Java 线程池
优点
- 重用存在的线程,减少对象创建、消亡的开销,性能佳。
- 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
四种线程池
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
定长线程池的大小最好根据系统资源进行设置。如: Runtime.getRuntime().availableProcessors()
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
延迟3秒执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
延迟1秒后每3秒执行一次
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。
性能优化这些事就先说到这,日后继续补充