【Android 性能优化】应用启动优化 ( 启动白屏问题 | 应用启动时间测量 | 冷启动 | 热启动 | 应用启动时间计算源码分析 )
文章目录
一、 APP 启动白屏 / 黑屏
1 . 问题描述 : Android 应用启动时 , 尤其是大型复杂的应用 , 经常出现几秒钟的白屏或黑屏 , 黑屏或白屏取决于主界面 Activity 的主题风格 ;
2 . 解决方案 : Android 应用启动时很多大型应用都会有一个几秒的倒计时 , 并配上动态广告 , 这并不是开发者想要放上去的 , 而是为了避免上述启动白屏导致用户体很差 ;
3 . 启动优化 : 这里的应用启动白屏 / 黑屏的时间 , 是可以通过优化解决的 , 如将
秒钟的启动白屏卡顿 , 优化为毫秒级 ;
二、 APP 启动速度测量
1. 通过 Logcat 日志查看应用启动时间
通过 Logcat 日志查看应用启动时间 : 该方法只适用于 Android 4.4 版本之后的手机 ;
① 使用工具 : 使用 Android Studio 中的 Logcat 日志查看工具 ;
② 过滤选项 : 设置过滤选项为 No Filters , 这是系统 ActivityTaskManager 打印的 , 不属于应用日志信息 ;
③ 关键字 : 使用 Display 关键字过滤 ;
④ 截取到的日志信息如下 :
2020-06-20 08:44:09.821 1266-1305/? I/ActivityTaskManager: Displayed kim.hsl.recyclerview/.MainActivity: +334ms
2020-06-20 08:44:18.457 1266-1305/? I/ActivityTaskManager: Displayed
com.google.android.permissioncontroller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity: +315ms
2020-06-20 08:44:22.308 1266-1305/? I/ActivityTaskManager: Displayed kim.hsl.cckfa/.MainActivity: +311ms
⑤ 具体操作选项参考下面的截图 :
2. 通过 adb 命令查看界面启动时间
通过 adb 命令查看界面启动时间 :
① 命令格式 : adb shell am start -W 包名/完整 Activity 类名 ;
② 本次命令 : adb shell am start -W kim.hsl.rtmp/kim.hsl.rtmp.MainActivity , 其中 kim.hsl.rtmp 是包名 , kim.hsl.rtmp.MainActivity 是完整的类名 ;
Microsoft Windows [版本 10.0.18362.900]
(c) 2019 Microsoft Corporation。保留所有权利。
C:\Users\octop>adb shell am start -W kim.hsl.rtmp/kim.hsl.rtmp.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]
cmp=kim.hsl.rtmp/.MainActivity }
Status: ok
LaunchState: COLD
Activity: kim.hsl.rtmp/.MainActivity
TotalTime: 374
WaitTime: 376
Complete
C:\Users\octop>
③ 命名截图 : 下面的截图作为参考 ;
④ Activity 界面源码 : 上述启动类类对应 RTMP_Pusher 应用中的主界面 ;
package kim.hsl.rtmp;
public class MainActivity extends AppCompatActivity {
//...
}
三、 APP 冷启动与热启动
热启动的速度要远高于冷启动 ;
1 . 冷启动 : 应用后台没有该应用 , 该应用所有资源都要重新加载 , 分配新的进程 , 初始化 Application , 初始化 Activity 界面 ; 下图中的 LanuchState 中显示了启动的模式 , 下图中显示的是冷启动 ;
2 . 热启动 : 按下 Home 键 , 应用进入后台 , 再次启动 , 应用由后台转到前台 , 这种启动称为热启动 ; 下图中的 LanuchState 中显示了启动的模式 , 下图中显示的是热启动 ;
四、 APP 启动时间计算
1 . 启动 Activity 命令 : adb shell am start -W 包名/完整 Activity 类名 ;
2 . 日志打印内容 :
C:\Users\octop>adb shell am start -W kim.hsl.rtmp/kim.hsl.rtmp.MainActivity
Starting: Intent { act=android.intent.action.MAIN
cat=[android.intent.category.LAUNCHER] cmp=kim.hsl.rtmp/.MainActivity }
Status: ok
LaunchState: COLD
Activity: kim.hsl.rtmp/.MainActivity
TotalTime: 431
WaitTime: 433
Complete
C:\Users\octop>
3 . am 命令及源码 : 在上述命令中可以启动 Android 应用 Activity 界面 , 其中 am 命令是由 Am.java 程序编译出来的 , 其路径是 \frameworks\base\cmds\am\src\com\android\commands\am\Am.java ;
4 . 分析 Am.java 可执行文件 :
① 执行启动 Activity 相关逻辑 : 启动前后都记录了时间 , 整个启动过程就是通过这两个时间计算出来的 ;
IActivityManager.WaitResult result = null;
int res;
// 记录开始时间
final long startTime = SystemClock.uptimeMillis();
if (mWaitOption) {
// 启动 Activity
result = mAm.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
res = result.result;
} else {
res = mAm.startActivityAsUser(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
}
// 记录结束时间
final long endTime = SystemClock.uptimeMillis();
② 打印启动时间相关源码 : IActivityManager.WaitResult 对象中封装了 thisTime , totalTime 等时间信息 ; 这些时间都是在启动过程中计算的 ;
if (mWaitOption && launched) {
if (result == null) {
result = new IActivityManager.WaitResult();
result.who = intent.getComponent();
}
System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));
if (result.who != null) {
System.out.println("Activity: " + result.who.flattenToShortString());
}
if (result.thisTime >= 0) {
System.out.println("ThisTime: " + result.thisTime);
}
if (result.totalTime >= 0) {
System.out.println("TotalTime: " + result.totalTime);
}
System.out.println("WaitTime: " + (endTime-startTime));
System.out.println("Complete");
}
5 . 分析启动时间计算源码 :
① 计算启动时间源码 : APP 启动时间计算在 \frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java 类中计算 ;
② 在 windowsDrawn 方法中调用 reportLaunchTimeLocked 计算时间 :
public void windowsDrawn() {
synchronized(service) {
if (displayStartTime != 0) {
// 该方法是入口方法
reportLaunchTimeLocked(SystemClock.uptimeMillis());
}
mStackSupervisor.sendWaitingVisibleReportLocked(this);
startTime = 0;
finishLaunchTickingLocked();
if (task != null) {
task.hasBeenVisible = true;
}
}
}
③ 启动时间计算 : 可以看到在 reportLaunchTimeLocked 方法中 , 计算了 thisTime 和 totalTime 的时间值 ;
private void reportLaunchTimeLocked(final long curTime) {
final ActivityStack stack = task.stack;
// 这里计算了 APP 启动时间
final long thisTime = curTime - displayStartTime;
final long totalTime = stack.mLaunchStartTime != 0
? (curTime - stack.mLaunchStartTime) : thisTime;
if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0);
EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,
userId, System.identityHashCode(this), shortComponentName,
thisTime, totalTime);
StringBuilder sb = service.mStringBuilder;
sb.setLength(0);
sb.append("Displayed ");
sb.append(shortComponentName);
sb.append(": ");
TimeUtils.formatDuration(thisTime, sb);
if (thisTime != totalTime) {
sb.append(" (total ");
TimeUtils.formatDuration(totalTime, sb);
sb.append(")");
}
Log.i(ActivityManagerService.TAG, sb.toString());
}
mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
if (totalTime > 0) {
//service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
}
displayStartTime = 0;
stack.mLaunchStartTime = 0;
}
6 . APP 启动时间总结 :
① 开始时间 : 点击 APP 图标 , 该时间就是 Am.java 中记录的 startTime 开始时间 ;
// 记录开始时间
final long startTime = SystemClock.uptimeMillis();
② 系统分配内存时间 : 之后 Android 系统会给 APP 分配内存 , 这段时间是无法进行优化的 , 用户无法控制 , 占用时间由系统控制 ;
③ 画面绘制完毕调用方法 : 当调用 ActivityRecord.java 中的 windowsDrawn 方法时 , 画面绘制完毕 , 此时会调用 reportLaunchTimeLocked 方法 , 传入当前时间 SystemClock.uptimeMillis() 作为参数 ;
if (displayStartTime != 0) {
// 该方法是入口方法
reportLaunchTimeLocked(SystemClock.uptimeMillis());
}
④ 画面绘制时间 : 在 reportLaunchTimeLocked 方法中 , 计算 thisTime 时间 , curTime 是传入的 SystemClock.uptimeMillis() 参数 , 即当前时间 , displayStartTime 是画面开始绘制的时间 , thisTime 就是画面绘制时间 ;
final ActivityStack stack = task.stack;
final long thisTime = curTime - displayStartTime;
⑤ 总时间计算 : 这里计算总时间时 , 需要根据当前是冷启动还是热启动进行不同的计算 , 如果是冷启动 , (curTime - stack.mLaunchStartTime) 时间是总时间 , thisTime 是热启动的启动总时间 ;
final long totalTime = stack.mLaunchStartTime != 0
? (curTime - stack.mLaunchStartTime) : thisTime;
相关文章
- Android 平台的Python——基础篇(一)
- 74款android开机动画,修改Android系统开机动画
- strictmode android,Android 应用性能优化-StrictMode(严格模式)
- android 混淆规则作用,Android代码混淆详解
- android 读取本地数据库db文件(Android sqlite)
- Android SDK下载安装及环境配置[通俗易懂]
- 工厂方法模式及在Android源码中的应用
- android app 退出功能,Android 完美退出 App (Exit)
- Android进程间通信之一:Binder机制学习
- android线程间通信的几种方法_Android进程间和线程间通信方式
- 货拉拉 Android H5离线包原理与实践
- 【Android 应用开发】Android游戏音效实现
- 【Android 应用开发】UI绘制流程 ( 生命周期机制 | 布局加载机制 | UI 绘制流程 | 布局测量 | 布局摆放 | 组件绘制 | 瀑布流布局案例 )
- 【Android RTMP】x264 图像数据编码 ( Camera 图像数据采集 | NV21 图像数据传到 Native 处理 | JNI 传输字节数组 | 局部引用变量处理 | 线程互斥 )
- 【Android 电量优化】电量优化特性 ( Doze 低电耗模式 | Standby 应用待机模式 | 白名单设置 | 白名单添加系统设置界面 | 指定应用的白名单添加界面 | 测试应用 )
- 【Android 高性能音频】OboeTester 音频性能测试应用 ( Oboe 输出测试参数 | API 选择 | 音频输出设备选择 | 采样率 | 通道 | 采样格式 | 播放偏好 )
- 【Android 异步操作】Handler 机制 ( MessageQueue 空闲任务 IdleHandler 机制 )
- 【Android 逆向】Android 权限 ( adb 降权相关的属性 | ro.secure 属性 | ro.debuggable 属性 | service.adb.root 属性 )
- 【Android 逆向】加壳的 Android 应用启动流程 | 使用反射替换 LoadedApk 中的类加载器流程
- 【Google Play】从 Android 应用中跳转到 Google Play 中 ( 跳转代码示例 | Google Play 页面的链接格式 | Google Play 免安装体验 )
- 【错误记录】Android 应用安装后出现两个启动图标 ( 排查应用及依赖库中配置了 android.intent.category.LAUNCHER 的 Activity )
- 【Android 屏幕适配】屏幕适配通用解决方案 ② ( 自定义组件解决方案 | 需要解决的问题 : 设计稿坐标数据转为屏幕真实坐标数据 | 实现步骤 )
- 史上最大设计变化:Android 12让你眼前一亮
- Android 4.4 开发代号确定:KitKat
- 多图:开发者文档揭示 Android Auto 第三方应用 UI 规范
- 解析Android开发优化之:软引用与弱引用的应用
- Android自定义桌面功能代码实现
- Android短信接收监听、自动回复短信操作例子
- Android入门简单实例