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
- 可以在项目核心部分添加打点,插桩也是可以的。(插桩:目标程序代码中某些位置插入或修改成一些代码,从而在目标程序运行过程中获取某些程序状态并加以分析。)