目录
实战:在Camera Java Framework中添加Trace(已添加注释)
Android使用一个集中式系统来记录所有的日志,应用开发者也可以编写自定义日志,也可以定义日志过滤器
日志分析是开发的核心阶段之一,开发人员经常会遇到这样那样的问题需要借助日志分析来解决。Bug日志有助于在开发阶段识别Android应用中的Bug。一旦应用发布到市场上,开发者(或者支持工程师)也要通过分析bug日志来解决问题。
-
Android日志系统
-
不同类型的日志
在Android生态系统中有不同类型的日志:
-
主日志:主日志用于应用程序
-
Android系统日志:system用于低级系统消息和调试
-
事件日志:events用于系统事件信息
-
Radio日志:radio用于电话相关信息
-
Android日志系统
Android日志系统包括一个内核驱动程序和用于存储Android日志消息的内核缓冲区,用于创建日志条目和访问日志消息的C、c++和Java类,一个用于查看日志消息的独立程序(logcat),以及查看和过滤来自主机的日志消息的能力(通过Android Studio或ddms)。
-
四个不同的日志缓冲区
Linux内核中有四个不同的日志缓冲区,它们为系统的不同部分提供日志记录。所有的的缓冲区都在设备节点“在/dev/log”下,有
-
“/dev/log/main”, ——主日志
-
“/dev/log/radio”,——事件日志Radio日志
-
“/dev/log/event”,——事件日志
-
“/dev/log/system”,——系统日志。
日志中的每条消息都包含一个标记,表明消息来自系统或应用程序的哪个部分:
-
一个时间戳(日志发生时间),
-
日志级别(日志优先级)
-
日志内容(错误、异常或信息的详细描述等)。
-
日志接口介绍
主日志使用android.util.Log打印,主要被应用使用。
系统日志使用android.util.Slog打印。许多android框架层的模块使用该工具打印日志,这样可以与应用日志区分开,避免其他日志干扰。
事件日志使用android.util.EventLog打印,输出二进制格式的日志。日志入口包含二进制tag code,后面跟二进制参数。二进制tag code存储在 /system/etc/event-log-tags。
-
日志格式
时间戳 进程号 线程号 优先级 标签 日志内容
-
V - 详细(最低优先级)*
-
D - 调试*
-
I - 信息*
-
W - 警告*
-
E - 错误*
-
F - 致命的*
-
S - 沉默(最高优先级,不打印任何东西)
-
log的使用
在Android开发中,我们可以通过Log类来输出log信息。Log类提供了以下几个方法来输出不同等级的log:
Log.v():输出Verbose等级的log。
Log.d():输出Debug等级的log。
Log.i():输出Info等级的log。
Log.w():输出Warning等级的log。
Log.e():输出Error等级的log。
Log.wtf():输出Assert等级的log。
演示如何在Android应用程序中输出log:
package com.example.myapp;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity {
private static final String TAG = "MyApp";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate() called");
int a = 10;
int b = 20;
int sum = a + b;
Log.i(TAG, "sum = " + sum);
}
}
在上面的代码中,我们在onCreate()方法中分别使用Log.d()和Log.i()输出了Debug和Info等级的log信息。
-
Log状态图
简单的log输出状态图,使用mermaid语法表示:
-
日志文件位置
Android的日志(包括崩溃的日志)存储在几个目录中,它不是标准化的(可能被特定手机厂家修改定制)。这里放了一些常见的。
-
/data/anr: Dalvik在anr上写堆栈跟踪,即“应用程序不响应”或“强制关闭”。
-
/data/dontpanic: 包含一些崩溃日志,包括跟踪。
-
/data/kernelpanics: —存储内核恐慌相关的日志。
-
/data/tombstones: 可以保存多个tombstone_nn文件(nn是一个从0到10的数字,在10之后再次重复它)。
-
日志命令行工具
从设备或模拟器捕获Android日志可通过一些命令行工具来进行。在实践中,还有一些日志捕获应用程序或工具用于捕获用户设备上的日志,并将其呈现给开发人员进行分析。
-
adb logcat
: 显示当前android系统的所有类型日志 -
adb logcat -v threadtime
: 显示当前android系统的所有类型日志, 包含日期和时间 -
adb logcat -v threadtime > logfile.txt
: 将日志存储(覆盖重定向)在logfile.txt文件中
-
过滤模式
您可以在adb命令中使用过滤器来过滤日志。
-
adb logcat *:E
: 获取所有错误和致命文件 -
adb logcat | grep -i "error"
: 过滤出日志中所有携带error的行 -
adb logcat MyTag:* *:S
: 过滤所有标签为“MyTag”的行 -
adb logcat -b events gsm_service_state_change *:S
: 获取所有GSM状态变化 -
adb logcat -b radio
: 获取所有radio事件
(以上未完全包含,只列举常见的部分)
-
日志文件分析
在开发过程中我们并不总是能够实时查看日志,比如测试报告bug、者线上发生问题、或自动化测试时,会附加一份存储到文件中的android日志。
虽然在日志格式上命令行输出的日志和文件中的日志没有什么不同,但是在分析手段上,还是有明显差异。分析日志文件需要借助文本工具。
这些文本工具需要具备执行普通和正则表达式过滤的能力。
满足条件的文本工具有很多,如IDE、notepad++、UltroEdit、Tilipa日志工具、sublime等
如果需要打开超大日志文件(100MB以上),或者在非windows操作系统上打开,推荐使用Tilipa日志工具。
-
Android 分析EventLog
-
查看EventLog
使用命令:
logcat -b events
-
logtags生成
-
开机event log分析
1.获取开机时的event log:
logcat -b events | grep boot_progress
输出如下:
I boot_progress_start: 20726
I boot_progress_preload_start: 23255
I boot_progress_preload_end: 26806
I boot_progress_system_run: 27135
I boot_progress_pms_start: 27946
I boot_progress_pms_system_scan_start: 28447
I boot_progress_pms_data_scan_start: 29647
I boot_progress_pms_scan_end: 29958
I boot_progress_pms_ready: 30910
I boot_progress_ams_ready: 35811
I boot_progress_enable_screen: 38542
boot_progress_start表示Linux kernel启动到Zygote进程启动的时间,包含从kernel启动到Init启动Zygote的时间,接下来我们就以boot_progress_start为例来分析这条日志是如何输出的。
2.boot_progress_start是哪里输出的?
在/system/core/logcat/event.logtags找到了boot_progress_start的定义
# The entries in this file map a sparse set of log tag numbers to tag names.
# This is installed on the device, in /system/etc, and parsed by logcat.
...
# Device boot timings. We include monotonic clock values because the
# intrinsic event log times are wall-clock.
#
# Runtime starts:
3000 boot_progress_start (time|2|3)
从注释可以看出这个logtags文件是用于日志标记号映射到标记名,并且安装在/system/etc下。在/system/etc下查找到event-log-tags文件,我们看下event-log-tags是如何生成的。
在build/soong/java/gen.go下:
pctx.SourcePathVariable("logtagsCmd", "build/tools/java-event-log-tags.py")
pctx.SourcePathVariable("mergeLogtagsCmd", "build/tools/merge-event-log-tags.py")
java-event-log-tags.py:生成一个java类,其中包含给定输入文件中每个事件日志标记的常量。
merge-event-log-tags.py:将零个或多个事件日志标记文件合并在一起,生成一个单独的输出文件,去掉注释。检查没有标签号冲突,如果有就失败。
我们知道boot_progress对应的id是3000,去framework下查找
grep -rn 3000
base/core/jni/AndroidRuntime.cpp:994: const int LOG_BOOT_PROGRESS_START = 3000;
看下代码
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
...
const int LOG_BOOT_PROGRESS_START = 3000;
LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
}
同样,我们在
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java找到了
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis());
-
应用启动event log分析
1.获取应用启动event log
logcat -b events | grep am_
输出:
I am_create_task: [0,364]
I am_create_activity: [0,165832734,364,com.android.settings/.Settings,android.intent.action.MAIN,NULL,NULL,270532608]
I am_focused_stack: [0,1,0,startedActivity setFocusedActivity]
I am_focused_activity: [0,com.android.settings/.Settings,startedActivity]
I am_pause_activity: [0,102587883,com.android.launcher3/.Launcher]
I am_on_paused_called: [0,com.android.launcher3.Launcher,handlePauseActivity]
I am_restart_activity: [0,165832734,364,com.android.settings/.Settings]
I am_on_resume_called: [0,com.android.settings.Settings,LAUNCH_ACTIVITY]
I am_activity_launch_time: [0,165832734,com.android.settings/.Settings,825,825]
I am_stop_activity: [0,102587883,com.android.launcher3/.Launcher]
I am_on_stop_called: [0,com.android.launcher3.Launcher,handleStopActivity]
I am_pss : [5733,10023,com.android.launcher3,12664832,9179136,0]
接下来我们以am_create_activity为例,分析log的输出,以及log的含义。
2.am_create_activity在哪里输出的
前面讲到java-event-log-tags.py会将logtags文件转换成java文件
-
1.先在/out/target/common/目录下查找am_create_activity
find . -name EventLogTags.java | xargs grep am_create_activity 找到了: ./obj/JAVA_LIBRARIES/services.core_intermediates/src/java/com/android/server/am/EventLogTags.java: /** 30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5) */ 看下这个文件: /** 30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5) */ public static final int AM_CREATE_ACTIVITY = 30005; public static void writeAmCreateActivity(int user, int token, int taskId, String componentName, String action, String mimeType, String uri, int flag s) { android.util.EventLog.writeEvent(AM_CREATE_ACTIVITY, user, token, taskId, componentName, action, mimeType, uri, flags); }
-
2.接下来去framework下查找哪里使用了writeAmCreateActivity
grep -rn writeAmCreateActivity
没有查找到,那就查AM_CREATE_ACTIVITY
grep -rn AM_CREATE_ACTIVITY
输出:
services/core/java/com/android/server/am/ActivityStarter.java:1242: EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.task);
看下代码:
ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.task);
再来看下logStartActivity方法:
static final void logStartActivity(int tag, ActivityRecord r,
TaskRecord task) {
final Uri data = r.intent.getData();
final String strData = data != null ? data.toSafeString() : null;
EventLog.writeEvent(tag,
r.userId, System.identityHashCode(r), task.taskId,
r.shortComponentName, r.intent.getAction(),
r.intent.getType(), strData, r.intent.getFlags());
}
3.输出log表示什么
由上面的输出行代码,再来看看
[0,165832734,364,com.android.settings/.Settings,android.intent.action.MAIN,NULL,NULL,270532608]
这就明白了
userId=0
identityHashCode=0,165832734
taskId=364
shortComponentName=com.android.settings/.Settings
上面的方式是通过查找代码来看出log含义。其实如果只是看log含义不需要这么麻烦,在/system/etc/event-log-tags可以看到
30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
这里的数字是什么意思呢?在framework下查找:
find . -name *.logtags | xargs grep am_create_activity
输出:
./services/core/java/com/android/server/am/EventLogTags.logtags:30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
这个文件开头就告诉我们去system/core/logcat/event.logtags看文件格式的描述
前面在分析开启启动log时,我们就看过这个文件。此时,我们再来看看。这个文件告诉我们描述格式是:
(<name>|data type[|data unit])
-
data type表示:
1: int 2: long 3: string 4: list 5: float
-
data unit表示:
1: Number of objects
2: Number of bytes # int/long类型数据的默认值是2(字节)。
3: Number of milliseconds
4: Number of allocations
5: Id
6: Percent
-
实例分析 (User|1|5)表示:User是一个int类型的id (Component Name|3)表示是一个string
-
dumpsys
dumpsys 是一种在 Android 设备上运行的工具,可提供有关系统服务的信息。可以使用 Android 调试桥 (adb) 从命令行调用 dumpsys,获取在连接的设备上运行的所有系统服务的诊断输出。
Android使用ServiceManager服务进程来管理系统所有的服务,在系统启动时,每个服务必须注册到ServiceManager进程中,那如何查看系统运行了那些服务呢?ServiceManager提供了listServices接口来罗列出系统注册的所有服务, dumpsys工具比较简单,就是调用ServiceManager服务的listServices来查询系统注册的所有服务,并且通过checkService接口来获取服务的Binder远程代理对象,使用每个服务的dump()函数来打印该服务的相关信息。
dumpsys源码在 frameworks\native\cmds\dumpsys\dumpsys.cpp
-
获取dumpsys支持的命令
执行
$ dumpsys
在输出的信息的开头, 列出了系统中正在运行的service,但是这样输出太多了, 因此, 可以使用如下命令来进行过滤
$ dumpsys | grep “DUMP OF SERVICE”
-
支持dumpsys的service
-
acount 显示设备上所有用户的信息
-
Cpuinfo 可以显示每个进程在内核空间和用户空间的cpu占用率
-
Activity 用于查看 activity组件的信息,详细信息使用如下命令获取帮助
$ dumpsys activity -h
appwidget android窗口小部件的信息
audio android音频组件信息
battery android电池设备信息
connectivity android网络连接状态及进程请求网络信息
content 查看android content provider相关的信息
device_policy android设备政策
dropbox 系统崩溃的信息
input_method Android输入法信息
iphonesubinfo 手机制式及设备id
location gps等位置信息
meminfo android进程实际物理内存占用情况
mount 加载的存储设备信息
network_management android网络流量管理信息
notification android通知栏广告信息
package 通过l、perm、perf、p等参数dump出package信息,还能够直接跟包名来dump出该包的具体信息
power 电源管理器的一些状态信息
sensorservice android传感器的一些状态信息
statusbar 显示状态栏相关的一些信息
telephony.registry 电话服务相关的信息
uimode ui mode service的状态信息
usagestats 各个app使用情况统计
wallpaper 壁纸信息
wifi wifi连接及状态信息
-
dumpsys power
-
这部分内容来自dumpsys power 字段含义介绍-CSDN博客,为防止连接内容丢失,以下为摘抄。
在分析app测功耗以及亮灭屏的过程中,经常会执行dumpsys power来获取PowerManagerService的状态
下面就关于dumpsys power的打印的部分字段的含义简要说明
POWER MANAGER (dumpsys power)
Power Manager State:
Settings power_manager_constants:
no_cached_wake_locks=true
mDirty=0x0 (mDirty PMS中的核心参数,用来表示当前发生改变的事件类型)
mWakefulness=Awake 当前所希望的屏幕状态,Awake 表示唤醒状态
mWakefulnessChanging=false mWakefulnessChanging 表示上一个参数mWakefulness是否发生变化,如果变化这个参数会被改成true,然后会调用DIsplayManager来切换屏幕状态
mIsPowered=true 是否充电,true表示在充电中
mPlugType=2 充电类型
mBatteryLevel=82 当前电量
mBatteryLevelWhenDreamStarted=0
mDockState=0
mStayOn=false 屏幕是否需要长亮
mProximityPositive=false 通话时候的距离感应传感器(打电话的时候,手机贴到脸上就灭屏)
mBootCompleted=true 是否启动完毕
mSystemReady=true 系统是否准备好
mHalAutoSuspendModeEnabled=false HAL层是否自己进入Suspend模式
mHalInteractiveModeEnabled=true 向HAL层设置的当前用户活动状态,true表示屏幕打开,用户在使用,false表示用户没有使用
mWakeLockSummary=0x25 记录所有锁的类型
mNotifyLongScheduled=+27s645ms
mNotifyLongDispatched=-51s118ms
mNotifyLongNextCheck=(none)
mUserActivitySummary=0x1 用户活动事件标志
mRequestWaitForNegativeProximity=false
mSandmanScheduled=false
mSandmanSummoned=false
mBatteryLevelLow=false 电池是否是低电状态
mLightDeviceIdleMode=false
mDeviceIdleMode=false
mDeviceIdleWhitelist=[1000, 2000, 10014] 设备在IDLE状态下的app白名单
mDeviceIdleTempWhitelist=[1000]
mLastWakeTime=577519 (32358 ms ago) 重要,表示上次屏幕点亮的事件 32358 ms ago 表示距离当前时间,屏幕在32秒之前点亮的
mLastSleepTime=498525 (111352 ms ago) 上次屏幕灭屏时间,111352 ms ago 表示在111秒之之前息屏的
mLastUserActivityTime=577519 (32358 ms ago) 最后一次用户的活动事件时间,点击,充电,等
mLastUserActivityTimeNoChangeLights=64807 (545070 ms ago) 用户最后一次不影响屏幕亮度的事件
mLastInteractivePowerHintTime=577519 (32358 ms ago)
mLastScreenBrightnessBoostTime=0 (609877 ms ago) 最近一次自动调节屏幕亮度的时间(就是手机由黑暗的地方进入明亮的地方,手机屏幕会自动调整到最大亮度)
mScreenBrightnessBoostInProgress=false 是否正在进行屏幕亮度调整
mDisplayReady=true 显示器是否准备OK
mHoldingWakeLockSuspendBlocker=true 是否持有阻止CPU休眠的锁
mHoldingDisplaySuspendBlocker=true 是否持有阻止显示器息屏的锁
Settings and Configuration: 下面这部分是系统的原始配置参数
mDecoupleHalAutoSuspendModeFromDisplayConfig=false
mDecoupleHalInteractiveModeFromDisplayConfig=false
mWakeUpWhenPluggedOrUnpluggedConfig=true
mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=false
mTheaterModeEnabled=false
mSuspendWhenScreenOffDueToProximityConfig=true
mDreamsSupportedConfig=true
mDreamsEnabledByDefaultConfig=true
mDreamsActivatedOnSleepByDefaultConfig=false
mDreamsActivatedOnDockByDefaultConfig=true
mDreamsEnabledOnBatteryConfig=false
mDreamsBatteryLevelMinimumWhenPoweredConfig=-1
mDreamsBatteryLevelMinimumWhenNotPoweredConfig=15
mDreamsBatteryLevelDrainCutoffConfig=5
mDreamsEnabledSetting=true
mDreamsActivateOnSleepSetting=false
mDreamsActivateOnDockSetting=true
mDozeAfterScreenOff=false
mMinimumScreenOffTimeoutConfig=10000
mMaximumScreenDimDurationConfig=7000
mMaximumScreenDimRatioConfig=0.20000005
mScreenOffTimeoutSetting=60000
mSleepTimeoutSetting=-1
mMaximumScreenOffTimeoutFromDeviceAdmin=9223372036854775807 (enforced=false)
mStayOnWhilePluggedInSetting=0
mScreenBrightnessSetting=0
mScreenBrightnessModeSetting=0
mScreenBrightnessOverrideFromWindowManager=-1
mUserActivityTimeoutOverrideFromWindowManager=-1
mUserInactiveOverrideFromWindowManager=false
mDozeScreenStateOverrideFromDreamManager=0
mDrawWakeLockOverrideFromSidekick=false
mDozeScreenBrightnessOverrideFromDreamManager=-1
mScreenBrightnessSettingMinimum=10
mScreenBrightnessSettingMaximum=255
mScreenBrightnessSettingDefault=102
mDoubleTapWakeEnabled=false
mIsVrModeEnabled=false
mForegroundProfile=0
Sleep timeout: -1 ms
Screen off timeout: 60000 ms 用户设置的手机灭屏实现(从最后一个用户事件到屏幕彻底熄灭,这里是60秒)
Screen dim duration: 7000 ms 屏幕熄灭之前变暗所持续的事件
应用的状态信息
UID states (changing=false changed=false):
1000 表示应用的UDI,ACTIVE 表示应用是否是活动状态,count表示此应用申请的锁的数量 state 表示应用的运行等级
应用的运行等级可以百度搜索 PROCESS_STATE_NONEXISTENT 来看看具体都有哪些已经各个状态下的运行状态。
UID 1000: ACTIVE count=0 state=0
UID 1001: ACTIVE count=0 state=0
UID 1027: ACTIVE count=0 state=0
UID 1068: ACTIVE count=0 state=0
UID u0a8: INACTIVE count=0 state=18
UID u0a13: INACTIVE count=0 state=18
UID u0a14: INACTIVE count=0 state=9
UID u0a19: INACTIVE count=0 state=18
UID u0a21: ACTIVE count=0 state=18
UID u0a24: ACTIVE count=0 state=0
UID u0a25: ACTIVE count=0 state=4
UID u0a34: INACTIVE count=0 state=18
UID u0a38: INACTIVE count=0 state=18
UID u0a40: INACTIVE count=0 state=18
UID u0a43: INACTIVE count=0 state=18
UID u0a44: ACTIVE count=0 state=5
UID u0a53: ACTIVE count=0 state=13
UID u0a54: ACTIVE count=2 state=2
Looper state:
待处理的handl消息
Looper (PowerManagerService, tid 34) {6ca4820}
Message 0: { when=+20s642ms what=1 target=com.android.server.power.PowerManagerService$PowerManagerHandler } Message 1: { when=+27s645ms what=4 target=com.android.server.power.PowerManagerService$PowerManagerHandler }
(Total messages: 2, polling=true, quitting=false)
Wake Locks: size=2 应用申请的锁的数量
锁的类型 锁的tag 当前锁所持有的时间 申请锁的app的UID以及PID
PARTIAL_WAKE_LOCK 'My_Tag_PARTIAL_WAKE_LOCK' ACQ=-24s518ms (uid=10054 pid=15301)
SCREEN_DIM_WAKE_LOCK 'My_Tag_SCREEN_DIM_WAKE_LOCK' ACQ=-24s512ms (uid=10054 pid=15301)
Suspend Blockers: size=4
PowerManagerService.WakeLocks: ref count=1
PowerManagerService.Display: ref count=1
PowerManagerService.Broadcasts: ref count=0
PowerManagerService.WirelessChargerDetector: ref count=0
Display Power: state=ON
Battery saving stats:
Battery Saver is currently: OFF
Last OFF time: 2020-10-30 09:36:09.545 -6h5m54s767ms 距离上次充满电的时间
Times enabled: 0
Drain stats:
Battery saver OFF ON
NonDoze NonIntr: 358m 29mAh( 1%) 4.9mAh/h 0m 0mAh( 0%) 0.0mAh/h
Intr: 1m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h
Deep NonIntr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h
Intr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h
Light NonIntr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h
Intr: 0m 0mAh( 0%) 0.0mAh/h 0m 0mAh( 0%) 0.0mAh/h
Battery saver policy (*NOTE* they only apply when battery saver is ON):
Settings: battery_saver_constants
value: null
Settings: (overlay)
value:
mAccessibilityEnabled=false
vibration_disabled:config=true
vibration_disabled:effective=true
animation_disabled=false
fullbackup_deferred=true
keyvaluebackup_deferred=true
firewall_disabled=false
datasaver_disabled=true
launch_boost_disabled=true
adjust_brightness_disabled=true
adjust_brightness_factor=0.5
gps_mode=2
force_all_apps_standby=true
force_background_check=true
optional_sensors_disabled=true
aod_disabled=true
send_tron_log=false
Interactive File values:
Noninteractive File values:
Battery saver state machine:
Enabled=false
mLastChangedIntReason=0
mLastChangedStrReason=null
mBootCompleted=true
mSettingsLoaded=true
mBatteryStatusSet=true
mBatterySaverSnoozing=false
mIsPowered=true
mBatteryLevel=82
mIsBatteryLevelLow=false
mSettingBatterySaverEnabled=false
mSettingBatterySaverEnabledSticky=false
mSettingBatterySaverTriggerThreshold=0
Profile power states: size=0
Wireless Charger Detector State:
mGravitySensor={Sensor name="Gravity Sensor", vendor="AOSP", version=3, type=9, maxRange=19.6133, resolution=0.0012, power=0.002, minDelay=10000}
mPoweredWirelessly=false
mAtRest=false
mRestX=0.0, mRestY=0.0, mRestZ=0.0
mDetectionInProgress=false
mDetectionStartTime=0 (never)
mMustUpdateRestPosition=false
mTotalSamples=0
mMovingSamples=0
mFirstSampleX=0.0, mFirstSampleY=0.0, mFirstSampleZ=0.0
mLastSampleX=0.0, mLastSampleY=0.0, mLastSampleZ=0.0
-
TRACE
参考如下分析应用性能 | Android Studio | Android Developers
-
app/framework 加trace
-
Java Trace类说明
-
实战:在Camera App中添加Trace
-
实战:在Camera Java Framework中添加Trace
-
Java Trace类说明
android.os.Trace类 | 描述 |
beginSection(String sectionName) | 在一个函数中打印Trace开始标记,会显示在某个线程中 |
endSection() | 在一个函数中打印Trace结束标记,会显示在某个线程中 |
beginAsyncSection(String methodName, int cookie) | 打印异步Trace开始标记,cookie用来区分相同methodName不同的异步Trace,会独立成一行Trace显示 |
endAsyncSection(String methodName, int cookie) | 打印异步Trace结束标记,cookie用来区分相同methodName不同的异步Trace,会独立成一行Trace显示 |
setCounter(String counterName, long counterValue) | 以给定计数器的值打印Trace |
isEnabled() | 判断是否Trace打开了,建议打印Trace前都判断下避免创建一些无用的临时对象 |
-
实战:在Camera App中添加Trace(添加了注释)
public class GeekCamera2Trace { // 常量定义,用于标识不同的跟踪事件 public static final String OPEN_CAMERA = "GC2_openCamera"; public static final String CREATE_CAPTURE_SESSION = "GC2_createCaptureSession"; public static final String FRAME_NUMBER = "GC2_FrameNumber"; /** * 开始一个异步跟踪区段。 * @param methodName 跟踪区段的名称 * @param cookie 唯一标识异步操作的cookie */ public static void beginAsyncSection(String methodName, int cookie) { // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) { Trace.beginAsyncSection(methodName, cookie); } } /** * 结束一个异步跟踪区段。 * @param methodName 跟踪区段的名称 * @param cookie 唯一标识异步操作的cookie */ public static void endAsyncSection(String methodName, int cookie) { // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) { Trace.endAsyncSection(methodName, cookie); } } /** * 开始一个同步跟踪区段。 * @param sectionName 跟踪区段的名称 */ public static void beginSection(String sectionName) { // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) { Trace.beginSection(sectionName); } } /** * 结束一个同步跟踪区段。 */ public static void endSection() { // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) { Trace.endSection(); } } /** * 设置一个计数器的值。 * @param counterName 计数器的名称 * @param counterValue 计数器的值 */ public static void setCounter(String counterName, long counterValue) { // 仅在系统版本大于等于Android Q(API 29)且跟踪功能启用时执行 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && Trace.isEnabled()) { Trace.setCounter(counterName, counterValue); } } }
解释
-
常量定义:
-
OPEN_CAMERA
,CREATE_CAPTURE_SESSION
,FRAME_NUMBER
: 这些是用于标识不同跟踪事件的常量字符串。
-
-
beginAsyncSection:
-
参数
methodName
表示异步跟踪区段的名称。 -
参数
cookie
是一个唯一标识异步操作的整数。 -
只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用
Trace.beginAsyncSection
方法。
-
-
endAsyncSection:
-
参数
methodName
表示异步跟踪区段的名称。 -
参数
cookie
是一个唯一标识异步操作的整数。 -
只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用
Trace.endAsyncSection
方法。
-
-
beginSection:
-
参数
sectionName
表示同步跟踪区段的名称。 -
只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用
Trace.beginSection
方法。
-
-
endSection:
-
没有参数。
-
只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用
Trace.endSection
方法。
-
-
setCounter:
-
参数
counterName
表示计数器的名称。 -
参数
counterValue
表示计数器的值。 -
只有在系统版本大于等于Android Q(API 29)且跟踪功能启用时,才会调用
Trace.setCounter
方法。
-
-
实战:在Camera Java Framework中添加Trace(已添加注释)
/**
* 打开指定的相机设备,为特定UID进行跟踪。
*
* @param cameraId 相机设备的ID
* @param callback 相机设备状态的回调
* @param executor 执行回调的Executor
* @param clientUid 客户端的UID
* @param oomScoreOffset OOM(Out-Of-Memory)分数偏移量
* @throws CameraAccessException 如果相机访问失败
*/
public void openCameraForUid(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
int clientUid, int oomScoreOffset) throws CameraAccessException {
// 记录日志,输出相机ID
Log.i(TAG, "v.deepinout.com openCameraForUid cameraId:" + cameraId);
// 开始一个名为 "GC2_FWK_openCameraForUid" 的同步跟踪区段
Trace.beginSection("GC2_FWK_openCameraForUid");
// 检查cameraId是否为空,如果为空则抛出IllegalArgumentException
if (cameraId == null) {
throw new IllegalArgumentException("cameraId was null");
}
// 检查callback是否为空,如果为空则抛出IllegalArgumentException
else if (callback == null) {
throw new IllegalArgumentException("callback was null");
}
// 检查相机服务是否被禁用,如果禁用则抛出IllegalArgumentException
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No cameras available on device");
}
// 异步打开相机设备
openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);
// 结束同步跟踪区段
Trace.endSection();
}
解释
-
方法签名:
-
@NonNull String cameraId
: 相机设备的ID,不能为空。 -
@NonNull CameraDevice.StateCallback callback
: 回调接口,用于通知相机设备的状态变化,不能为空。 -
@NonNull Executor executor
: 用于执行回调的Executor,不能为空。 -
int clientUid
: 客户端的UID,用于标识哪个应用在使用相机。 -
int oomScoreOffset
: OOM(Out-Of-Memory)分数偏移量,用于调整进程的内存优先级。 -
throws CameraAccessException
: 可能抛出的异常,表示相机访问失败。
-
-
日志记录:
-
Log.i(TAG, "v.deepinout.com openCameraForUid cameraId:" + cameraId);
:记录日志,输出相机ID。
-
-
开始跟踪区段:
-
Trace.beginSection("GC2_FWK_openCameraForUid");
:开始一个名为 "GC2_FWK_openCameraForUid" 的同步跟踪区段。
-
-
参数检查:
-
检查
cameraId
是否为空,如果为空则抛出IllegalArgumentException
。 -
检查
callback
是否为空,如果为空则抛出IllegalArgumentException
。
-
-
相机服务检查:
-
检查
CameraManagerGlobal.sCameraServiceDisabled
是否为true
,如果为true
则抛出IllegalArgumentException
。
-
-
异步打开相机设备:
-
openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset);
:异步打开相机设备。
-
-
结束跟踪区段:
-
Trace.endSection();
:结束同步跟踪区段。
-
-
systrace实现
systrace的框架
1、systrace调用atrace抓取目标机的trace数据;
2、systrace把trace数据和’prefix.html’、‘suffix.html’、'systrace_trace_viewer.html’合成一个’trace.html’文件;
3、使用chrome浏览器打开’trace.html’就可以非常方便的以图形化的形式来查看和分析trace数据。背后是Trace-Viewer的脚本在运行;
内核态和用户态的存储trace数据的实现:
1、内核trace信息,通过trace event记录到ftrace的buffer中;
2、用户态(app/java framework/native)是通过使用Trace类来记录trace信息的,也是记录到内核ftrace buffer当中的,是通过"/sys/kernel/debug/tracing/trace_marker"接口记录的。
-
systrace的使用
在定位Android性能问题的时候,我们经常会用到systrace工具。配置到adb,连接上目标手机,在主机侧使用类似命令启动systrace:
ASOP_ROOT/external/chromium-trace/catapult/systrace/bin$ ./systrace -o trace.html -t 10 gfx view wm am dalvik input sched freq idle
Starting tracing (10 seconds)
Tracing completed. Collecting output...
Outputting Systrace results...
Tracing complete, writing results
Wrote trace HTML file: file:///ASOP_ROOT/external/chromium-trace/catapult/systrace/bin/trace.html
systrace命令在ASOP源码包的ASOP_ROOT/external/chromium-trace/catapult/systrace/bin路径下。上述命令的参数:
-
‘-t 10’,指定了抓取trace的时长为10s;
-
‘-o trace.html’,指定了trace的输出文件;
-
‘gfx view wm am dalvik input sched freq idle’,指定了需要抓取的事件;
在命令启动以后,我们就可以在目标机上进行滑动、启动app等一系列操作,10s内的这些操作都会被记录下来最后dump进trace.html文件。我们可以通过google的chrome浏览器来查看、分析trace.html:‘google-chrome trace.html’。
可以通过’-l’选项来查看目标机支持的systrace事件全集:
$ ./systrace -l
gfx - Graphics
input - Input
view - View System
webview - WebView
wm - Window Manager
am - Activity Manager
sm - Sync Manager
audio - Audio
video - Video
camera - Camera
hal - Hardware Modules
app - Application
res - Resource Loading
dalvik - Dalvik VM
rs - RenderScript
bionic - Bionic C Library
power - Power Management
pm - Package Manager
ss - System Server
database - Database
network - Network
adb - ADB
pdx - PDX services
sched - CPU Scheduling
irq - IRQ Events
freq - CPU Frequency
idle - CPU Idle
disk - Disk I/O
workq - Kernel Workqueues
memreclaim - Kernel Memory Reclaim
regulators - Voltage and Current Regulators
binder_driver - Binder Kernel driver
binder_lock - Binder global lock trace
pagecache - Page cache
NOTE: more categories may be available with adb root
还可以通过’–help’选项来查看systrace命令的详细选项:
$ ./systrace --help
Usage: systrace [options] [category1 [category2 ...]]
Example: systrace -b 32768 -t 15 gfx input view sched freq
Options:
-h, --help show this help message and exit
-o FILE write trace output to FILE
-j, --json write a JSON file
--link-assets (deprecated)
--asset-dir=ASSET_DIR
(deprecated)
-e DEVICE_SERIAL_NUMBER, --serial=DEVICE_SERIAL_NUMBER
adb device serial number
--timeout=TIMEOUT timeout for start and stop tracing (seconds)
--collection-timeout=COLLECTION_TIMEOUT
timeout for data collection (seconds)
-t N, --time=N trace for N seconds
--target=TARGET choose tracing target (android or linux)
-b N, --buf-size=N use a trace buffer size of N KB
-l, --list-categories
list the available categories and exit
Atrace options:
--atrace-categories=ATRACE_CATEGORIES
Select atrace categories with a comma-delimited list,
e.g. --atrace-categories=cat1,cat2,cat3
-k KFUNCS, --ktrace=KFUNCS
specify a comma-separated list of kernel functions to
trace
--no-compress Tell the device not to send the trace data in
compressed form.
-a APP_NAME, --app=APP_NAME
enable application-level tracing for comma-separated
list of app cmdlines
--from-file=FROM_FILE
read the trace from a file (compressed) rather than
running a live trace
BattOr trace options:
--battor-categories=BATTOR_CATEGORIES
Select battor categories with a comma-delimited list,
e.g. --battor-categories=cat1,cat2,cat3
--serial-map=SERIAL_MAP
File containing pregenerated map of phone serial
numbers to BattOr serial numbers.
--battor-path=BATTOR_PATH
specify a BattOr path to use
--battor Use the BattOr tracing agent.
Ftrace options:
--ftrace-categories=FTRACE_CATEGORIES
Select ftrace categories with a comma-delimited list,
e.g. --ftrace-categories=cat1,cat2,cat3
WALT trace options:
--walt Use the WALT tracing agent. WALT is a device for
measuring latency of physical sensors on phones and
computers. See https://github.com/google/walt
-
主机systrace命令的实现(python)
systrace实质上是一个python文件,整个python相关包在ASOP_ROOT/external/chromium-trace/catapult/路径下,以此路径为根(root=ASOP_ROOT/external/chromium-trace/catapult),我们来分析它的实现过程:
./systrace/bin/systrace:
import os
import sys
# (1) 将'./systrace/'路径加入到python的搜索路径 #
_SYSTRACE_DIR = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.path.pardir))
sys.path.insert(0, _SYSTRACE_DIR)
# (2) 这样就能找到'./systrace/systrace/run_systrace.py'模块并导入 #
from systrace import run_systrace
# (3) 调用run_systrace.py模块中的main()函数 #
if __name__ == '__main__':
sys.exit(run_systrace.main())
./systrace/systrace/run_systrace.py:
# (3.1) main()函数继续调用main_impl()函数 #
def main():
main_impl(sys.argv)
↓
def main_impl(arguments):
# Parse the command line options.
# (3.1.1) 将用户输入的参数解析为options和categories #
options, categories = parse_options(arguments)
# Override --atrace-categories and --ftrace-categories flags if command-line
# categories are provided.
# (3.1.2) 如果解析出了trace事件categories #
# 根据options.target指定的平台'android'/'linux',来给options中的选项赋值 #
# 平台是使用sytrace命令的'--target=TARGET'选项来指定的,如果没有指定默认值是'android' #
if categories:
if options.target == 'android':
options.atrace_categories = categories
elif options.target == 'linux':
options.ftrace_categories = categories
else:
raise RuntimeError('Categories are only valid for atrace/ftrace. Target '
'platform must be either Android or Linux.')
↓
./systrace/systrace/run_systrace.py:
# Include atrace categories by default in Systrace.
# (3.1.3) 如果平台是'android',且没有指定trace事件,给出默认的trace事件 #
if options.target == 'android' and not options.atrace_categories:
options.atrace_categories = atrace_agent.DEFAULT_CATEGORIES
# (3.1.4) 如果平台是'android',且不是从文件中读取数据,那么就是从实际的目标机中读取数据了 #
if options.target == 'android' and not options.from_file:
# 初始化adb #
initialize_devil()
# 如果没有指定目标机的serialnumber,尝试读取 #
if not options.device_serial_number:
devices = [a.GetDeviceSerial() for a in adb_wrapper.AdbWrapper.Devices()]
if len(devices) == 0:
raise RuntimeError('No ADB devices connected.')
elif len(devices) >= 2:
raise RuntimeError('Multiple devices connected, serial number required')
options.device_serial_number = devices[0]
# If list_categories is selected, just print the list of categories.
# In this case, use of the tracing controller is not necessary.
# (3.1.5) 如果当前是'systrace -l'命令,列出目标机支持的所有trace事件后直接返回 #
if options.list_categories:
if options.target == 'android':
# 调用'systrace/systrace/tracing_agents/atrace_agent.py'文件中的list_categories()函数 #
# 最后调用的是`adb shell atrace --list_categories'命令 #
atrace_agent.list_categories(options)
elif options.target == 'linux':
ftrace_agent.list_categories(options)
return
# Set up the systrace runner and start tracing.
# (3.1.6) 如果是普通的trace命令,根据'systrace/systrace/systrace_runner.py'模块中的SystraceRunner类来创建对象 #
controller = systrace_runner.SystraceRunner(
os.path.dirname(os.path.abspath(__file__)), options)
# (3.1.6.1) 开始tracing #
controller.StartTracing()
# Wait for the given number of seconds or until the user presses enter.
# pylint: disable=superfluous-parens
# (need the parens so no syntax error if trying to load with Python 3)
if options.from_file is not None:
print('Reading results from file.')
elif options.trace_time:
print('Starting tracing (%d seconds)' % options.trace_time)
time.sleep(options.trace_time)
else:
raw_input('Starting tracing (stop with enter)')
# Stop tracing and collect the output.
print('Tracing completed. Collecting output...')
# (3.1.6.2) 停止tracing #
controller.StopTracing()
print('Outputting Systrace results...')
# (3.1.6.3) 输出tracing结果到文件中 #
controller.OutputSystraceResults(write_json=options.write_json)
我们可以看到trace过的重点最后落在SystraceRunner对象的创建,以及.StartTracing()/.StopTracing()/.OutputSystraceResults()几个方法上。