从点击应用图标到UI界面完全显示且用户可操作的全部过程。
特点
耗时最多,衡量标准
启动流程
Click Event -> IPC -> Process.start -> ActivityThread -> bindApplication -> LifeCycle -> ViewRootImpl
热启动
因为会从已有的应用进程启动,所以不会再创建和初始化Application,只会重新创建并初始化Activity。
特点
耗时较少
启动流程
LifeCycle -> ViewRootImpl
ViewRootImpl
ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁。每一个ViewRootImpl关联一个Window,
ViewRootImpl最终会通过它的setView方法绑定Window所对应的View,并通过其performTraversals方法对View进行布局、测量和绘制。
三、启动耗时检测 1、查看Logcat在Android Studio Logcat中过滤关键字“Displayed”,可以看到对应的冷启动耗时日志。
2、adb shell使用adb shell获取应用的启动时间
// 其中的AppstartActivity全路径可以省略前面的packageName
adb shell am start -W [packageName]/[AppstartActivity全路径]
执行后会得到三个时间:ThisTime、TotalTime和WaitTime,详情如下:
ThisTime
最后一个Activity启动耗时。
TotalTime
所有Activity启动耗时。
WaitTime
AMS启动Activity的总耗时。
一般查看得到的TotalTime,即应用的启动时间,包括创建进程 + Application初始化 + Activity初始化到界面显示的过程。
特点:
1、线下使用方便,不能带到线上。
2、非严谨、精确时间。
3、代码打点(函数插桩)
可以写一个统计耗时的工具类来记录整个过程的耗时情况。
其中需要注意的有:
在上传数据到服务器时建议根据用户ID的尾号来抽样上报。
在项目中核心基类的关键回调函数和核心方法中加入打点。
代码如下:
public class TimeMonitor {
private final String TAG = TimeMonitor.class.getSimpleName();
private int mMonitord = -1;
// 保存一个耗时统计模块的各种耗时,tag对应某一个阶段的时间
private HashMap
private long mStartTime = 0;
public TimeMonitor(int mMonitorId) {
Log.d(TAG, "init TimeMonitor id: " + mMonitorId);
this.mMonitorId = mMonitorId;
}
public int getMonitorId() {
return mMonitorId;
}
public void startMonitor() {
// 每次重新启动都把前面的数据清除,避免统计错误的数据
if (mTimetag.size() > 0) {
mTimetag.clear();
}
mStartTime = System.currentTimeMillis();
}
public void recordingTimetag(String tag) {
// 若保存过相同的tag,先清除
if (mTimetag.get(tag) != null) {
mTimetag.remove(tag);
}
long time = System.currentTimeMillis() - mStartTime;
Log.d(TAG, tag + ": " + time);
mTimetag.put(tag, time);
}
public void end(String tag, boolean writeLog) {
recordingTimetag(tag);
end(writeLog);
}
public void end(boolean writeLog) {
if (writeLog) {
//写入到本地文件
}
}
public HashMap
return mTimetag;
}
}
为了使代码更好管理,定义一个打点配置类:
public final class TimeMonitorConfig {
// 应用启动耗时
public static final int TIME_MONITOR_ID_APPLICATION_START = 1;
}
因为,耗时统计可能会在多个模块和类中需要打点,所以需要一个单例类来管理各个耗时统计的数据:
public class TimeMonitorManager {
private static TimeMonitorManager mTimeMonitorManager = null;
private HashMap
public synchronized static TimeMonitorManager getInstance() {
if (mTimeMonitorManager == null) {
mTimeMonitorManager = new TimeMonitorManager();
}
return mTimeMonitorManager;
}
public TimeMonitorManager() {
this.mTimeMonitorMap = new HashMap
}
public void resetTimeMonitor(int id) {
if (mTimeMonitorMap.get(id) != null) {
mTimeMonitorMap.remove(id);
}
getTimeMonitor(id);
}
public TimeMonitor getTimeMonitor(int id) {
TimeMonitor monitor = mTimeMonitorMap.get(id);
if (monitor == null) {
monitor = new TimeMonitor(id);
mTimeMonitorMap.put(id, monitor);
}
return monitor;
}
}
主要在以下几个方面需要打点:
应用程序的生命周期节点。
启动时需要初始化的重要方法,如数据库初始化,读取本地的一些数据。
其他耗时的一些算法。
例如,启动时在Application和第一个Activity加入打点统计:
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();
SoLoader.init(this, false);
TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimetag(“Application-onCreate”);
}
第一个Activity:
@Override
protected void onCreate(Bundle savedInstanceState) {
TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimetag(“SplashActivity-onCreate”);
super.onCreate(savedInstanceState);
initData();
TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimetag(“SplashActivity-onCreate-Over”);
}
@Override
protected void onStart() {
super.onStart();
TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).end(“SplashActivity-onStart”, false);
}
特点
精确,可带到线上,推荐使用。
注意
在上传数据到服务器时建议根据用户ID的尾号来抽样上报。
onWindowFocusChanged只是首帧时间,App启动完成的结束点应该是真实数据展示出来的时候,如列表第一条数据展示,记
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整资料开源分享
得使用getViewTreeObserver().addonPreDrawListener(),它会把任务延迟到列表显示后再执行。
AOP(Aspect Oriented Programming)打点
面向切面编程,通过预编译和运行期动态代理实现程序功能统一维护的一种技术。
作用
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时大大提高了开发效率。
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理。
2、切面(Aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象。
3、连接点(JoinPoint)
被拦截到的点(方法、字段、构造器)。
4、切入点(PointCut)
对JoinPoint进行拦截的定义。
5、通知(Advice)
拦截到JoinPoint后要执行的代码,分为前置、后置、环绕三种类型。
准备
首先,为了在Android使用AOP埋点需要引入AspectJ,在项目根目录的build.gradle下加入:
classpath ‘com.hujiang.aspectjx:gradle-android-plugin- aspectjx:2.0.0’
然后,在app目录下的build.gradle下加入:
apply plugin: ‘android-aspectjx’
implement ‘org.aspectj:aspectjrt:1.8.+’
AOP埋点实战
JoinPoint一般定位在如下位置
1、函数调用
2、获取、设置变量
3、类初始化
使用PointCut对我们指定的连接点进行拦截,通过Advice,就可以拦截到JoinPoint后要执行的代码。Advice通常有以下几种类型:
1、Before:PointCut之前执行
2、After:PointCut之后执行
3、Around:PointCut之前、之后分别执行
首先,我们举一个小栗子:
@Before(“execution(* android.app.Activity.on**(…))”)
public void onActivityCalled(JoinPoint joinPoint) throws Throwable {
…
}
在execution中的是一个匹配规则,第一个*代表匹配任意的方法返回值,后面的语法代码匹配所有Activity中on开头的方法。
处理Join Point的类型:
1、call:插入在函数体里面
2、execution:插入在函数体外面
如何统计Application中的所有方法耗时?
@Aspect
public class ApplicationAop {
@Around(“call (* com.json.chao.application.baseApplication.**(…))”)
public void getTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.i(TAG, name + " cost" + (System.currentTimeMillis() - time));
}
}
注意
当Action为Before、After时,方法入参为JoinPoint。
当Action为Around时,方法入参为ProceedingPoint。
Around和Before、After的最大区别:
ProceedingPoint不同于JoinPoint,其提供了proceed方法执行目标方法。
总结AOP特性:
1、无侵入性
2、修改方便
使用方式
1、代码中添加:Debug.startMethodTracing()、检测方法、Debug.stopMethodTracing()。(需要使用adb pull将生成的**.trace文件导出到电脑,然后使用Android Studio的Profiler加载)
2、打开Profiler -> CPU -> 点击 Record -> 点击 Stop -> 查看Profiler下方Top Down/Bottom Up 区域找出耗时的热点方法。
Profile CPU
1、Trace types
Trace Java Methods
会记录每个方法的时间、CPU信息。对运行时性能影响较大。
Sample Java Methods
相比于Trace Java Methods会记录每个方法的时间、CPU信息,它会在应用的Java代码执行期间频繁捕获应用的调用堆栈,对运行时性能的影响比较小,能够记录更大的数据区域。
Sample C/C++ Functions
需部署到Android 8.0及以上设备,内部使用simpleperf跟踪应用的native代码,也可以命令行使用simpleperf。
Trace System Calls
检查应用与系统资源的交互情况。
查看所有核心的CPU瓶。
内部采用systrace,也可以使用systrace命令。
2、Event timeline
显示应用程序中在其生命周期中转换不同状态的活动,如用户交互、屏幕旋转事件等。
3、CPU timeline
显示应用程序实时CPU使用率、其它进程实时CPU使用率、应用程序使用的线程总数。
4、Thread activity timeline
列出应用程序进程中的每个线程,并使用了不同的颜色在其时间轴上指示其活动。
绿色:线程处于活动状态或准备好使用CPU。
黄色:线程正等待IO操作。(重要)
灰色:线程正在睡眠,不消耗CPU时间。
1、Call Chart
提供函数跟踪数据的图形表示形式。
水平轴:表示调用的时间段和时间。
垂直轴:显示被调用方。
橙色:系统API。
绿色:应用自有方法
蓝色:第三方API(包括Java API)
提示:右键点击Jump to source跳转至指定函数。
2、Flame Chart
将具有相同调用方顺序的完全相同的方法收集起来。
水平轴:执行每个方法的相对时间量。
垂直轴:显示被调用方。
注意:看顶层的哪个函数占据的宽度最大(平顶),可能存在性能问题。
3、Top Down
递归调用列表,提供self、children、total时间和比率来表示被调用的函数信息。
Flame Chart是Top Down列表数据的图形化。
4、Bottom Up
展开函数会显示其调用方。
按照消耗CPU时间由多到少的顺序对函数排序。
注意点:
Wall Clock Time:程序执行时间。
Thread Time:CPU执行的时间。
特点
1、图形的形式展示执行时间、调用栈等。
2、信息全面,包含所有线程。
3、运行时开销严重,整体都会变慢,得出的结果并不真实。
作用
主要做热点分析,得到两种数据:
单次执行最耗时的方法。
执行次数最多的方法。
使用方式:
分享读者作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。
被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!
我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。
主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
方法。
执行次数最多的方法。
使用方式:
分享读者作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。
被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!
我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。
主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你有需要,尽管拿走好了。
[外链图片转存中…(img-tdPbmF8D-1640325285878)]
35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。
本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录



