Android:应用启动优化

https://developer.android.com/topic/performance/launch-time.html

Launch-Time Performance

Users expect apps to be responsive and fast to load. An app with a slow startuptime doesn’t meet this expectation, and can be disappointing to users. Thissort of poor experience may cause a user to rate your app poorly on the Playstore, or even abandon your app altogether.

This document provides information to help you optimize your app’s launch time.It begins by explaining the internals of the launch process. Next, it discusseshow to profile startup performance. Last, it describes some common startup-timeissues, and gives some hints on how to address them.

Launch Internals


App launch can take place in one of three states, each affecting howlong it takes for your app to become visible to the user: cold start,warm start, and lukewarm start. In a cold start, your app starts from scratch.In the other states, the system needs to bring the app from the background tothe foreground. We recommend that you always optimize based on an assumption ofa cold start. Doing so can improve the performance of warm and lukewarm starts,as well.

To optimize your app for fast startup, it’s useful to understand what’shappening at the system and app levels, and how they interact, in each ofthese states.

Cold start

A cold start refers to an app’s starting from scratch: the system’s processhas not, until this start, created the app’s process. Cold starts happen incases such as your app’s being launched for the first time since the devicebooted, or since the system killed the app. This type of start presents thegreatest challenge in terms of minimizing startup time, because the systemand app have more work to do than in the other launch states.

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

  1. Loading and launching the app.
  2. Displaying a blank starting window for the app immediately after launch.
  3. Creating the app process.

As soon as the system creates the app process, the app process is responsiblefor the next stages. These stages are:

  1. Creating the app object.
  2. Launching the main thread.
  3. Creating the main activity.
  4. Inflating views.
  5. Laying out the screen.
  6. Performing the initial draw.

Once the app process has completed the first draw, the system process swapsout the currently displayed background window, replacing it with the mainactivity. At this point, the user can start using the app.

Figure 1 shows how the system and app processes hand off work between eachother.


Figure 1. A visual representation of the important parts of a cold application launch.

Performance issues can arise during creation of the app andcreation of the activity.

Application creation

When your application launches, the blank starting window remains on the screenuntil the system finishes drawing the app for the first time. At that point,the system process swaps out the starting window for your app, allowing theuser to start interacting with the app.

If you’ve overloaded Application.oncreate()in your own app, the system invokes the onCreate() method on yourapp object. Afterwards, the app spawns the main thread, also known as the UIthread, and tasks it with creating your main activity.

From this point, system- and app-level processes proceed in accordance withthe app lifecycle stages.

Activity creation

After the app process creates your activity, the activity performs thefollowing operations:

  1. Initializes values.
  2. Calls constructors.
  3. Calls the callback method, such as Activity.onCreate(), appropriate to the current lifecycle state of the activity.

Typically, theonCreate()method has the greatest impact on load time, because it performs the work withthe highest overhead: loading and inflating views, and initializing the objectsneeded for the activity to run.

Warm start

A warm start of your application is much simpler and lower-overhead than acold start. In a warm start, all the system does is bring your activity tothe foreground. If all of your application’s activities are still resident inmemory, then the app can avoid having to repeat object initialization, layoutinflation, and rendering.

However, if some memory has been purged in response to memory trimmingevents, such asonTrimMemory(),then those objects will need to be recreated inresponse to the warm start event.

A warm start displays the same on-screen behavior as a cold start scenario:The system process displays a blank screen until the app has finished renderingthe activity.

Lukewarm start

A lukewarm start encompasses some subset of the operations thattake place during a cold start; at the same time, it represents less overheadthan a warm start. There are many potential states that could be consideredlukewarm starts. For instance:

  • The user backs out of your app, but then re-launches it. The process may have continued to run, but the app must recreate the activity from scratch via a call to onCreate().
  • The system evicts your app from memory, and then the user re-launches it. The process and the Activity need to be restarted, but the task can benefit somewhat from the saved instance state bundle passed into onCreate().

Profiling Launch Performance


In order to properly diagnose start time performance, you can track metricsthat show how long it takes your application to start.

Time to initial display

From Android 4.4 (API level 19), logcat includes an output line containinga value called Displayed. This value representsthe amount of time elapsed between launching the process and finishing drawingthe corresponding activity on the screen. The elapsed time encompasses thefollowing sequence of events:

  1. Launch the process.
  2. Initialize the objects.
  3. Create and initialize the activity.
  4. Inflate the layout.
  5. Draw your application for the first time.

The reported log line looks similar to the following example:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

If you’re tracking logcat output from the command line, or in a terminal,finding the elapsed time is straightforward. To find elapsed time inAndroid Studio, you must disable filters in your logcat view. Disabling thefilters is necessary because the system server, not the app itself, servesthis log.

Once you’ve made the appropriate settings, you can easily search for thecorrect term to see the time. Figure 2 shows how to disable filters, and,in the second line of output from the bottom, an example of logcat output ofthe Displayed time.


Figure 2. Disabling filters, and finding the Displayed value in logcat.

The Displayed metric in the logcat output does not necessarily capturethe amount of time until all resources are loaded and displayed: it leaves outresources that are not referenced in the layout file or that the app createsas part of object initialization. It excludes these resources because loadingthem is an inline process, and does not block the app’s initial display.

Sometimes the Displayed line in the logcat output contains an additional field fortotal time. For example:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

In this case, the first time measurement is only for the activity that was first drawn. Thetotal time measurement begins at the app process start, and could include anotheractivity that was started first but did not display anything to the screen. The totaltime measurement is only shown when there is a difference between the single activity and totalstartup times.

You can also measure the time to initial display by running your app with theADB Shell Activity Managercommand. Here's an example:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
The Displayed metric appears in the logcat output as before. Yourterminal window should also display the following:
Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

The -c and -a arguments are optional and let you specify<category>and <action>for the intent.

Time to full display

You can use the reportFullyDrawn() method tomeasure the elapsed timebetween application launch and complete display of all resources and viewhierarchies. This can be valuable in cases where an app performs lazy loading.In lazy loading, an app does not block the initial drawing of the window, butinstead asynchronously loads resources and updates the view hierarchy.

If, due to lazy loading, an app’s initial display does not include allresources, you might consider the completed loading and display of allresources and views as a separate metric: For example, your UI might befully loaded, with some text drawn, but not yet display images that theapp must fetch from the network.

To address this concern, you can manually callreportFullyDrawn()to let the system know that your activity isfinished with its lazy loading. When you use this method, the valuethat logcat displays is the time elapsedfrom the creation of the application object to the momentreportFullyDrawn() is called. Here's an exampleof the logcat output:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

The logcat output sometimes includes a total time, as discussed inTime to initial display.

If you learn that your display times are slower than you’d like, you cango on to try to identify the bottlenecks in the startup process.

Identifying bottlenecks

Two good ways to look for bottlenecks are Android Studio’s Method Tracer tooland inline tracing. To learn about Method Tracer, see that tool’sdocumentation.

If you do not have access to the Method Tracer tool, or cannot start the toolat the correct time to gain log information, you can gain similar insightthrough inline tracing inside of your apps’ and activities’ onCreate()methods. To learn about inline tracing, see the reference documentation forthe Trace functions, and for theSystrace tool.

Common Issues


This section discusses several issues that often affect apps’ startupperformance. These issues chiefly concern initializing app and activityobjects, as well as the loading of screens.

Heavy app initialization

Launch performance can suffer when your code overrides the Applicationobject, and executes heavy work or complex logic when initializing that object.Your app may waste time during startup if your Application subclasses performinitializations that don’t need to be done yet. Some initializations may becompletely unnecessary: for example, initializing state information for themain activity, when the app has actually started up in response to an intent.With an intent, the app uses only a subset of the previously initialized statedata.

Other challenges during app initialization include garbage-collection eventsthat are impactful or numerous, or disk I/O happening concurrently withinitialization, further blocking the initialization process. Garbage collectionis especially a consideration with the Dalvik runtime; the Art runtime performsgarbage collection concurrently, minimizing that operation's impact.

Diagnosing the problem

You can use method tracing or inline tracing to try to diagnose the problem.

Method tracing

Running the Method Tracer tool reveals that thecallApplicationOnCreate()method eventually calls your com.example.customApplication.onCreatemethod. If the tool shows that thesemethods are taking a long time to finish executing, you should explore furtherto see what work is occurring there.

Inline tracing

Use inline tracing to investigate likely culprits including:

  • Your app’s initial onCreate() function.
  • Any global singleton objects your app initializes.
  • Any disk I/O, deserialization, or tight loops that might be occurring during the bottleneck.

Solutions to the problem

Whether the problem lies with unnecessary initializations or disk I/O,the solution calls for lazy-initializing objects: initializing only thoseobjects that are immediately needed. For example, rather than creating globalstatic objects, instead, move to a singleton pattern, where the app initalizesobjects only the first time it accesses them. Also, consider using a dependencyinjection framework like Daggerthat creates objects and dependencies are when they are injected for the first time.

Heavy activity initialization

Activity creation often entails a lot of high-overhead work. Often, there areopportunities to optimize this work to achieve performance improvements. Suchcommon issues include:

  • Inflating large or complex layouts.
  • Blocking screen drawing on disk, or network I/O.
  • Loading and decoding bitmaps.
  • Rasterizing VectorDrawable objects.
  • Initialization of other subsystems of the activity.

Diagnosing the problem

In this case, as well, both method tracing and inline tracing can prove useful.

Method tracing

When running the Method Tracer tool, the particular areas tofocus on your your app’s Application subclass constructors andcom.example.customApplication.onCreate() methods.

If the tool shows that these methods are taking a long time to finishexecuting, you should explore further to see what work is occurring there.

Inline tracing

Use inline tracing to investigate likely culprits including:

  • Your app’s initial onCreate() function.
  • Any global singleton objects it initializes.
  • Any disk I/O, deserialization, or tight loops that might be occurring during the bottleneck.

Solutions to the problem

There are many potential bottlenecks, but two common problems and remediesare as follows:

  • The larger your view hierarchy, the more time the app takes to inflate it. Two steps you can take to address this issue are:
    • Flattening your view hierarchy by reducing redundant or nested layouts.
    • Not inflating parts of the UI that do not need to be visible during launch. Instead, use use a ViewStub object as a placeholder for sub-hierarchies that the app can inflate at a more appropriate time.
  • Having all of your resource initialization on the main thread can also slow down startup. You can address this issue as follows:
    • Move all resource initialization so that the app can perform it lazily on a different thread.
    • Allow the app to load and display your views, and then later update visual properties that are dependent on bitmaps and other resources.

Themed launch screens

You may wish to theme your app’s loading experience, so that the app’slaunch screen is thematically consistent with the rest of the app, instead ofwith the system theming. Doing so can hide a slow activity launch.

A common way to implement a themed launch screen is to use thewindowDisablePreview theme attribute to turn offthe initial blank screenthat the system process draws when launching the app. However, this approachcan result in a longer startup time than apps that don’t suppress the previewwindow. Also, it forces the user to wait with no feedback while the activitylaunches, making them wonder if the app is functioning properly.

Diagnosing the problem

You can often diagnose this problem by observing a slow response when a userlaunches your app. In such a case, the screen may seem to be frozen, or tohave stopped responding to input.

Solutions to the problem

We recommend that, rather than disabling the preview window, youfollow the commonMaterial Design patterns. You can use the activity'swindowBackground theme attribute to provide a simple custom drawablefor the starting activity.

For example, you might create a new drawable file and reference it from thelayout XML and app manifest file as follows:

Layout XML file:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>

Manifest file:

<activity ...
android:theme="@style/AppTheme.Launcher" />

The easiest way to transition back to your normal theme is to callsetTheme(R.style.AppTheme)before calling super.onCreate() and setContentView():

public class MyMainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Make sure this is before calling super.onCreate
    setTheme(R.style.Theme_MyApp);
    super.onCreate(savedInstanceState);
    // ...
  }
}

猜你喜欢

转载自blog.csdn.net/ultrapro/article/details/77049687