android性能优化之启动耗时测量(代码片段)

bubbleben bubbleben     2023-01-07     175

关键词:

Android启动优化之启动耗时测量

本文基于Android 11.0源码分析,涉及如下文件

frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.java

frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java

frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java

frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

frameworks/base/core/java/android/app/WaitResult.java

frameworks/base/core/java/android/os/BasicShellCommandHandler.java

frameworks/base/core/java/com/android/internal/os/BaseCommand.java

frameworks/base/cmds/am/src/com/android/commands/am/Am.java

frameworks/base/cmds/am/src/com/android/commands/am/ActivityManagerShellCommand.java

1. Displayed Time

观察应用启动时间最直观的指标就是logcat中包含的Displayed日志,它的输出行代表从启动应用进程开始到在屏幕上完成应用界面的绘制所用的时间:I/ActivityTaskManager( 1489): Displayed com.tencent.mm/.ui.LauncherUI: +502ms

接下来我们从源码角度来分析下这行日志的打印时机:

1.1 应用启动状态

[-> WaitResult.java]

/**
 * Cold launch sequence: a new process has started.
 */
public static final int LAUNCH_STATE_COLD = 1;

/**
 * Warm launch sequence: process reused, but activity has to be created.
 */
public static final int LAUNCH_STATE_WARM = 2;

/**
 * Hot launch sequence: process reused, activity brought-to-top.
 */
public static final int LAUNCH_STATE_HOT = 3;

首先应用有三种启动状态,每种状态都会影响应用显示所需的时间:冷启动、温启动或热启动。在冷启动中,需要启动应用进程。在另外两种状态中,系统需要将后台运行的进程带入前台。一般我们优化的是冷启动的场景,因为这样也可以提升温启动和热启动的性能;

[-> ActivityMetricsLogger.java]

private void logAppDisplayed(TransitionInfoSnapshot info) 
    // 仅记录温启动和冷启动的启动时间, 热启动的启动时间需要通过修改源码或其他方式来获取
    if (info.type != TYPE_TRANSITION_WARM_LAUNCH && info.type != TYPE_TRANSITION_COLD_LAUNCH) 
        return;
    
    
    // 记录event log
    EventLog.writeEvent(WM_ACTIVITY_LAUNCH_TIME,
            info.userId, info.activityRecordIdHashCode, info.launchedActivityShortComponentName,
            info.windowsDrawnDelayMs);

    StringBuilder sb = mStringBuilder;
    sb.setLength(0);
    sb.append("Displayed ");
    // 打印Activity的ComponentName
    sb.append(info.launchedActivityShortComponentName);
    sb.append(": ");
    // 打印启动时间,单位是ms
    TimeUtils.formatDuration(info.windowsDrawnDelayMs, sb);
    Log.i(TAG, sb.toString());

首先logAppDisplayed仅记录温启动(TYPE_TRANSITION_WARM_LAUNCH)和冷启动(TYPE_TRANSITION_COLD_LAUNCH)的启动时间,而不记录热启动(TYPE_TRANSITION_HOT_LAUNCH)的启动时间,它会打印出对应的ComponentName和启动时间;同时也会记录event logI wm_activity_launch_time: [0,62925681,com.tencent.mm/.ui.LauncherUI,502],它与Displayed打印的时间时一致的;

1.2 应用启动时间计算

[-> ActivityMetricsLogger.java]

private void logAppTransitionFinished(@NonNull TransitionInfo info) 
    if (DEBUG_METRICS) Slog.i(TAG, "logging finished transition " + info);

    // Take a snapshot of the transition info before sending it to the handler for logging.
    // This will avoid any races with other operations that modify the ActivityRecord.
    // 传入TransitionInfo,并由它来初始化TransitionInfoSnapshot
    final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
    if (info.isInterestingToLoggerAndObserver()) 
        BackgroundThread.getHandler().post(() -> logAppTransition(
                info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs,
                infoSnapshot));
    
    // 后台线程打印Displayed日志
    BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot));
    if (info.mPendingFullyDrawn != null) 
        info.mPendingFullyDrawn.run();
    

    info.mLastLaunchedActivity.info.launchToken = null;

logAppDisplayed在后台线程上运行,并由logAppTransitionFinished调用执行,其中启动时间TransitionInfoSnapshot.windowsDrawnDelayMs由传入的TransitionInfo.mWindowsDrawnDelayMs进行赋值;

TransitionInfoSnapshot notifyWindowsDrawn(@NonNull ActivityRecord r, long timestampNs) 
    if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn " + r);

    // 从mTransitionInfoList查找ActivityRecord对应的在notifyActivityLaunched创建的TransitionInfo
    final TransitionInfo info = getActiveTransitionInfo(r);
    if (info == null || info.allDrawn()) 
        if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn no activity to be drawn");
        return null;
    
    // Always calculate the delay because the caller may need to know the individual drawn time.
    // 根据当前时间戳计算出启动时间
    info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs);
    info.removePendingDrawActivity(r);
    final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
    if (info.mLoggeTransitionInfodTransitionStarting && info.allDrawn()) 
        // 最终将更新后的TransitionInfo传递给logAppTransitionFinished
        done(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs);
    
    return infoSnapshot;

notifyWindowsDrawn用来通知应用的所有窗口已经绘制成功:TransitionInfo.mWindowsDrawnDelayMs在这里被赋值,根据当前时间戳由TransitionInfo.calculateDelay计算得来:

    int calculateDelay(long timestampNs) 
        // Shouldn't take more than 25 days to launch an app, so int is fine here.
        return (int) TimeUnit.NANOSECONDS.toMillis(timestampNs - mTransitionStartTimeNs);
    

其中timestampNs代表当前时间即启动结束的时间,mTransitionStartTimeNs代表启动开始的时间;

    /**
     * The timestamp of the first @link #notifyActivityLaunching. It can be used as a key for
     * observer to identify which callbacks belong to a launch event.
     */
    final long mTransitionStartTimeNs;

    /** Use @link TransitionInfo#create instead to ensure the transition type is valid. */
    private TransitionInfo(ActivityRecord r, LaunchingState launchingState, int transitionType,
            boolean processRunning, boolean processSwitch) 
        mLaunchingState = launchingState;
        // 创建TransitionInfo并对mTransitionStartTimeNs进行初始化
        mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
        mTransitionType = transitionType;
        mProcessRunning = processRunning;
        mProcessSwitch = processSwitch;
        mCurrentTransitionDeviceUptime =
                (int) TimeUnit.MILLISECONDS.toSeconds(SystemClock.uptimeMillis());
        setLatestLaunchedActivity(r);
        launchingState.mAssociatedTransitionInfo = this;
    

mTransitionStartTimeNsLaunchingState.mCurrentTransitionStartTimeNs赋值;

/**
 * Notifies the tracker at the earliest possible point when we are starting to launch an
 * activity. The caller must ensure that @link #notifyActivityLaunched will be called later
 * with the returned @link LaunchingState.
 */
private LaunchingState notifyActivityLaunching(Intent intent, @Nullable ActivityRecord caller,
        int callingUid) 
    final long transitionStartTimeNs = SystemClock.elapsedRealtimeNanos();
    TransitionInfo existingInfo = null;
    ....
    if (existingInfo == null) 
        // Only notify the observer for a new launching event.
        launchObserverNotifyIntentStarted(intent, transitionStartTimeNs);
        final LaunchingState launchingState = new LaunchingState();
        launchingState.mCurrentTransitionStartTimeNs = transitionStartTimeNs;
        return launchingState;
    
    existingInfo.mLaunchingState.mCurrentTransitionStartTimeNs = transitionStartTimeNs;
    return existingInfo.mLaunchingState;

LaunchingState.mCurrentTransitionStartTimeNsnotifyActivityLaunching中被初始化;

到这里我们已经可以清楚的知道应用启动开始时间以及结束时间是分别在notifyActivityLaunchingnotifyWindowsDrawn记录的,接下来我们接着分析它们又是何时被调用的;

1.3 应用启动开始时间

[-> ActivityStarter.java]

int execute() 
    try 
        ...
        final LaunchingState launchingState;
        synchronized (mService.mGlobalLock) 
            final ActivityRecord caller = ActivityRecord.forTokenLocked(mRequest.resultTo);
            // 通过ActivityMetricsLogger.notifyActivityLaunching创建LaunchingState并记录创建Activity开始的时间
            launchingState = mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(
                    mRequest.intent, caller);
        
        
        ...
        // 执行启动请求
        res = executeRequest(mRequest);
        ...
        
        // Notify ActivityMetricsLogger that the activity has launched.
        // ActivityMetricsLogger will then wait for the windows to be drawn and populate
        // WaitResult.
        // 通过ActivityMetricsLogger.notifyActivityLaunched记录Activity启动完成的时间
        mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState, res,
                mLastStartActivityRecord);
        // 同时将Request.waitResult添加到ActivityStackSupervisor的mWaitingActivityLaunched中,等待窗口绘制完成
        return getExternalResult(mRequest.waitResult == null ? res
                : waitForResult(res, mLastStartActivityRecord));        
         finally 
            onExecutionComplete();
        
            

ActivityStarter.execute()会调用ActivityMetricsLogger.notifyActivityLaunching方法并记录启动开始的时间;

static class DefaultFactory implements Factory 
	@Override
    public ActivityStarter obtain() 
        ActivityStarter starter = mStarterPool.acquire();

        if (starter == null) 
            starter = new ActivityStarter(mController, mService, mSupervisor, mInterceptor);
        

        return starter;
    

ActivityStarter以对象池的方式进行复用,如果池中没有则创建一个新的ActivityStarter对象;

[-> ActivityStartController.java]

/**
 * @return A starter to configure and execute starting an activity. It is valid until after
 *         @link ActivityStarter#execute is invoked. At that point, the starter should be
 *         considered invalid and no longer modified or used.
 */
ActivityStarter obtainStarter(Intent intent, String reason) 
    return mFactory.obtain().setIntent(intent).setReason(reason);

通过工厂模式使用默认的FactoryDefaultFactory)获取ActivityStarter

[-> ActivityTaskManagerService.java]

private int startActivityAsUser(IApplicationThread caller, String callingPackage,
        @Nullable String callingFeatureId, Intent intent, String resolvedType,
        IBinder resultTo, String resultWho, int requestCode, int startFlags,
        ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) 
    assertPackageMatchesCallingUid(callingPackage);
    enforceNotIsolatedCaller("startActivityAsUser");

    userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
            Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");

    // TODO: Switch to user app stacks here.
    // 获取ActivityStarter并执行execute方法
    return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
            .setCaller(caller)
            .setCallingPackage(callingPackage)
            .setCallingFeatureId(callingFeatureId)
            .setResolvedType(resolvedType)
            .setResultTo(resultTo)
            .setResultWho(resultWho)
            .setRequestCode(requestCode)
            .setStartFlags(startFlags)
            .setProfilerInfo(profilerInfo)
            .setActivityOptions(bOptions)
            .setUserId(userId)
            .execute();


应用启动之前通过ActivityStartController获取ActivityStarter初始化启动参数,并调用execute方法记录启动开始的时间,以最典型的startActivity(Intent intent)为例,整个调用流程如下:

--> Activity.startActivity

--> Activity.startActivityForResult

--> Instrumention.execStartActivity

--> ActivityTaskManagerService.startActivity

--> ActivityTaskManagerService.startActivityAsUser

注意:这里还并未真正开启Activity的启动流程,只是应用通过Binder跨进程调用ActivityTaskManagerService.startActivityAsUser准备开启真正的启动流程,对于冷启动来讲还需要先启动应用进程然后再启动对应的主Activity,而其余两种启动方式则会直接启动Activity,可以看到启动开始的时间是由系统侧(system_server)记录的;

1.4 应用启动结束时间

[-> ActivityRecord.java]

void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) 
    firstWindowDrawn = true;

    // We now have a good window to show, remove dead placeholders
    removeDeadWindows();

    if (startingWindow != null) 
        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s"
                + ": first real window is shown, no animation", win.mToken);
        // If this initial window is animating, stop it -- we will do an animation to reveal
        // it from behind the starting window, so there is no need for it to also be doing its
        // own stuff.
        win.cancelAnimation();
    
    // 窗口绘制完成,移除starting window
    removeStartingWindow();
    // 更新窗口可见性并记录启动时间
    updateReportedVisibilityLocked();


void updateReportedVisibilityLocked() 
    ...
    if (nowDrawn != reportedDrawn) 
        // 调用onWindowsDrawn记录启动完成的时间
        onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());
        reportedDrawn = nowDrawn;
    
    ...


/** Called when the windows associated app window container are drawn. */
void onWindowsDrawn(boolean drawn, long timestampNs) 
    mDrawn = drawn;
    if (!drawn) 
        return;
    
    // 调用ActivityMetricsLogger().notifyWindowsDrawn计算启动时间
    final TransitionInfoSnapshot info = mStackSupervisor
            .getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
    ...

--> WindowStateAnimator.commitFinishDrawingLocked

--> WindowState.performShowLocked

--> ActivityRecord.onFirstWindowDrawn

--> ActivityRecord.updateReportedVisibilityLocked

--> ActivityRecord.onWindowsDrawn

--> ActivityMetricsLogger.notifyWindowsDrawn

应用启动结束的调用流程相对来讲比较复杂,涉及到窗口动画以及surface的切换等,篇幅有限我们只关注最后的流程:即经过上面的调用流程之后,最终将启动完成时间传递给onWindowsDrawn记录;

2. adb shell am start -S -W packageName/ActivityName

我们还可以通过adb命令来测量应用启动时间:它会打印出应用的启动模式LaunchState,启动状态Status,以及TotalTimeWaitTime,接下来我们会从源码角度来分析这几项参数的含义:可以看到通过adb命令打印出来的启动时间(TotalTime)与logcat中打印的启动时间时一致的;

D:\\>adb shell am start -S -W com.android.settings/.Settings
Stopping: com.android.settings
Starting: Intent  act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.settings/.Settings 
Status: ok
LaunchState: COLD
Activity: com.android.settings/.Settings
TotalTime: 370
WaitTime: 378
Complete
09-19 09:46:57.474 I/ActivityTaskManager( 1489): Displayed com.android.settings/.Settings: +370ms

2.1 Am

[-> Am.java]

public class Am extends BaseCommand 
    /**
     * Command-line entry point.
     *
     * @param args The command-line arguments
     */
    public static void main(String[] args) 
        // am命令的入口函数, run方法的实现在其父类BaseCommand中
        (new Am()).run(args);
    

adbd会通过shell调用到具体的命令,包括amwmpminput在内所有的命令都可以在/frameworks/base/cmds/目录下找到对应的实现,其中am的实现在Am.java文件中;

[-> BaseCommand.java]

/**
 * Call to run the command.
 */
public void run(String[] args) 
    // 参数有效长度不足时打印命令的使用方法
    if (args.length < 1) 
        onShowUsage(System.out);
        return;
    

    try 
        // 执行Am的onRun方法
        onRun();
     catch (IllegalArgumentException e) 
        onShowUsage(System.err);
        System.err.println();
        System.err.println("Error: " + e.getMessage());
     catch (Exception e) 
        e.printStackTrace(System.err);
        System.exit(1);
    

[-> Am.java]

@Override
public void onRun() throws Exception 
    // 通过ServiceManager获取AMS和PMS
    mAm = ActivityManager.getService();
    mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
    String op = nextArgRequired();

    if (op.equals("instrument")) 
        runInstrument();
     else 
        // 解析命令参数并执行runAmCmd方法
        runAmCmd(getRawArgs());
    


void runAmCmd(String[] args) throws AndroidException 
    final MyShellCallback cb = new MyShellCallback();
    try 
        // 调用ActivityManagerService并执行其shellCommand方法
        mAm.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
                args, cb, new ResultReceiver(null)  );
     catch (RemoteException e) 
        System.err.println(NO_SYSTEM_ERROR_CODE);
        throw new AndroidException("Can't call activity manager; is the system running?");
     finally 
        cb.mActive = false;
    

am命令的功能最终还是通过ActivityManagerService来实现的;

2.2 ActivityManagerService

[-> ActivityManagerService.java]

@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
        FileDescriptor err, String[] args, ShellCallback callback,
        ResultReceiver resultReceiver) 
    (new ActivityManagerShellCommand(this, false)).exec(
            this, in, out, err, args, callback, resultReceiver);

每执行一次am命令,ActivityManagerService都会创建ActivityManagerShellCommand来执行真正的任务;

2.3 ActivityManagerShellCommand

[-> ShellCommand.java]

public abstract class ShellCommand extends BasicShellCommandHandler 

    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) 
        mShellCallback = callback;
        mResultReceiver = resultReceiver;
        // 继续调用其父类BasicShellCommandHandler的exec方法
        final int result = super.exec(target, in, out, err, args);

        if (mResultReceiver != null) 
            mResultReceiver.send(result, null);
        

        return result;
    


ActivityManagerShellCommand继承于ShellCommandexec的实现在其父类ShellCommand中;

[-> BasicShellCommandHandler.java]

public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
        String[] args) 
    String cmd;
    int start;
    if (args != null && args.length > 0) 
        // 获取命令参数
        cmd = args[0];
        start = 1;
     else 
        cmd = null;
        start = 0;
    
    init(target, in, out, err, args, start);
    mCmd = cmd;

    int res = -1;
    try 
        // 回调子类重写的抽象方法onCommand
        res = onCommand(mCmd);
        if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
    
    ...
    return res;

[-> ActivityShellCommand.java]

@Override
public int onCommand(String cmd) 
    if (cmd == null) 
        return handleDefaultCommands(cmd);
    
    final PrintWriter pw = getOutPrintWriter();
    try 
        switch (cmd) 
            case "start":
            case "start-activity":
                // 如果是start或者start-activity命令,则执行runStartActivity方法
                return runStartActivity(pw);
            ...
            default:
                return handleDefaultCommands(cmd);
            
         catch (RemoteException e) 
            pw.println("Remote exception: " + e);
        
        return -1;
    

am支持多种命令,包括ActivityServiceBroadcasatReceiver的启动等等;

private Intent makeIntent(int defUser) throws URISyntaxException 
    mWaitOption = false;
    mStopOption = false;

    return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() 
        @Override
        public boolean handleOption(String opt, ShellCommand cmd) 
            if (opt.equals("-D")) 
                mStartFlags |= ActivityManager.START_FLAG_DEBUG;
             else if (opt.equals("-N")) 
                mStartFlags |= ActivityManager.START_FLAG_NATIVE_DEBUGGING;
             else if (opt.equals("-W")) 
                // 记录mWaitOption
                mWaitOption = true;
             else if (opt.equals("-S")) 
                // 记录mStopOption
                mStopOption = true;
            
            ...
        
    
 

int runStartActivity(PrintWriter pw) throws RemoteException 
    Intent intent;
    try 
        // 解析命令参数, 我们这里用到了-W和-S, 分别记录在mWaitOption和mStopOption中
        intent = makeIntent(UserHandle.USER_CURRENT);
     catch (URISyntaxException e) 
        throw new RuntimeException(e.getMessage(), e);
    
        
    do 
        if (mStopOption) 
            String packageName;
            ...
            // 打印Stopping: com.android.settings
            pw.println("Stopping: " + packageName);
            pw.flush();
            // 这里的mInterface是指ActivityManagerService
            mInterface.forceStopPackage(packageName, mUserId);
            try 
                // 休眠250ms
                Thread.sleep(250);
             catch (InterruptedException e) 
            
        
        
        // 打印Starting: Intent  act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]   
        // cmp=com.android.settings/.Settings 
        pw.println("Starting: " + intent);
        pw.flush();

        // WaitResult用来记录startActivityAndWait的返回结果
        WaitResult result = null;
        int res;
        // 记录调用startActivityAndWait之前的时间
        final long startTime = SystemClock.uptimeMillis();
        
        if (mWaitOption) 
            // 这里的mInterface也是指ActivityManagerService
            result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
                    mimeType, null, null, 0, mStartFlags, profilerInfo,
                    options != null ? options.toBundle() : null, mUserId);
            res = result.result;
        
        
        // 记录调用startActivityAndWait之后的时间
        final long endTime = SystemClock.uptimeMillis();
        PrintWriter out = mWaitOption ? pw : getErrPrintWriter();
        boolean launched = false;
        switch (res) 
            // 启动成功
            case ActivityManager.START_SUCCESS:
                launched = true;
                break;
            ...
        
        out.flush();
        // 启动成功且指定-W参数,打印启动信息
        if (mWaitOption && launched) 
            // 打印启动结果Status: ok
            pw.println("Status: " + (result.timeout ? "timeout" : "ok"));
            // 打印启动状态LaunchState: COLD
            pw.println("LaunchState: " + launchStateToString(result.launchState));
            // 打印TotalTime:TotalTime: 370
            if (result.totalTime >= 0) 
                pw.println("TotalTime: " + result.totalTime);
            
            // 打印WaitTime:WaitTime: 378
            pw.println("WaitTime: " + (endTime-startTime));
            pw.println("Complete");
            pw.flush();
                
    
   

-S参数会指定ActivityManagerService将待启动的应用强行停止以确保其处于冷启动的状态;

-W参数会指定ActivityManagerServicestartActivityAndWait的方式启动指定应用的Activity,并根据返回的WaitResult输出启动结果,启动状态以及TotalTimeWaitTime等信息;

WaitTimeActivityManagerShellCommand执行startActivityAndWait前后的时间差;

TotalTime是执行startActivityAndWait后返回的WaitResult中的totalTime,接下来看下WaitResult中的totalTime是如何计算出来的;

2.4 reportActivityLaunchedLocked

1.3小节的ActivityStarter.execute方法中,我们已经分析过Activity启动完成后会通过ActivityMetricsLogger.notifyActivityLaunched记录Activity启动完成的时间,同时将Request.waitResult添加到ActivityStackSupervisormWaitingActivityLaunched中,等待窗口绘制完成;

[-> ActivityStackSuperVisor.java]

void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long totalTime,
        @WaitResult.LaunchState int launchState) 
    boolean changed = false;
    for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) 
        WaitResult w = mWaitingActivityLaunched.remove(i);
        if (w.who == null) 
            changed = true;
            w.timeout = timeout;
            if (r != null) 
                w.who = new ComponentName(r.info.packageName, r.info.name);
            
            // 根据传入的totalTime来更新WaitResult的totalTime
            w.totalTime = totalTime;
            w.launchState = launchState;
            // Do not modify w.result.
        
    
    if (changed) 
        mService.mGlobalLock.notifyAll();
    

reportActivityLaunchedLocked会取出mWaitingActivityLaunched中的WaitResult,并更新totalTime

[-> ActivityRecord.java]

/** Called when the windows associated app window container are drawn. */
void onWindowsDrawn(boolean drawn, long timestampNs) 
    mDrawn = drawn;
    if (!drawn) 
        return;
    
    final TransitionInfoSnapshot info = mStackSupervisor
            .getActivityMetricsLogger().notifyWindowsDrawn(this, timestampNs);
    final boolean validInfo = info != null;
    final int windowsDrawnDelayMs = validInfo ? info.windowsDrawnDelayMs : INVALID_DELAY;
    final @LaunchState int launchState = validInfo ? info.getLaunchState() : -1;
    // The activity may have been requested to be invisible (another activity has been launched)
    // so there is no valid info. But if it is the current top activity (e.g. sleeping), the
    // invalid state is still reported to make sure the waiting result is notified.
    if (validInfo || this == getDisplayArea().topRunningActivity()) 
        mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
                windowsDrawnDelayMs, launchState);
        mStackSupervisor.stopWaitingForActivityVisible(this, windowsDrawnDelayMs);
    
    finishLaunchTickingLocked();
    if (task != null) 
        task.setHasBeenVisible(true);
    

reportActivityLaunchedLocked又是何时被调用的呢?

这里又回到了1.4小节应用启动结束时间的分析:在窗口绘制完成后,会先通过ActivityMetricsLogger.notifyWindowsDrawn打印Displayed时间,然后调用ActivityStackSuperVisor.reportActivityLaunchedLocked更新WaitResult.totalTime,所以可以看到通过adb命令打印出来的TotaTimelogcat中打印的Displayed Time是一致的,至此adb shell am start -S -W命令的源码分析也到此结束了;

3. Systrace

前面两种测量方式都是针对整体的启动过程进行度量,除此之外我们可能还需要进一步细化启动过程中每个阶段的耗时,比如应用进程的创建,Application/Activity的创建,窗口布局的绘制等等,这时就需要使用Systrace来进行分析,在Android性能优化之Perfetto这篇文章中我们分析了Perfetto的使用,鉴于目前Android正在推广Perfetto,所以我们将使用Perfetto来抓取trace



通过Systrace我们可以看到从Activity.onCreateActivity.onResume,再到第一帧绘制完成的整体耗时,以及各个阶段的耗时情况,同时用户也可以在代码中通过Trace.beginSection()Trace.endSection()增加trace点进行分析和排查;

4. reportFullyDrawn

5. OnPreDrawListener

[--> ViewTreeObserver.java]

/**
 * Interface definition for a callback to be invoked when the view tree is about to be drawn.
 */
public interface OnPreDrawListener 
    /**
     * Callback method to be invoked when the view tree is about to be drawn. At this point, all
     * views in the tree have been measured and given a frame. Clients can use this to adjust
     * their scroll bounds or even to request a new layout before drawing occurs.
     *
     * @return Return true to proceed with the current drawing pass, or false to cancel.
     *
     * @see android.view.View#onMeasure
     * @see android.view.View#onLayout
     * @see android.view.View#onDraw
     */
    public boolean onPreDraw();

6. onWindowFocusChanged

android性能优化实践与总结(包含启动,内存优化)(代码片段)

应用中性能优化实践与总结(精心总结)任何优化都需要进行检测,以数据说话,优化前和优化后有了怎样的提升[TOC]启动优化检测启动时间检测工具任选其一hugo插件,自己定义时间开始和结束手动计算时间.AOP工具AspectJadb的amstart命令... 查看详情

android性能优化---启动优化---2(方法耗时获取与异步初始化)(代码片段)

方法耗时获取方法耗时获取分为常规方式和AOP的方式,AOP的代表主要是学习AspectJ的使用。AOP实战Demo1、想了解更多AOP可以参考https://www.jianshu.com/p/2e8409bc8c3b,2、可能遇到的问题:https://blog.csdn.net/zxl1173558248/article/details/1200857783、... 查看详情

android性能优化---启动优化---2(方法耗时获取与异步初始化)(代码片段)

方法耗时获取方法耗时获取分为常规方式和AOP的方式,AOP的代表主要是学习AspectJ的使用。AOP实战Demo1、想了解更多AOP可以参考https://www.jianshu.com/p/2e8409bc8c3b,2、可能遇到的问题:https://blog.csdn.net/zxl1173558248/article/details/1200857783、... 查看详情

android性能优化-启动速度优化(代码片段)

文章目录1.启动的状态2.冷启动耗时2.1系统日志统计2.2adb命令统计3.启动分析3.1CPUProfile工具简单教程3.2启动耗时分析3.3使用DebugApi生成.trace文件4.StrictMode严苛模式5.结尾做开发除了实现功能,还要注重优化,性能优化包括的... 查看详情

android性能优化之启动优化(代码片段)

Android性能优化之启动优化1.启动窗口优化Android系统在Activity的窗口尚未启动完成前,会先显示一个启动窗口(StartingWindow),等界面的第一帧渲染完成后再从启动窗口切换到真正的界面显示,启动窗口通常情况... 查看详情

android性能优化之启动优化(代码片段)

Android性能优化之启动优化1.启动窗口优化Android系统在Activity的窗口尚未启动完成前,会先显示一个启动窗口(StartingWindow),等界面的第一帧渲染完成后再从启动窗口切换到真正的界面显示,启动窗口通常情况... 查看详情

android面试之必问性能优化(代码片段)

对于Android开发者来说,懂得基本的应用开发技能往往是不够,因为不管是工作还是面试,都需要开发者懂得大量的性能优化,这对提升应用的体验是非常重要的。对于Android开发来说,性能优化主要围绕如下方面展开:启动优化... 查看详情

android性能优化总提纲

Android性能优化(1)–性能优化介绍Android性能优化(2)—启动优化–1(启动优化介绍+启动时间测量)Android性能优化(2)—启动优化—2(方法耗时获取与异步初始化)Android性能优化(3)–内存优化–(内存优化工具、内存管理机制... 查看详情

android性能优化---启动优化--1(启动优化介绍+启动时间测量)

启动优化介绍(1)背景介绍为什么要进行启动优化?第一体验和八秒定律,用户接触app首先就是第一体验比较重要,其次,app如果打开时间超过8s,大概会流失70%的用户。(2)启动分类:冷启动、热启动、温启动冷启动:特点:... 查看详情

android性能优化---启动优化--1(启动优化介绍+启动时间测量)

启动优化介绍(1)背景介绍为什么要进行启动优化?第一体验和八秒定律,用户接触app首先就是第一体验比较重要,其次,app如果打开时间超过8s,大概会流失70%的用户。(2)启动分类:冷启动、热启动、温启动冷启动:特点:... 查看详情

性能深度分析之systemtrace(代码片段)

前言App中大多数的性能指标都和时间相关,如启动速度,列表滑动FPS,页面打开耗时等等。为了优化这些指标,我们需要了解时间都消耗在哪里。通常我们会打开TimeProfiler,通过聚合CallStack来分析和优化代码... 查看详情

❤️android性能优化之启动优化❤️(代码片段)

...赞、收藏、评论粉丝福利:公众号「帅次」一个分享Android体系技术·相关知识·面试题库·技术互助·干货·资讯·高薪职位·教程的地方。🔥背景        用户希望应用能够快速打开。启动时间过长的应用不能满足这个... 查看详情

抖音android性能优化系列:启动优化实践(代码片段)

组件,其初始化就是借助了一个叫ProcessLifecycleOwnerInitializer的ContentProvider进行初始化的。LifeCycle的初始化只是进行了Activity的LifecycleCallbacks的注册耗时不多,我们在逻辑层面上不需要做太多的优化。值得注意的是,如果这类用于... 查看详情

抖音android性能优化系列:启动优化之理论和工具篇

Rhea指占用CPU进行计算所花费的时间绝对值,中断、挂起、休眠等行为是不会增加CPUTime的,所以因CPUTime开销占比高导致的不合理耗时点往往是逻辑本身复杂冗长需要消耗较多cpu时间片才能处理完。比较常见的高CPU占用是循环,比... 查看详情

android面试之必问性能优化(代码片段)

对于Android开发者来说,懂得基本的应用开发技能往往是不够,因为不管是工作还是面试,都需要开发者懂得大量的性能优化,这对提升应用的体验是非常重要的。对于Android开发来说,性能优化主要围绕如下方... 查看详情

android性能优化之内存泄漏检测以及内存优化(下)(代码片段)

上篇博客我们写到了Android中内存泄漏的检测以及相关案例,这篇我们继续来分析一下Android内存优化的相关内容。  上篇:Android性能优化之内存泄漏检测以及内存优化(上)。  中篇:Android性能优化之内... 查看详情

android性能优化之启动速度优化(代码片段)

前言:文本主要会介绍三大块:1.简略介绍APP启动的完整流程,对整个流程有所了解,才知道在哪里可以进行优化。2.一些常用的APP启动优化的方案,主要分为三大块优化方向。3.一些不常见的APP启动优化的方... 查看详情

android性能优化之内存泄漏检测以及内存优化(中)(代码片段)

上篇博客我们写到了Java/Android内存的分配以及相关GC的详细分析,这篇博客我们会继续分析Android中内存泄漏的检测以及相关案例,和Android的内存优化相关内容。  上篇:Android性能优化之内存泄漏检测以及内存优化&... 查看详情