一、布局优化
1.Android渲染机制
在Android中系统通过VSYNC信号触发对UI的渲染、重绘,其间隔时间是16ms,即1000ms内显示60帧画面的单位时间,如果每次渲染画面的时间保持在16ms之内,那么我们看到的UI就是非常流畅的,如果在16ms内不能完成绘制,那么就会造成丢帧现象,即当前该重绘的帧被未完成的逻辑阻塞,例如一次绘制任务耗时20ms,那么在16ms系统发出VSYNC信号时就会无法绘制,该帧就被丢弃,等待下次信号才开始绘制,导致16*2ms内都显示同一帧画面,这就是画面卡顿的原因。
2.Android中的UI渲染时间工具
Android系统中提供了检测UI渲染时间的工具,打开“开发者选项”选择“Profile GPU Render”(GPU呈现模式分析)并选择“On Screen as bars”(在屏幕上显示为条形图)。这是在屏幕上将显示一些条形图:
图上每一条柱状线都包含三部分,蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List的所需时间,黄色代表CPU等待GPU处理的时间。中间的绿色横线代表VSYNC时间16ms,需要尽量将所有条形图都控制在这条线以下。
3.避免Overdraw
Overdraw(过渡绘制)会浪费很多的CPU、GPU资源,例如系统默认绘制Activity的背景,而如果再给布局绘制了重叠的背景,那么默认Activity的背景就属于无效的过渡绘制,Android系统在开发者选项中提供了这样的一个检测工具——”Enable GPU Overdraw“。激活后可以通过界面上的颜色来判断Overdraw的次数:
通过这个工具可以查看当前区域中的绘制次数,从而尽量优化绘图层次,尽量增大蓝色的区域,减少红色区域。
4.优化布局层级
- 使用<include>标签重用layout
- 使用<ViewStub>实现View的延迟加载
首先创建一个布局,这个布局在初始化加载时不需要显示,只在某些情况下才显示出来,下面是一个这样的布局内容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="ViewStub"
android:padding="10dp"
android:textSize="36sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
然后在布局中通过ViewStub来引用:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.chaoyang805.chapter10performance.MainActivity"
tools:showIn="@layout/activity_main">
<Button
android:onClick="showViewStub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="Show ViewStub"/>
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout="@layout/view_stub"/>
</RelativeLayout>
然后在代码中通过findViewById来找到ViewStub组件,之后就可以用ViewStub.setVisibility(View.VISIBLE);或ViewStub.inflate();将ViewStub显示出来了:
private ViewStub mViewStub;
private TextView tv = null;
View v;
public void showViewStub(View view) {
//一旦ViewStub调用Inflate或setVisiblity后,这个<ViewStub>就不存在了
if (tv == null) {
mViewStub = (ViewStub) findViewById(R.id.view_stub);
v = mViewStub.inflate();
}
tv = (TextView) v.findViewById(R.id.tv);
tv.setText("I'm Visible");
// mViewStub.setVisibility(View.VISIBLE);
}
两个方法的区别在于inflate可以返回引用的布局,从而通过view.findViewById()来找到相应的控件。需要注意的是一旦ViewStub调用了inflate或setVisibility后,这个ViewStub就不存在了,取而代之的是其对应的Layout。
与View.GONE不同的是ViewStub是在显示的时候才会去渲染整个布局,而View.GONE在初始化布局的时候就已经添加在布局树上了,相比之下ViewStub拥有更高的效率。
5.Hierarchy Viewer
Hierarchy Viewer位于SDK/tools下,通过在命令行输入HierarchyViewer.bat来打开,然后选择要测试的进程,点击Load View Hierarchy按钮,显示界面如图:
当点击一个View时会显示这个View的绘制情况,不过第一次点击会显示na,需要再点击菜单中的Profile Node按钮重新计算,就能获取View的绘制信息:
图中的三个小圆点用来表示绘制的效率,绿、黄、红的效率依次降低。
二、内存优化
1.由于Android采用沙箱机制,每个应用所分配的内存大小是有限的,内存太低就会触发LMK——Low Memory Killer机制,通常我们所说的内存包括以下几个部分:
- 寄存器(Register)
- 栈(Stack)
- 堆(Heap)
- 静态存储区(Static Field)
- 常量池(Constant Pool)
在这些概念中最容易搞混的就是堆和栈的区分,当定义一个变量,Java虚拟机就会在栈中为该变量分配内存空间,当该变量的作用于结束后这部分内存空间会马上被用作新的空间进行分配,如果使用new的方式创建一个变量,那么就会在堆中为这个对象分配内存空间,即使该对象的作用域结束,这部分内存也不会立即被回收,而是等待系统GC进行回收,堆的大小随着手机的不断发展而不断变大。在程序中可以使用如下代码获得堆的大小,所谓的内存分析,正是分析Heap中的内存状态。
//获取堆内存大小
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int heapLargeSize = am.getLargeMemoryClass();
2.获取系统内存信息
获取系统内存信息有两种方式使用dumpsys命令或通过手机中的开发者选项,下面介绍使用dumpsys命令:
- adb shell dumpsys procstats——可以查看系统内存监视服务
- adb shell dumpsys meminfo——可以查看正在运行的进程内存信息
Bitmap优化技巧:
- 使用适当分辨率的和大小的图片
- 即使回收内存,自Android3.0起,由于Bitmap被放置到了堆内存中,由GC管理,就不需要进行释放了
- 使用图片缓存,通过内存缓存(LruCache)和硬盘缓存(DiskLruCache)可以更好的使用Bitmap
- 对常量使用static修饰符
- 使用静态方法,静态方法比普通方法提高15%的访问速度
- 减少不必要的成员变量,如果一个成员可以定义为局部变量就不要声明成成员变量
- 减少不必要的对象,使用基础类型会比使用对象更加节省资源,同时更应该避免频繁的创建短作用域的变量
- 尽量不要使用枚举,少用迭代器
- 对Cursor、Receiver、Sensor、File等对象要非常注意对它们的创建、回收与注册、解注册。
- 避免使用IOC框架,IOC通常使用注解、反射来进行实现,虽然现在Java对反射的效率已经进行了很好的优化,但大量使用反射依然会带来性能的下降
- 使用RenderScript、OpenGL来进行非常复杂的绘图操作
- 使用SurfaceView来替代View进行大量、频繁的绘图操作
- 尽量使用视图缓存、而不是每次都使用inflate()解析视图。
三、使用TraceView工具优化App性能
1.生成TraceView日志地方法
- 使用Debug类的方法开启TraceView监听
- 通过Android Device Monitor生成TraceView日志
- 使用AndroidStudio生成TraceView日志
2.分析TraceView日志
TraceView分为两部分:时间轴区域和Profile区域,时间轴区域显示了不同线程在不同的时间段内的执行情况,在时间轴中每一行都代表了一个独立的线程;Profile区域显示了方法的性能分析,主要有以下信息:
- Incl CPU Time——某方法占用CPU的时间
- Excl CPU Time——某方法本身(不包括子方法)占用CPU的时间。
- Incl Real Time——某方法真正执行的时间
- Excl Real Time——某方法本身真正执行的时间
- Calls+RecurCalls——调用次数+递归调用次数
- class——这部分显示了heap中的所有对象的类
- instance——显示了heap中对应类的所有实例,点击可以显示对象的属性等信息
- Reference Tree——显示了对应实例的引用结构
- activity——显示所有的activity栈信息
- meminfo——内存信息
- battery——电池信息
- package——包信息
- wifi——WiFi信息
- alarm——显示alarm信息
- procstats——显示内存状态