• Application startup speed
  • Visual optimization
    • Start theme optimization
      • 1. Default
      • 2. Transparent theme optimization
      • 3. Set the image theme
  • Code optimization
    • Statistics of cold startup time
      • Adb command Statistics
      • System Log Statistics
    • Code optimization
      • Application to optimize
      • Optimize the blinking page service
      • Page optimization
  • The optimization effect
  • Launch window
  • conclusion
    • reference

Application startup speed

An App’s startup speed can affect a user’s first experience, and a slow (sensory) App launch can lead to a decrease in the user’s intention to start the App again, or to uninstall and abandon the App.

This article will optimize the startup speed of the application in two directions:

  • Visual experience optimization
  • Code logic optimization

Visual optimization

Google Development Documentation

There are three states of application startup, each of which affects how long it takes for the application to be visible to the user: cold startup, hot startup, and warm startup.

On cold startup, the application starts from scratch. In other states, the system needs to run running applications from the background to the foreground. We recommend that you always optimize against cold start assumptions. Doing so can also improve the performance of hot and warm starts.

At the start of cold start, the system has three tasks. These tasks are:

  1. Load and start the application.
  2. A blank startup window that displays the application immediately after startup.
  3. Create the application process.

Once the system creates the application process, the application process takes care of the next stage. These stages are:

  1. Create an app object.
  2. Start the main thread.
  3. Create an Activity object for the application entry.
  4. Fill loading layout Views
  5. Measure -> Layout -> draw

After the application process completes the first drawing, the system process swaps the currently displayed background window for the main activity. At this point, the user can start using the application.

Since the creation of the App process is determined by the hardware and software of the phone, visual optimization is only possible during the creation process.


Start theme optimization

Cold startup: 1. Load and start the application. 2. A blank startup window is displayed immediately after the application is started. 3. Create an application process.

Theme optimization is the setting of the theme of the startup window during cold startup (phases 1 and 2).

Because apps now launch into a LaunchActivity screen that displays App information.

1. Default

If we do nothing to the App (setting the default theme) and initialize other third-party services in the Application (assuming that 2000ms needs to be loaded), then the cold startup process will look like this:

By default, the system starts a blank window when the application is started until the entry Activity of the App is created and the view is drawn. (Presumably when the onWindowFocusChanged method is called back)

2. Transparent theme optimization

In order to solve the problem of a blank launch window, many developers use a transparent theme to solve the problem, but not the root cause.

Although the above problem has been solved, there are still some deficiencies.

<! -- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="android:windowFullscreen">true</item> <item name="android:windowIsTranslucent">true</item> </style>Copy the code



(No white screen, but there is still a visual delay from clicking to App ~)

3. Set the image theme

To make our splash screen more smooth and seamless, we can set the splash screen image in the Theme that starts the Activity so that the splash screen image in the launch window will be a splash screen image instead of a white screen.

<! -- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item Name =" Android :windowBackground">@mipmap/launch</item> <item name=" Android :windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> </style>Copy the code

In this way, the image of the splash screen is displayed during cold startup, and the App process initialloads the Activity (also a splash screen) seamlessly.

In fact, this approach does not really speed up the application process, but only through the user visual optimization experience.


Code optimization

Of course, the above way of setting the theme to optimize the user experience effect is a palliative rather than a cure, and the key lies in the optimization of the code.

First of all, we can count the application cold startup time.

Statistics of cold startup time

Adb command Statistics

Adb command: adb shell am start -s -w Package name/fully qualified name of the startup class, -s means to restart the current application more ADB commands

C:\Android\Demo>adb shell am start -S -W com.example.moneyqian.demo/com.example.moneyqian.demo.MainActivity
Stopping: com.example.moneyqian.demo
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.moneyqian.demo/.MainActivity }
Status: ok
Activity: com.example.moneyqian.demo/.MainActivity
ThisTime: 2247
TotalTime: 2247
WaitTime: 2278
Complete
Copy the code
  • ThisTime: start time of last Activity (e.g. LaunchActivity – >MainActivity “adb input Activity”, count MainActivity start time only)
  • TotalTime: the TotalTime it takes to start a series of activities.
  • WaitTime: application process creation + TotalTime.

  • During the first period, AMS creates an ActivityRecord block, selects a reasonable Task, and pauses the Activity currently resumed.
  • During the ② period, the process is started, activities such as onCreate() without interfaces are called, and activities such as Pause/Finish without interfaces are called.
  • In the third period, onCreate and onResume with interface activities are called.
//ActivityRecord

    private void reportLaunchTimeLocked(final long curTime) {
        ``````
        final long thisTime = curTime - displayStartTime;
        final long totalTime = stack.mLaunchStartTime != 0 ? (curTime - stack.mLaunchStartTime) : thisTime;
    }Copy the code

To sum up: if you need to count the time from clicking the desktop icon to the Activity starting up, you can use WaitTime as the standard, but the system startup time cannot be optimized, so we just need to care about ThisTime to optimize the cold start.

System Log Statistics

You can also count the startup time based on the system logs. To find the spent time in Android Studio, you must disable No Filters in the Logcat view. This is the system’s log output, not the application’s. You can also check the startup time of other apps.

filterdisplayedOutput startup logs.


Code optimization

According to the output statistics of startup time above, we can record the cold startup time before optimization, and then compare the startup time after optimization.

Application to optimize

Application, as the entry point for the entire initial configuration of an Application, often carries a burden it shouldn’t

There are many third-party components (including the App itself) that preempt the initialization of the Application.

However, heavy initialization operations and complex logic in the Application will affect the startup performance of the Application

Often, there is an opportunity to optimize these efforts to achieve performance improvements. Common questions include:

  1. Complex layout initialization
  2. Operations that block the main thread UI drawing, such as I/O reads and writes or network access.
  3. Bitmap large image or VectorDrawable loaded
  4. Other operations that occupy the main thread

Initialization can be classified according to the priority of these components:

  1. The necessary components must be initialized immediately in the main thread (the entry Activity may be used immediately)
  2. Components must be initialized in the main thread, but can be delayed.
  3. Components can be initialized in child threads.

Component initializations placed on child threads are recommended to be delayed so you can see if there is any impact on the project!

Therefore, for the above analysis, we can optimize the loading component of Application in the project as follows:

  • Bugly, X5 kernel initialization, SP read and write, friendship and other components in the child thread initialization. (Child thread initialization does not affect component usage.)
New Thread(new Runnable() {@override public void run() { Don't rob resources than the main Process. SetThreadPriority (Process. THREAD_PRIORITY_BACKGROUND); // The child Thread initializes the third-party component thread.sleep (5000); // It is recommended to delay initialization to see if other functions are affected, or crash! } }).start();Copy the code
  • Lazy loading of actions that need to be initialized in the main thread but don’t need to be completed immediately. (This is supposed to be done in the entry Activity, but it is better to centrally manage component initialization in the Application.)
Handler. PostDelayed (new Runnable() {@override public void run() {// Delay initialization component}}, 3000);Copy the code

Optimize the blinking page service

There are still a few components left to initialize the main thread, such as buried points, click streams, database initialization, etc., but these can be offset elsewhere.

Requirement Background: The application App usually sets a fixed display time of the flash screen, such as 2000ms. Therefore, we can adjust the display time according to the running speed of the user’s phone, but the total time is still 2000ms.

Total display time = component initialization time + Remaining display time.

This is the total time of 2000ms, the component initialized 800ms, so show another 1200ms.

Although the source code of the following picture is not the latest source code (5.0 source code), it does not affect the overall process. (7.0,8.0 method names will change).

During cold startup, the system will initialize the Application process and create tasks such as Application. At this time, the Starting Window will be displayed. As mentioned above, if the theme is not optimized, the screen will be blank. To learn more about the source of the startup process, check out my blog: The process of launching an Activity

After analyzing the source code, we know that the Application initializes by calling the attachBaseContext() method, calling the Application’s onCreate() method, and then creating and executing the onCreate() method on the entry Activity. So we can record the startup time in the Application.

//Application @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); SPUtil.putLong("application_attach_time", System.currentTimeMillis()); // Record the Application initialization time}Copy the code

With the startup time, we need to know the time the Acitivty of the entry displays to the user (the View has been drawn). We learned from the blog (the View’s workflow) that the callback time of onWindowFocusChanged() indicates that the user’s touch time can be obtained and the View’s process has been drawn. So we can record the display time in this method.

/ / entrance Activity @ Override public void onWindowFocusChanged (Boolean hasFocus) {super. OnWindowFocusChanged (hasFocus); long appAttachTime = SPUtil.getLong("application_attach_time"); long diffTime = System.currentTimeMillis() - appAttachTime; // The time from application to entry Acitity // so the time displayed on the splash page is 2000ms-diffTime.}Copy the code

Therefore, we can dynamically set the display time of the application flash screen, and try to make the display time of each mobile phone consistent, so that users with low mobile phone configuration will not feel the long time of the flash screen (for example, after the initialization of 2000ms, they have to display the flash screen time of 2000ms). To optimize user experience.


Page optimization

The flash screen is followed by an AD page for the donor fathers.

Because the pictures on the advertisement page in the project may be large pictures or APng dynamic pictures, it is necessary to download these pictures to local files and display them after downloading. This process often encounters the following two problems:

  • The AD page download, because it’s an asynchronous process, often doesn’t know when the right time to load onto the page is.
  • Save the AD page, because save is I/O stream operation, it is likely to be interrupted by the user, the next time to get damaged pictures.

Because the network environment of the user is not clear, some users may need a period of time to download the AD page, and at this time it is impossible to wait indefinitely. So for this problem we can start IntentService to download the AD page image.

  • Start IntentService in the entry Acitivity to download the AD page. Or other asynchronous download operations.
  • Record the image size after the AD page image file stream is fully written, or record a logo.

In the next AD page loading, you can judge whether the AD page picture has been downloaded and whether the picture is complete, otherwise delete and download the picture again.

In addition, there is still remaining display time in the flash screen page, so if the user has downloaded the picture and the picture is complete during this time, the AD page can be displayed. Otherwise, enter the main Activity, as IntentService continues to silently download and save images in the background


The optimization effect

Before optimization: (Mi 6)

Displayed LaunchActivity MainActivity
+2s526ms +1s583ms
+2s603ms +1s533ms
+2s372ms +1s556ms

Optimized: (Mi 6)

Displayed LaunchActivity MainActivity
+995ms +1s191ms
+911ms +1s101ms
+903ms +1s187ms

After the startup test of MI 6, MI MIX2S and MI 2S, it was found that the startup speed of cold startup of App after optimization was increased by 60%!! And we can take a look at the memory of the phone when it starts cold:

Before optimization: With the creation and collection of a large number of objects, the system GC was performed 5 times within 15s. Memory usage is rippling.



To view a larger version

After optimization: the object was created in a stable and rising state, and the system GC was performed 5 times within 15s. (New functions were added in later business development, so the amount of code increased.) After that, total memory usage drops gently.



To view a larger version

  • Other: Memory that the application system does not know how to classify.
  • Code: Memory used by the application to process Code and resources such as dex bytecodes, optimized or compiled dex codes,.so libraries, and fonts.
  • Stack: Memory used by the native Stack and Java Stack in the application. This usually depends on how many threads your application runs.
  • The buffer queue displays the memory used by pixels (including GL surfaces, GL textures, and so on) to the screen. (Please note that this is CPU-shared memory, not GPU-dedicated memory.)
  • Native: Object memory allocated from C or C++ code. Even if you don’t use C++ in your application, you might see some of the native memory used here, because the Android framework uses native memory to represent various tasks, such as image resources and other graphics, even if the code is written in Java or Kotlin.
  • Java: Object memory allocated from Java or Kotlin code.
  • Allocated: Number of Java/Kotlin objects Allocated by the application. It doesn’t count toward objects allocated in C or C++.

See more: Google Development Documentation







Launch window

After optimizing our code, analyze the source of the launch window. Based on the android – 25 (7.1.1)

The start Window is a Window that is managed by WindowManagerService. It is used as a preview Window for activities that enter the cold start page. The start Window is determined by ActivityManagerService. This window is not displayed for every Activity launch or jump.

WindowManagerService creates the launch window through the window management policy class PhoneWindowManager.



Pictures fromLao Luo source code analysis

Take a look at the startup flowchart in my previous source code analysis article: the working process of a Launcher Activity



To view a larger version

To get to the point, the startActivityLocked() method of ActivityStack is called in the startActivityUnchecked() method of ActivityStarter. At this point, the Activity is still being started and the window is not displayed.



To view a larger version

First, the flow chart shows the display process of the startup window.

First, the Activity state manager ActivityStack starts the process of displaying the launch window.

//ActivityStack final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition, ActivityOptions options) { `````` if (! IsHomeStack () | | numActivities () > 0) {/ / HOME_STACK said the Launcher the Stack in the desktop / / 1. // We want to show the starting preview window if We are // switching to a We want to show the starting preview window if We are // switching to a new task, or the next activity's process is // not currently running. boolean doShow = true; If (newTask) {// 2. Start the Activity component in a newTask stack // Even though this Activity is starting fresh, we still need // to reset it to make sure we apply affinities to move any // existing activities from other tasks in to it. if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) ! = 0) { resetTaskIfNeededLocked(r, r); doShow = topRunningNonDelayedActivityLocked(null) == r; } } else if (options ! = null && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) { doShow = false; } if (r.mLaunchTaskBehind) { //3. Warm start, Don't do a starting window for mLaunchTaskBehind. More importantly make sure we // tell WindowManager that R is visible even though it is at the back of the stack. mWindowManager.setAppVisibility(r.appToken, true); ensureActivitiesVisibleLocked(null, 0, ! PRESERVE_WINDOWS); } else if (SHOW_APP_STARTING_PREVIEW && doShow) { `````` //4. R.howstartingwindow (prev, showStartingIcon); }} else {// If this is the first activity, don't do any fancy animations, // because there is nothing for it to animate on top of. `````` } }Copy the code
  1. First, determine that the Activity you want to start is not in the Launcher stack
  2. Whether the Activity to be started is in a new Task and does not have a transition animation
  3. If the startup is hot/warm, you do not need to start the window and directly set the Visibility of the App

The showStartingWindow() method of ActivityRecord is then called to set up the launch window and change the state of the current window.

If the App process is created and the entry Activity is ready, you can use mStartingWindowState to determine whether you need to close the startup window.

//ActivityRecord


    void showStartingWindow(ActivityRecord prev, boolean createIfNeeded) {
        final CompatibilityInfo compatInfo =
                service.compatibilityInfoForPackageLocked(info.applicationInfo);
        final boolean shown = service.mWindowManager.setAppStartingWindow(
                appToken, packageName, theme, compatInfo, nonLocalizedLabel, labelRes, icon,
                logo, windowFlags, prev != null ? prev.appToken : null, createIfNeeded);
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN;
        }
    }Copy the code

WindowManagerService determines the token and theme of the current Activity.

//WindowManagerService @Override public boolean setAppStartingWindow(IBinder token, String pkg, int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, IBinder transferFrom, boolean createIfNeeded) { synchronized(mWindowMap) { //1. AppWindowToken wToken = findAppWindowToken(token); //2. If (wtoken.startingData! = null) { return false; } // If this is a translucent window, then don't // show a starting window -- the current effect (a full-screen // opaque starting window that fades away to the real contents // when it is ready) does not work for this. if (theme ! = 0) { AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme, com.android.internal.R.styleable.Window, mCurrentUserId); If (windowIsTranslucent) {return false; if (windowIsTranslucent) {return false; } if (windowIsFloating || windowDisableStarting) { return false; } ``````} //4. Create StartingData StartingData = new startingData (PKG, theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, etc.) windowFlags); Message m = mH.obtainMessage(H.ADD_STARTING, wtoken); // Note: we really want to do sendMessageAtFrontOfQueue() because we // want to process the message ASAP, before any other queued // messages. mH.sendMessageAtFrontOfQueue(m); } return true; }Copy the code
  1. The startup window also needs to have the same token as the Activity. Although the startup window may have a blank screen or an image, it still needs to go through the drawing process to pass the WMS display window.
  2. The StartingData object is used to represent the data related to the start window and describes the view information of the start window.
  3. If the current Activity is a transparent theme or floating window, etc., there is no need for a launch window to transition the startup process, so the transparent theme setting in the visual optimization above does not show a white launch window.
  4. Displaying the launch window is also a bit of a rush, WMS internal classHThe main thread processes the Message, so the current Message needs to be placed at the head of the queue.

PS: Why do I need to send messages through Handler? You can see handlers in various services, and they may have a very odd name, H, because it is possible that one of the execution methods calling the Service is in a child thread, so the Handler’s job is to switch them to the main thread, and also to manage the scheduling. Read more about Handler in the article: Do you really know Handler?

//WindowManagerService --> H public void handleMessage(Message msg) { switch (msg.what) { case ADD_STARTING: { final AppWindowToken wtoken = (AppWindowToken)msg.obj; final StartingData sd = wtoken.startingData; View view = null; try { final Configuration overrideConfig = wtoken ! = null && wtoken.mTask ! = null ? wtoken.mTask.mOverrideConfig : null; view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme, sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo, sd.windowFlags, overrideConfig); } catch (Exception e) { Slog.w(TAG_WM, "Exception when adding starting window", e); } `````` } break; }Copy the code

In the current handleMessage method, it is in the main thread processing messages, to get the token and StartingData start after the data, then by mPolicy addStartingWindow () method will launch WIndow is added to the WIndow.

MPolicy is PhoneWindowManager, which controls the addition, deletion and modification of startup Windows.

Configure the launch window in PhoneWindowManager, get the theme and resource information of the current Activity setting, and set it in the launch window.

//PhoneWindowManager @Override public View addStartingWindow(IBinder appToken, String packageName, int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, Configuration overrideConfig) {// You can use SHOW_STARTING_ANIMATIONS to not show the start window if (! SHOW_STARTING_ANIMATIONS) { return null; } WindowManager wm = null; View view = null; Try {//1. Get the Context and the theme. if (theme ! = context.getThemeResId() || labelRes ! = 0) { try { context = context.createPackageContext(packageName, 0); context.setTheme(theme); } catch (PackageManager.NameNotFoundException e) { // Ignore } } //2. Create PhoneWindow to display final PhoneWindow win = new PhoneWindow(context); win.setIsStartingWindow(true); //3. Set the current window type and flag. win.setType( WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); // Force the window flags: this is a fake window, so it is not really // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM // flag because we do know that the next window will take input // focus, so we want to get the IME window up on top of us right away. win.setFlags( windowFlags| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, windowFlags| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); win.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); `````` view = win.getDecorView(); Wm. addView(view, params); return view.getParent() ! = null ? view : null; } catch (WindowManager.BadTokenException e) { // ignore } catch (RuntimeException e) { // don't crash if something else bad happens, for example a // failure loading resources because we are loading from an app // on external storage that has been unmounted. Log.w(TAG, appToken + " failed creating starting window", e); } return null; }Copy the code
  1. If the theme and labelRes values are not 0, then the developer specified a theme and title for the launch window, which needs to be retrieved from the current Activity to be launched and set to the launch window.
  2. Like any other window, the launch window also needs to set the layout information DecorView through the PhoneWindow. So in the above visual optimization Settings splash screen picture theme of the launch window display is the picture content.
  3. A launch window differs from a regular window in that it is a fake window and does not require a touch event
  4. Finally, the startup window will be displayed by WindowManger through the View’s measure-layout-draw process. Finally, WindowManagerService will be requested to add a WindowState object for the startup window, which will actually show the startup window to the user. And you can manage the startup window.

More Windows Manager addView flow can be found in View workflow


conclusion

At this point the application start optimization and start window source code analysis has been summarized, in the development of the project to know and why, and the source code analysis helps us to understand the principle and solve the problem.

reference

Google Development Documentation

How do I count the startup time of Android App