一、绘制原理
Android应用程序把经过测量、布局、绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到显示屏幕上,通过Android的刷新机制来刷新数据。也就是说应用层负责绘制,系统层负责渲染,通过进程间通信把应用层需要绘制的数据传递到系统层服务,系统层服务通过刷新机制把数据更新到屏幕。
1.应用层
在Android系统中,整体的绘图源码是在ViewRootImpl类的performTraversals()方法,通过这个方法可以看出Measure和Layout都是递归来获取View的大小和位置,并且以深度作为优先级。可以看出,层级越深,元素越多,耗时也就越长。
Measure
用深度优先原则递归得到所有视图(View)的宽、高。当获取当前View的正确宽度childWidthMeasureSpec和高度childHeightMeasureSpec之后,可以调用它的成员函数Measure来设置它的大小。如果当前正在测量的子视图child是一个视图容器,那么它又会重复执行操作,直到它的所有子孙视图的大小都测量完成为止。Layout
用深度优先原则递归得到所有视图(View)的位置。当一个子View在应用程序窗口左上角的位置确定之后,再结合它在前面测量过程中确定的宽度和高度,就可以完全确定它在应用程序窗口中的布局。-
Draw
目前Android支持两种绘制方式:软件绘制(CPU)和硬件加速(GPU),其中硬件加速在Android 3.0开始已经全面支持,硬件加速在UI的显示和绘制的效率远远高于CPU绘制。硬件加速的缺点:耗电,GPU的功耗比CPU高;兼容问题,某些接口和函数不支持硬件加速;内存大,使用OpenGl的接口至少需要8MB内存。
是否使用硬件加速需要考虑一些接口是否支持硬件加速,同时结合产品的形态和平台,比如TV版本就不需要考虑功耗问题,而且TV屏幕大,使用硬件加速容易实现更好的显示效果。
2.系统层
真正把需要显示的数据渲染到屏幕上,是通过系统级进程中的SurfaceFlinger服务来实现的,SurfaceFlinger把缓存区数据渲染到屏幕上主要是驱动层的事情。
3.绘制时长
FPS:每秒传递的帧数。在理想情况下,60FPS就感觉不到卡,这意味着每个绘制时长应该在16ms以内。Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需的60FPS。即为了实现60FPS,就意味着程序的大多数绘制操作都必须在16ms内完成,如果某个操作花费的时间是24ms,系统在得到VSYNC信号时就无法进行正常渲染,这样就发生了丢帧现象,那么用户在32ms内看到的会是同一帧画面。主要场景是在执行动画或者滑动ListView时更容易感知到卡顿不流畅,是因为这里的操作相对复杂,容易发生丢帧的现象,从而感觉卡顿。
二、启动速度影响因素
1.界面绘制
主要原因是:界面复杂、绘制层级深、刷新不合理。
2.数据处理
一般分为三种情况:
- 数据处理在主线程(这种情况应当避免)。
- 数据处理占用CPU高,导致主线程拿不到时间片。
- 内存增加导致GC频繁,从而引起卡顿。
三、启动速度优化
1.绘制优化
-
使用ConstraintLayout使界面布局扁平化
-
使用merge减少界面布局层级
merge只能用在xml布局文件的根元素。
使用merge来加载一个布局时,必须指定一个ViewGroup作为其父元素,并且要设置加载的attachToRoot参数为true。
不能在ViewStub中使用merge,原因是ViewStub的inflate方法中根本没有attachToRoot的设置。
-
使用ViewStub按需加载界面布局
ViewStub是一个看不见、不占布局位置、占用资源非常小的控件。
加载ViewStub有两种方式:inflate()和setVisibility(View.VISIBLE)。
ViewStub被加载后就无法再用来控制布局,如果ViewStub有设置android:inflatedId,则其会取代填充布局的根布局id,如果ViewStub没有设置android:inflatedId,则会直接使用填充布局的根布局id。
使用include复用界面布局
2.数据处理优化
核心思想:在启动过程中少做事情,越少越好。
分步加载:以大化小,优先级高的放前。
异步加载:耗时多的异步化。
-
延迟加载:非必需的数据延迟加载。
延迟加载方式1:
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "zwm, onCreate"); setContentView(R.layout.activity_main); getWindow().getDecorView().post(new Runnable() { @Override public void run() { Log.d(TAG, "zwm, DecorView post run"); } }); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "zwm, onResume"); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Log.d(TAG, "zwm, onWindowFocusChanged, hasFocus: " + hasFocus); } } //日志打印 2020-05-18 13:34:52.724 669-669/com.tomorrow.target28 D/MainActivity: zwm, onCreate 2020-05-18 13:34:52.746 669-669/com.tomorrow.target28 D/MainActivity: zwm, onResume 2020-05-18 13:34:52.799 669-669/com.tomorrow.target28 D/MainActivity: zwm, DecorView post run 2020-05-18 13:34:52.800 669-669/com.tomorrow.target28 D/MainActivity: zwm, onWindowFocusChanged, hasFocus: true
延迟加载方式2:
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "zwm, onCreate"); setContentView(R.layout.activity_main); Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { //在这里处理延迟加载内容 Log.d(TAG, "zwm, IdleHandler queueIdle"); //最后返回false,后续不用再监听了 return false; } }); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "zwm, onResume"); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Log.d(TAG, "zwm, onWindowFocusChanged, hasFocus: " + hasFocus); } } //日志打印 2020-05-18 13:40:59.029 2970-2970/com.tomorrow.target28 D/MainActivity: zwm, onCreate 2020-05-18 13:40:59.056 2970-2970/com.tomorrow.target28 D/MainActivity: zwm, onResume 2020-05-18 13:40:59.109 2970-2970/com.tomorrow.target28 D/MainActivity: zwm, onWindowFocusChanged, hasFocus: true 2020-05-18 13:40:59.134 2970-2970/com.tomorrow.target28 D/MainActivity: zwm, IdleHandler queueIdle
四、耗时操作检测
耗时操作例子:
//AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.tomorrow.architetest">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:appComponentFactory="abc"
tools:replace="android:appComponentFactory">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity"/>
</application>
</manifest>
//MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate");
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "zwm, onClick");
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
}
//SecondActivity
public class SecondActivity extends AppCompatActivity {
private static final String TAG = "SecondActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate");
// Debug.startMethodTracing("zwm-test");
setContentView(R.layout.activity_second);
heavyTask();
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "zwm, onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "zwm, onResume");
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.d(TAG, "zwm, onWindowFocusChanged hasFocus: " + hasFocus);
// Debug.stopMethodTracing();
}
private void heavyTask() {
Log.d(TAG, "zwm, heavyTask start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "zwm, heavyTask end");
}
}