Contact us:
Youdao technical team assistant : ydtech01 / mailbox : ydtech@rd.netease.com
The focus of this article is on how to quantitatively investigate the time-consuming operations in the cold start process, and provide corresponding optimization ideas and a summary of practical methods. At the same time, the cold start optimization involved in this article mainly covers two aspects: Application performance optimization and Launcher Activity performance optimization.
1. Background
China University MOOC is an online education platform jointly launched by NetEase and Higher Education Society. At present, after long-term product polishing and research, China University MOOC has become the world's leading Chinese MOOC platform in terms of the number, quality and influence of courses. At the same time, after this optimization, the cold start speed increased by 27% overall.
In our daily development, as the overall number of iterations of the app increases, due to long-term iteration requirements, the android app itself also integrates more third-party components and SDKs. At the same time, in daily iterations, the realization of business iteration requirements is the main Purpose, leading to more or less room for performance optimization in the app itself. So it’s necessary to optimize performance to improve user experience
This optimization mainly focuses on two aspects:
- Application performance optimization
- App launch page performance optimization
This document focuses not on performance problems caused by code specifications and business code logic, but on the assumption that the code has no obvious and serious performance vulnerabilities, and does not change the original business logic, quantifies performance monitoring data and problems, and optimizes and modifies them. .
2. Cold start speed optimization
2.1 Related knowledge points
2.1.1 Cold start time-consuming statistics
adb shell am start -S -W [packageName]/[activiytName]
In the above adb command, several key parameters are explained:
- -S : Indicates that the current app process is completely closed before starting the app
- -W : start and output related time-consuming data
- packageName : applicationID of the app
- : The activity that needs to be pulled up for app startup. If used to count the cold start time, this parameter is the first activity started by the application (the intent-filter is the activity of LAUNCHER)
After executing the appeal adb, the APP will be successfully invoked, and three key parameters will be output on the console:
- LaunchState : start mode, the appeal start mode is cold start
- : The time it takes for the system to start the application = TotalTime + system resource start time (unit: ms)
- TotalTime : The time it takes for the application to start = the activity start time + the application application and other resources start time (unit: ms)
For the application-level cold start performance optimization, we are concerned about the time TotalTime, which can be roughly summarized as: Application construction method → the sum of the Activity's onWindowFocusChange method time. And this process can also be roughly understood as the user clicks on the desktop icon to get the focus of the first activity of the app, and the total time of business code execution (for business code optimization, we don’t care about the interaction of the Zygote process, the Launcher process, and the AMS process for the time being. ).
2.1.2 Cold start time-consuming stack observation method:
In the system version of Android API>=26, it is recommended to use CPU Profile or Debug.startMethodTracing to monitor and export the trace file for analysis. Either way, there are two modes for collecting stack information: sampling mode and tracking mode. The tracking mode will always capture data, which requires high device performance.
(1)CPU Profile
(2)Debug.startMethodTracing
Since the cold start involves the time of the business application level: the activity start time + application application and other resources start time, so we start the collection in the Application construction method, stop the collection in the onWindowFocusChange of the first Activity, and output the trace file.
/**
* 在Application构造方法中开始采集
*/
public UcmoocApplication() {
//保存Trace文件的目录
File file = new File(Environment.getExternalStorageDirectory(), "ucmooc.trace");
//采集方式有以下两种,根据需求选择其一
//第一种:通过采样的方式,追踪堆栈信息
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//通过采样方式追踪堆栈信息,需要指定文件保存目录、文件最大大小(单位M)、采样间隔(单位us)
Debug.startMethodTracingSampling(file.getAbsolutePath(), 8, 1000);
}
//第二种:通过追踪的方式,全量采集堆栈信息
Debug.startMethodTracing(file.getAbsolutePath());
coreApplication = new CoreApplication();
}
/**
* 在启动后的第一个Activity的onWindowFocusChanged中停止监听
*
* @param hasFocus
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Debug.stopMethodTracing();
}
At the same time, because this operation involves file read and write permissions, you need to manually grant the APP this permission
2.1.3 .trace log file reading:
After exporting and obtaining the .trace file, drag the .trace to the androidStudio editing area; or browse the CPU Profile view directly to analyze the stack of the program:
The above picture is the effect of opening the trace file. It shows a few points that need to be noted in the upper icon for the optimization of the startup speed based on CPU usage and thread running status:
(1) CPU running time axis: drag horizontally to select the time range for viewing
(4) The thread that the current device CPU rotates: click to select the thread that needs to be viewed, we focus on the main thread
(2) The currently selected thread follows the time axis, the call status of each method stack and its time-consuming status. Its different colors represent
- yellow : android system method (FrameWork layer code, if you need the final lower-level method, you need the final C/C++ method call stack)
- blue : Java JDK method
- green : belongs to the method of current app process execution, including some class loaders and our business code (start speed optimization is mainly for this part)
(3) The calling sequence and time-consuming situation of each method stack can be selected different sorting methods and views.
Therefore, when troubleshooting time-consuming methods in general, it is recommended to first visually detect the more time-consuming methods through (2) view, and after locking, view the specific method call sequence in (3) view.
2.2 Optimization steps
In the cold start process, the time-consuming business code is mainly concentrated in Application and Launcher Activity, so the optimization process is also optimized for these two pieces.
2.2.1 Optimization results
Using the method of 2.1.1, before and after optimization, 10 cold-start time-consuming statistics were made, and the results are as follows:
Starting speed increased 27% overall.
2.2.2 Application optimization
Through the trace file, it can be found intuitively that in the application, the longest time-consuming method is the onCreate method in its life cycle. Among the onCreate methods, the time-consuming methods are: initMudleFactory, initURS, Unicorn.init , InitUmeng.
In the Top Down view, it can be seen more intuitively that this sampling is also the most time-consuming four methods.
Through source code investigation, this is a method, which is the initialization of third-party SDKs. At the same time, these SDKs contain more IO operations, and internal thread management is implemented to ensure thread safety, so these SDKs can be The initialization is completed in the child thread. Here is an example of Youmeng SDK:
/**
* 友盟SDK中有涉及到线程不安全的地方,都自己维护了线程,保证线程安全
**/
try {
var6 = getClass("com.umeng.umzid.ZIDManager");
if (var6 == null) {
Log.e("UMConfigure", "--->>> SDK 初始化失败,请检查是否集成umeng-asms-1.2.x.aar库。<<<--- ");
(new Thread() {
public void run() {
try {
Looper.prepare();
Toast.makeText(var5, "SDK 初始化失败,请检查是否集成umeng-asms-1.2.X.aar库。", 1).show();
Looper.loop();
} catch (Throwable var2) {
}
}
}).start();
return;
}
} catch (Throwable var27) {
}
/**
* 在友盟SDK内部中有很多IO操作的地方,和加锁操作,所以可以将SDK初始化操作,放在子线程中
**/
if (!TextUtils.isEmpty(var1)) {
sAppkey = var1;
sChannel = var2;
UMGlobalContext.getInstance(var3);
k.a(var3);
if (!needSendZcfgEnv(var3)) {
FieldManager var4 = FieldManager.a();
var4.a(var3);
}
synchronized(PreInitLock) {
preInitComplete = true;
}
}
Finally, we can put the several SDK initialization tasks mentioned above in sub-threads:
private void initSDKAsyn(){
new Thread(() -> {
if (Util.inMainProcess()){
// 登录
initURS();
if (BuildConfig.ENTERPRISE) {
Unicorn.init(BaseApplication.this, "", QiyuOptionConfig.options(), new QiyuImageLoader());
initModuleRegister();
} else {
Unicorn.init(BaseApplication.this, "", QiyuOptionConfig.options(), new QiyuImageLoader());
}
// 初始化下载服务
try {
initDownload();
} catch (Exception e) {
NTLog.f(TAG, e.toString());
}
}
initModuleFactory();
initUmeng();
}).start();
}
For some SDKs that must be initialized in the main thread, you can consider using IdleHandler to complete the initialization when the main thread is idle (about IdleHandler will be discussed below).
2.2.3 Launcher Activity optimization
The auncher Activity is WelcomeActivity. After the Application optimization is over, the WelcomeActivity is optimized again. The idea is the same as on the road, first through the trace file tracking:
It can be seen that in the onCreate method of WelcomeActivity, the three places that consume more time are: initActionBar, EventBus.register, and setContentView. The following are the corresponding optimization operations for these three pieces of content:
(1)initActionBar
In the above figure, you can see that the most time-consuming operation in initActionBar is getSupportActionBar. Through researching the code, it is found that in WelcomeActivity, there is no need to operate actionBar, so directly override the parent method and remove the super call.
(2)EventBus.register
When EventBus is registered, the performance is poor, because a large number of reflection operations are involved in the modification process, so the performance loss is large. By checking the official documents, the problem has been well handled in EventBus3.0, mainly through the apt technology to increase the index and improve efficiency. (The current project has not upgraded the version, it will be optimized later)
(3)setContentView
setContentView is a necessary method for Activity to render the layout. The time-consuming point is that reflection is used when parsing the xml layout file, so if the xml layout file is very rechecked, you can use androidx.asynclayoutinflater:asynclayoutinflater to load the xml file asynchronously. The usage is as follows:
new AsyncLayoutInflater(this).inflate(R.layout.activity_welcome, null, (view, resid, parent) -> { setContentView(view); });
III. Summary of optimization methods
The above cold start optimization is based on the steps of the current project itself. Here are some common cold start optimization ideas :
(1) Reasonable use of asynchronous initialization, delayed initialization and lazy loading mechanism : mainly for the initialization of various SDKs in Application
should be avoided in the main thread, such as IO operations, database read and write operations
(3) Simplify the layout structure of launcher Activity . If the layout is very complex, there are two ways to optimize:
- It is recommended to use ConstraintLayout to reduce layout nesting and avoid over-rendering.
- Use androidx.asynclayoutinflater:asynclayoutinflater to load xml files asynchronously.
(4) Reasonably use IdleHandler for delayed initialization , the usage is as follows:
/**
* 需要在当前线程中处理耗时任务,并且并不需要马上执行的话,可以使用IdleHandler
* 这样该任务可以消息队列空闲时,被处理
*/
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//此处添加处理任务
//返回值为false:则执行完毕之后移除这条消息,
//返回值为true:则则执行完毕之后依然保留,等到下次空闲时会再次执行,
return false;
}
});
(5) Start StrictMode
This mode does not help us automatically optimize performance, but can help us detect things that we may inadvertently or do in some third-party SDKs that will block the Main thread (such as disk operations, network operations), and remind them, In order to be repaired during the development phase. Thread Checker detects its strategies and tactics virtual machine detection strategies, we can set the operation to be detected, when the operation code is illegal, can remind us Logcat or directly by the collapse of the form, specific use as follows :
/**
* 开启严苛模式,当代码有违规操作时,可以通过Logcat或崩溃的方式提醒我们
*/
private void startStrictMode() {
if (BuildConfig.DEBUG) { //一定要在Debug模式下使用,避免在生产环境中发生不必要的崩溃和日志输出
//线程检测策略
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads() //检测主线程磁盘读取操作
.detectDiskWrites() //检测主线程磁盘写入操作
.detectNetwork() //检测主线程网络请求操作
.penaltyLog() //违规操作以log形式输出
.build());
//虚拟机检测策略
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() //检测SqlLite泄漏
.detectLeakedClosableObjects() //检测未关闭的closable对象泄漏
.penaltyDeath() //发生违规操作时,直接崩溃
.build());
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。