Android启动耗时监测(adb shell am、代码打点)

1.应用启动流程

Android 应用程序的载体是 APK 文件,其中包含了组件和资源,APK 文件可能运行在一个独立的进程中,也有可能产生多个进程,还可以多个 APK 运行在同一个进程中,可以通过不同的方式来实现。但有两点需要注意,第一,每个应用只对应一个 Application 对象,并且启动应用一定会产生一个 Application 对象;第二,应用程序可视化组件 Activity 是应用的基本组成之一。这里不去讲述Application和Activity的生命周期,大家估计都知道。

2.启动耗时监测

因为一个应用在启动或者跳入某个页面时是否流畅,时间是否太长,仅仅通过肉眼来观察是非常不准确的,并且在不同设备和环境会有完全不同的表现,所以要准确知道耗时,就需要有效准确的数据,首先通过 shell 来获取启动耗时。

  • adb shell am
    应用启动的时间会受到很多因素的影响,比如首次安装后需要解压 apk 文件,绘制时GPU 的耗时等,所以在应用层很难获取到启动耗时,但借助 ADB 可以得到准确的启动时间。使用 adb shell 获得应用真实的启动时间,代码如下:
// 注:启动的activity必须是{ act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] }
adb shell am start -W com.包名/com.包名.module.start.SplashActivity

执行后可以得到以下的数据:

ThisTime: 1042
TotalTime: 1042
WaitTime: 1077
  • ThisTime:一般和 TotalTime 时间一样,如果在应用启动时开了一个过度的全透明的页面(Activity)预先处理一些事,再显示出主页面(Activity),这样将比 TotalTime 小。

  • TotalTime:应用的启动时间,包括创建进程+Application 初始化+Activity

  • WaitTime:一般比 TotalTime 大些,包括系统影响的耗时。
    但这个方法只能得到固定的某一个阶段的耗时,不能得到具体哪个方法的耗时,下面介绍第二个方案:代码打点输出耗时。

  • 代码打点

下面是具体的代码结构:

// 统计耗时的数据结构
public class TimeMonitor {

    private final String TAG = "TimeMonitor";
    private int monitorId = -1;
    private HashMap<String, Long> mTimeTag = new HashMap<>();
    private long mStartTime = 0;

    public TimeMonitor(int monitorId) {
        Log.e(TAG, "init TimeMonitor id:" + monitorId);
        this.monitorId = monitorId;
    }

    public int getMonitorId() {
        return monitorId;
    }

    public void startMonitor() {
        if (mTimeTag.size() > 0) {
            mTimeTag.clear();
        }
        mStartTime = System.currentTimeMillis();
    }

    // 打一次点,tag交线需要统计的上层定义
    public void recodingTimeTag(String tag) {
        // 检查是否保存过相同的tag
        if (mTimeTag.get(tag) != null) {
            mTimeTag.remove(tag);
        }
        long time = System.currentTimeMillis() - mStartTime;
//        Log.e(TAG, tag + ":" + time);
        mTimeTag.put(tag, time);
    }

    public void end(String tag, boolean writeLog) {
        recodingTimeTag(tag);
        end(writeLog);
    }

    public void end(boolean writeLog) {
        if (writeLog) {
            // 写入到本地文件
        }
        showDataToLogcat();
    }

    private void showDataToLogcat() {
        if (mTimeTag.size() <= 0) {
            Log.e(TAG, "mTimeTag is empty");
        }
        Iterator iterator = mTimeTag.keySet().iterator();
        while (iterator.hasNext()) {
            String tag = (String) iterator.next();
            Log.e(TAG, tag + ":" + mTimeTag.get(tag));
        }
    }

    public HashMap<String, Long> getmTimeTag() {
        return mTimeTag;
    }
}

一个管理id的配置类

public class TimeMonitorConfig {
    // 应用启动
    public static final int TIME_MONITOR_ID_APPLICATION_START = 1;

}

单例类来管理TimeMonitor类。

public class TimeMonitorManager {

    private static TimeMonitorManager mTimeMonitorManager = null;
    private static Context mContext = null;
    private HashMap<Integer, TimeMonitor> timeMonitorList = null;

    public synchronized static TimeMonitorManager getInstance() {
        if (mTimeMonitorManager == null) {
            mTimeMonitorManager = new TimeMonitorManager();
        }
        return mTimeMonitorManager;
    }

    private TimeMonitorManager() {
        timeMonitorList = new HashMap<>();
    }

    // 初始化打点器
    public void resetTimeMonitor(int id) {
        TimeMonitor monitor = timeMonitorList.get(id);
        if (monitor != null) {
            timeMonitorList.remove(id);
        }
        getTimeMonitor(id);
    }

    public TimeMonitor getTimeMonitor(int id) {
        TimeMonitor monitor = timeMonitorList.get(id);
        if (monitor == null) {
            monitor = new TimeMonitor(id);
            monitor.startMonitor();
            timeMonitorList.put(id, monitor);
        }
        return monitor;
    }

}

接下来打点,我们监控应用的启动

// Application中,记得在manifest文件配置
public class App extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        TimeMonitorManager.getInstance().resetTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        init();
        TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recodingTimeTag("ApplicationCreate");
    }
    private void init() {


    }
}

启动页打点如下

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
// 打点     TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recodingTimeTag("MainActivity_Create_start");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
      // .......... 业务代码省略
// 打点     TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recodingTimeTag("MainActivity_Create_over");
    }


    @Override
    protected void onStart() {
        super.onStart();
        // 打点     
        TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).end("MainActivity_onStart", false);
    }

    private static Bitmap getBitmap(Context context, int vectorDrawableId) {
        	// 业务代码省略
    }
}

最后通过打印存储的数据结果如下:

TimeMonitor: init TimeMonitor id:1
TimeMonitor: ApplicationCreate:4
TimeMonitor: MainActivity_Create_start:83
TimeMonitor: MainActivity_Create_over:148
TimeMonitor: MainActivity_onStart:150
  • 可以在项目核心部分添加打点,插桩也是可以的。(插桩:目标程序代码中某些位置插入或修改成一些代码,从而在目标程序运行过程中获取某些程序状态并加以分析。)
发布了119 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/100022547