Android 性能优化

性能优化的几个方面

快稳省小

内存优化、UI 优化(布局优化和绘制优化)、速度优化(线程优化和网络优化)、电量优化、启动优化

内存优化

说到内存优化,就会想到内存泄漏这一常见问题。当一些不能被回收的内存逐渐累积,也就是内存泄漏,一般会导致应用卡顿,甚至会出现 OOM 的情况。OOM 的原因是因为超过内存的阈值。原因有两个方面:

  1. 代码存在泄漏,内存无法及时释放导致 OOM
  2. 应用逻辑消耗大量内存,无法及时释放或超过导致 OOM

所谓消耗大量内存,绝大多数是因为图片加载。避免的方法有:控制每次加载的数量、保证每次滑动时不进行加载滑动完毕再进行加载,等等。

如何查看内存泄漏问题(工具)

  • Android -> System Information -> MemoryUsage 查看 Object 里面是否有没有被释放的 Views 和 Activitys
  • 命令行:adb shell dumpsys meminfo 包名 -d
  • AS Memory Monitor
  • LeakCanary
  • MAT 工具

代码习惯

尽量不要使用 Activity 的上下文,而是使用 Application 的上下文。(单例模式是最容易造成内存溢出的原因所在,因为单例模式的生命周期应该和 Application 的生命周期一样长,而不是 Activity的相同)。

Animation.cancel()

其他应注意也就都是,注意长生命周期对象持有短生命周期对象所造成的内存泄漏问题

具体场景及解决方式

集合类泄漏、单例/静态变量造成的内存泄漏、匿名内部类/非静态内部类、资源未关闭造成的内存泄漏

1.集合类泄漏

1
2
3
4
5
6
static List<Objec> mList = new ArrayList<>();
for(int i=0; i<100; i++){
Object obj = new Object();
mList.add(obj);
obj = null;
}
1
2
mList.clear();
mList = null;

2.单例/静态变量造成的内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SingleInstance{
private static SingleInstance mInstance;
private Context mContext;

private SingleInstance(Context context){
this.mContext = context;
}

public static SingleInstance newInstance(Context context){
if(mInstance == null)}{
mInstance = new SingleInstance(context);
}
return mInstance;
}
}

当在 Activity 里面使用这个的时候,我们把 Activity 的 context 传进去了,那么这个单例就持有了这个 Activity 的引用,当 Activity 被销毁了,因为这个单例还持有这个 Activity 的引用,所以 Activity 无法被 GC 回收,出现内存泄漏的问题。生命周期长的对象持有了生命周期短的对象的引用就会造成这样的问题。解决方案就是用 Application 的 Context。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SingleInstance{
private static SingleInstance mInstance;
private Context mContext;

private SingleInstance(Context context){
this.mContext = context.getApplicationContext();
}

public static SingleInstance newInstance(Context context){
if(mInstance == null){
mInstance = new SingleInstance(context);
}
return mInstance;
}
}

3.匿名内部类/非静态内部类

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
new MyAsyncTask().execute();
}

class MyAsyncTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params){
try{
Thread.sleep(100000);
}catch(InterruptedException e){
e.printStackTrace();
}
return "";
}
}
}
1
2
3
4
...
static class MyAsyncTask extends AsyncTask<Void, Integer, String>{
...
}

匿名内部类持有外部引用,弱引用解决内存泄漏问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TestActivity extends Activity{
private TextView mText;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
mText.setTest("do someThing");
}
};

@Override
protected void onCreate(Bundle savedInstanceState){

// 匿名线程持有 Activity 的引用,进行耗时操作
new Thread(new Runnable(){
@Override
public void run(){
try{
Thread.sleep(100000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}).start();

mHandler.sendEmptyMessageDelayed(0, 100000);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestActivity extends Activity{
private TextView mText;
private MyHandler myHandler = new MyHandler(TestActivity.this);
private MyThread myThread = new MyThread();

private static class MyHandler extends Handler{
WeakReference<TestActivity> weakReference;

MyHandler(TestActivity testActivity){
this.weakReference = new WeakReference<TestActivity>(testActivity);
}

@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
if(weakReference.get() != null){
weakReference.get().mText.setText("do somethings");
}
}
}

private static class MyThread extends Thread{
...
}
}

4.资源未关闭造成的内存泄漏

  • 网络、文件等流忘记关闭
  • 手动注册广播时,退出时忘记unregisterReceiver()
  • Service 执行后忘记stopSelf()
  • EventBus 等观察者模式的框架忘记手动解除注册

UI 优化

布局优化

GPU 绘制

开发者选项中的 GPU 过度绘制工具进行分析

img

Overdraw 有时候是因为你的 UI 布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个 Activity 有一个背景,然后里面的 Layout 又有自己的背景,同时子 View 又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能减少大量的红色 Overdraw 区域,增加蓝色区域的占比。

优化 1

  1. 如果父控件有颜色,也是自己需要的颜色,那么就不必在子控件加背景颜色
  2. 如果每个子控件的颜色不太一样,而且可以完全覆盖父控件,那么久不需要再父控件上加背景
  3. 尽量减少不必要的嵌套
  4. 能用 LinearLayout 和 FrameLayout,就不要用 RelativeLayout
  5. 使用 include、merge 增加复用,减少层级
  6. 使用 ViewStub 按需加载
  7. 复杂布局可选择ContraintLayout,可有效减少层级

GPU 呈现模式分析

开发者选项 -> GPU 呈现模式分析

img

蓝色代表测量绘制 Display List 的时间,红色代表 OpenGL 渲染 Display List 所需要的时间,黄色代表 CPU 等待 GPU 处理的时间。

命令行:adb shell dumpsys gfxinfo [应用包名],Draw + Process + Execute = 完整的显示一帧时间 < 16ms

我们把布局优化了,但是和布局息息相关的还有绘制,这是直接影响显示的两个根本因素。其实布局优化对于性能的提升影响不算很大,我们平时感觉的卡顿问题最主要的原因之一是因为渲染性能。Android 系统每隔 16ms 就会发出 VSYNC 信号,触发对 UI 进行渲染,但是渲染未必成功,失败了可能就会延误时间或是直接就给跳过去了。这给人的视觉上的表现就是,卡了一会儿,或是跳帧。

View 的 绘制频率保证 60fps 是最佳的,这就要求每帧的绘制时间不超过 16ms(16ms = 1000 / 60),虽然程序很难保证 16ms 这个时间,但是尽量降低onDraw()方法中的复杂度总是切实有效的。

优化2

  1. onDraw()中不要创建新的局部对象
  2. onDraw()中不要做耗时的任务

启动优化

app 启动分为冷启动(Cold Start)、热启动(Hot Start)、温启动(Warm Start)三种。

冷启动(Cold Start)

冷启动是首次创建应用程序,在冷启动开始时,系统有三个任务:

  1. 加载并启动应用程序
  2. 启动后立即显示应用程序的空白启动窗口
  3. 创建应用程序进程

当系统为我们创建应用进程之后,就开始创建应用程序对象:

  1. 启动主线程
  2. 创建主 Activity
  3. 加载布局
  4. 屏幕布局?
  5. 执行初始绘制

应用程序进程完成第一次绘制后,系统进程会交换当前显示的背景窗口,将其替换为主 Activity。此时,用户可以开始使用该应用程序。至此启动完成。

Application 创建

当 Application 启动时,空白的启动窗口将保留在屏幕上,直到系统首次完成绘制应用程序。此时,系统进程会交换应用进程的启动窗口,允许用户开始与应用程序进行交互。这就是为什么我们的程序启动时会先出现一段时间的黑屏(白屏)。如果我们有自己的 Application,系统会onCreate()在我们的 Application 对象上回调该方法,之后应用程序会生成主线程(也称为 UI 线程),并通过创建主 Activity 来执行任务,开始 Activity 的生命周期。

Activity 创建

  1. 初始化值
  2. 调用构造函数
  3. 调用回调方法,例如onCreate()

通常onCreate()对加载时间影响最大,因为它以最高的开销执行工作:加载和膨胀视图,以及初始化 Activity 运行所需的对象。

热启动(Hot Start)

比冷启动要简单得多,开销也更低。在一个热启动中,系统会把你的 Activity 带到前台。如果应用程序的 Activity 仍然驻留在内存中,那么应用程序可以避免重复对象初始化、布局加载和渲染。

与冷启动相同的屏幕行为:系统进程显示空白屏幕直至应用程序完成呈现 Activity。

温启动(Warm Start)

包含了冷启动发生的一些操作,与此同时,它的开销比热启动少,有许多潜在的状态,可以被认为是温暖的开始。

场景

  • 用户退出应用,但随后重新启动它。该过程可能已继续运行,但应用程序必须通过调用从头开始重新创建 Activity 的onCreate()。(仍得重跑生命周期)
  • 系统将应用程序从内存中逐出,然后用户重新启动它。重新启动进程和 Activity,但是在调用onCreate()时可以从 Bundle (savedInstanceState)获取数据

由上知,在创建应用程序和创建 Activity期间都可能出现性能问题。优化点:Application、Activity 创建以及回调等过程

优化3

  1. 利用提前展示出来的 Window,快速展示出来一个界面,给用户快速反馈的体验
  2. 避免在启动时做密集沉重的初始化(Heavy app initialization)
  3. 避免 I/O 操作、反序列化、网络操作、布局嵌套等。

网络优化

线程优化

采用线程池

AsyncTask、HandlerThread、ThreadPool、IntentService

其他优化

1.包体优化

  • lint工具查找去除无用资源
  • 资源压缩、代码混淆
  • 某些情况下可使用绘制对象替代静态图片资源
  • 重用资源、使用 WebP 文件格式
  • 插件化,按需下载

2.耗电优化

JobScheduler来调整任务优先级等策略来达到降低损耗的目的,避免在不合适的时间(低电量、弱网络)执行过多的任务损耗电量。

具体功能:

  1. 可以推迟的非面向用户的任务(如定期数据库数据更新)
  2. 当充电时才希望执行的任务(如数据备份)
  3. 需要访问网络或 Wifi 链接的任务(如向服务器拉取配置数据)
  4. 零散任务合并到一个批次去定期执行
  5. 当设备空闲时启动某些任务
  6. 只有当条件得到满足,系统才会启动计划中的任务(充电、WiFi)

3.ListView 和 Bitmap 的优化

ListView 使用 ViewHolder,分段,分页加载

压缩 Bitmap


参考引用

Android性能优化全方面解析

Android 性能优化最佳实践

Android性能优化典范 - 第1季