Measure Android App Launch Time Using Logcat and Multithreading

This article presents a Java‑based solution that repeatedly launches and closes an Android app while capturing logcat output to automatically calculate splash‑screen and home‑screen launch times, providing a reproducible method for accurate performance testing.

FunTester
FunTester
FunTester
Measure Android App Launch Time Using Logcat and Multithreading

Overview

The author needed a reliable way to measure both the cold start time of an Android app and the time to reach its home screen. Traditional methods such as screenshot comparison or manual screen recording were unsatisfactory, so a custom multithreaded logger was created to parse logcat entries for activity launch timestamps.

Core Approach

The solution consists of two parts:

A LauchTime thread that runs adb logcat continuously, watches for lines containing the Displayed keyword, extracts the timing information for SplashActivity and HomeActivity, and stores the results in a local MySQL database.

A StartApp helper that repeatedly starts and stops the target application using adb shell am start and adb shell am force-stop.

By looping the start‑stop sequence several times, the logger gathers multiple measurements, which can be averaged or analyzed further.

Key Code Snippets

package monkeytest;

import java.io.*;
import java.util.regex.*;
import source.AppLocalMySql;
import source.Common;

public class LauchTime extends Thread {
    public static String ADB_PATH = Common.ADB_PATH;
    public static String package_name = Common.PAGEKAGE;
    public static String test_name = "normal";
    public static Pattern pattern = Pattern.compile("\\+.+?ms");
    public static boolean LauchKey = false;

    public static void main(String[] args) {
        execCmdAdb("adb logcat -c");
        Common.getInstance().sleep(2000);
        LauchTime lauchTime = new LauchTime();
        lauchTime.start(); // start logcat collection thread
        StartApp startApp = new StartApp();
        for (int i = 0; i < 5; i++) {
            startApp.startJuziApp();
            Common.getInstance().sleep(9000);
            startApp.stopJuziApp();
            Common.getInstance().sleep(1000);
        }
        lauchTime.stopLauch(); // stop collection
    }

    @Override
    public void run() {
        execCmdAdb("adb logcat");
    }

    /** Stop the logcat thread */
    public void stopLauch() {
        LauchTime.LauchKey = true;
    }

    private static void execCmdAdb(String cmd) {
        System.out.println("Executing: " + cmd);
        String OSname = System.getProperty("os.name");
        try {
            Process p;
            if (OSname.contains("Mac")) {
                p = Runtime.getRuntime().exec(Common.ADB_PATH + cmd);
            } else {
                p = Runtime.getRuntime().exec("cmd /c " + cmd);
            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                if (LauchKey) {
                    p.destroy();
                    reader.close();
                    break;
                }
                if (line.contains("Displayed")) {
                    if (line.contains("SplashActivity")) {
                        double time = getLauchTime(line);
                        AppLocalMySql.getInstance().saveLauchTime(test_name, package_name, "SplashActivity", time);
                    }
                    if (line.contains("HomeActivity")) {
                        double time = getLauchTime(line);
                        AppLocalMySql.getInstance().saveLauchTime(test_name, package_name, "HomeActivity", time);
                    }
                }
            }
            reader.close();
            // handle error stream
            BufferedReader errorReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            String errLine;
            while ((errLine = errorReader.readLine()) != null) {
                output(errLine);
            }
            errorReader.close();
        } catch (IOException e) {
            Common.getInstance().output("Failed to execute " + cmd);
            e.printStackTrace();
        }
    }

    /** Extract launch time from a log line */
    public static double getLauchTime(String line) {
        Matcher matcher = pattern.matcher(line);
        if (matcher.find()) {
            line = matcher.group(0);
            line = line.substring(1, line.length() - 2);
            line = line.replace("s", ".");
            if (!line.contains(".")) {
                line = "0." + line;
            }
            return Common.getInstance().changeStringToDouble(line);
        }
        return 0.00;
    }

    public static void output(String text) {
        System.out.println(text);
    }

    public static void output(Object... object) {
        if (object.length == 1) {
            output(object[0].toString());
            return;
        }
        for (int i = 0; i < object.length; i++) {
            System.out.println("Item " + (i + 1) + ": " + object[i]);
        }
    }
}
/** Start the target app */
public void startJuziApp() {
    if (Monkey.package_name.contains("happyjuzi")) {
        execCmdAdb("adb shell am start -n com.happyjuzi.apps.juzi/.SplashActivity");
    } else if (Monkey.package_name.contains("article.news")) {
        execCmdAdb("adb shell am start -n com.ss.android.article.news/.activity.SplashBadgeActivity");
    }
}

public void stopJuziApp() {
    if (Monkey.package_name.contains("happyjuzi")) {
        execCmdAdb("adb shell am force-stop com.happyjuzi.apps.juzi");
    } else if (Monkey.package_name.contains("article.news")) {
        execCmdAdb("adb shell force-stop com.ss.android.article.news");
    }
}

Running the Test

1. Ensure adb is in the system PATH or set Common.ADB_PATH accordingly.

2. Compile and run LauchTime. The program clears the logcat buffer, starts the collector thread, and then launches the app five times, pausing between launches to allow the UI to settle.

3. After the loop, the collector thread stops, and the measured times for SplashActivity and HomeActivity are persisted in the local MySQL database for further analysis.

Considerations

The approach assumes the logcat output contains lines like Displayed com.xxx/.SplashActivity: +123ms. If the activity names differ, adjust the string checks accordingly. The method also relies on a stable device connection; intermittent ADB failures may cause missing entries.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAndroidPerformance TestingmultithreadinglogcatLaunch Time
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.