2022年6月2日星期四

Android logcat

Log等级

Android log等级在android/log.h中定义如下:

typedef enum android_LogPriority {
  /** For internal use only. */
ANDROID_LOG_UNKNOWN = 0,
  /** The default priority, for internal use only. */
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
  /** Verbose logging. Should typically be disabled for a release apk. */
ANDROID_LOG_VERBOSE,
  /** Debug logging. Should typically be disabled for a release apk. */
ANDROID_LOG_DEBUG,
  /** Informational logging. Should typically be disabled for a release apk. */
ANDROID_LOG_INFO,
  /** Warning logging. For use with recoverable failures. */
ANDROID_LOG_WARN,
  /** Error logging. For use with unrecoverable failures. */
ANDROID_LOG_ERROR,
  /** Fatal logging. For use when aborting. */
ANDROID_LOG_FATAL,
  /** For internal use only. */
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;

缓冲池

Android的日志系统包含了一系列回环缓冲池,这些缓冲池由系统进程logd来维护。

其中main缓冲池存储大部分应用日志;system缓冲池存储Android系统的日志;crash存储崩溃日志,等等。

typedef enum log_id {
LOG_ID_MIN = 0,

  /** The main log buffer. This is the only log buffer available to apps. */
LOG_ID_MAIN = 0,
  /** The radio log buffer. */
LOG_ID_RADIO = 1,
  /** The event log buffer. */
LOG_ID_EVENTS = 2,
  /** The system log buffer. */
LOG_ID_SYSTEM = 3,
  /** The crash log buffer. */
LOG_ID_CRASH = 4,
  /** The statistics log buffer. */
LOG_ID_STATS = 5,
  /** The security log buffer. */
LOG_ID_SECURITY = 6,
  /** The kernel log buffer. */
LOG_ID_KERNEL = 7,

LOG_ID_MAX,

  /** Let the logging function choose the best log target. */
LOG_ID_DEFAULT = 0x7FFFFFFF
log_id_t;

接口

系统提供liblog共享库和<android/log.h>头文件来访问日志系统。

所有的日志事件最终都会调用到__android_log_write方法;这个方法使用__android_log_logd_logger方法通过socket将日志写入logd

Android API level 30开始可以使用__android_log_set_logger来更改日志方法。

过滤

Android日志有四层过滤:

  1. 编译时过滤:譬如使用proguard移除掉所有Log.d的调用
  2. 系统属性过滤:liblog通过读取一些系统属性,来决定发送给logd的log的最小敏感级别,譬如tagMyApp的log,会检查以下属性(log级别用单字符字母VDIWE代表,S代表关闭所有log)
    *. log.tag.MyApp
    *. persist.log.tag.MyApp
    *. log.tag
    *. persist.log.tag
  3. 应用过滤:如果上述属性都没有设置,则liblog使用通过__android_log_set_minimum_priority设置的最小优先级,默认值为INFO
  4. adb logcat可以过滤logd中的log。

logcat命令

logcat命令行语法如下:

[adb] logcat [<option>] ... [<filter-spec>] ...

可以通过adb直接运行logcat

$ adb logcat

也可以通过adb shell创建到设备的连接,然后执行logcat

$ adb shell
# logcat

logcat --help

使用logcat --help命令可用查看所有选项

$ adb logcat --help
Usage: logcat [options] [filterspecs]

General options:
  -b, --buffer=<buffer>       Request alternate ring buffer(s):
...

也可以查看选项的详细信息:

$ adb logcat -v --help

过滤日志输出

通过tag:priority ...方式设置过滤表达式。

tag标签可以使用*以匹配所有标签

优先级priority有:

  1. V: Verbose
  2. D: Debug
  3. I: Info
  4. W: Warning
  5. E: Error
  6. F: Fatal
  7. S: Silent

priority可以忽略,默认D

譬如:

adb logcat ActivityManager:I MyApp:D *:S

只显示标签为ActivityManager的优先级为I及以上的日志及标签为MyApp的优先级为D以上的日志,其它日志都忽略

还可以使用ANDROID_LOG_TAGS环境变量来设置过滤表达式

$ adb shell
# export ANDROID_LOG_TAGS="ActivityManager:I MyApp:D *:S"
# logcat

输出格式

使用logcat -v选项可以设置日志输出格式。

-v, --format=<format>

有以下几种格式:

  1. brief:显示优先级、标签、PID及消息
  2. long:显示日志所有元数据信息,消息之间使用空行分隔
  3. process:只显示优先级、进程PID及消息
  4. raw:只显示原始日志消息
  5. tag:只显示优先级、标签与消息
  6. thread:显示优先级、PID、TID及消息
  7. threadtime:默认格式,显示日期、时间、优先级、标签、PID、TID及消息
  8. time:显示日期、时间、优先级、标签、PID及消息

默认格式为threadtime

譬如:

$ adb logcat -v brief

修饰符

可以使用如下修饰符(或其组合)来修改输出样式:

  1. color:不同优先级的日志使用不同的颜色进行显示
  2. descriptive:Show log buffer event descriptions. This modifier affects event log buffer messages only, and has no effect on the other non-binary buffers. The event descriptions come from the event-log-tags database.
  3. epoch:时间显示为从1970/1/1开始的秒数
  4. monotonic:时间显示为最近启动时间开始的秒数
  5. printable: Ensure that any binary logging content is escaped.
  6. uid:显示发送日志的进程的UIDAndroid ID
  7. usec:时间精度显示到微秒
  8. UTC:显示UTC时间
  9. year:显示时间加入年份
  10. zone:显示时间加入时区

譬如:

$ adb logcat -v color,brief

不同优先级日志显示不同颜色,同时显示格式为brief

切换日志缓冲区

logcat默认显示mainsystemcrash缓冲区内容,可以使用-b切换缓冲区:

-b, --buffer=<buffer>

可用<buffer>如下:

  1. radio:显示radio/telephony相关日志
  2. events:显示解析的系统事件消息
  3. main:显示主日志
  4. system:显示系统日志
  5. crash:显示崩溃日志
  6. all:显示所有日志缓冲区
  7. default:显示默认的mainsystemcrash缓冲区日志

示例:

adb logcat -b radio
adb logcat -b main -b radio -b events
adb logcat -b main,radio,events

选项(options)

其它选项如下:

选项描述
-c, --clear清理选定的缓冲区并退出。默认的缓冲区集合为mainsystemcrash。可以使用-b all -c`清理所有缓冲区
-e <expr>, --regex=<expr>只打印匹配正则表达式<expr>的日志
-m <count>, --max-count=<count>打印<count>数量的日志并退出。可以配合--regex使用,也可以单独使用
--print配合--regex--max-count使用
-ddump日志到屏幕上并退出
-f <filename>输入日志到<filename>,默认为stdout
-g, --buffer-size打印指定log缓冲区的大小
-n <count>需要配合-r选项使用,设置环绕日志数量为<count>,默认为4
-r <kbytes>需要配合-f选项使用,设置环绕输出文件大小,以k字节为单位,默认为16
-s等同于*:S正则表达式,设置所有tag的优先级为静默,通常在添加内容的过滤表达式前,配合过滤表达式达到只显示指定内容的功能
-D,--dividers每个log缓冲区之间显示分隔线
-c清理所有log并退出
-t <count>只打印最近的<count>行日志,包含了-d的功能
-t '只打印从指定时间<time>开始的日志,包含-d的功能,例:adb logcat -t '01-26 20:52:41.820'
-T <count>类似-t <count>,不包含-d功能
-T '类似-t '<time>',不包含-d功能
-L, --lastdump上次重启之前的log
-B, --binary二进制形式输出log
-S, --statistics输出中包含统计信息
-G <size>设置log回绕缓冲区大小,可以在<size>尾部添加KM
-p, --prune打印当前黑名单白名单
-P '<list> ...'
--prune '<list> ...' -P '<white_and_black_list>'设置黑/白名单。白名单用<white>黑名单用~<black>。其中<white><black>可以用UIDUID/PID/PID来表示。
--pid=<pid> ...只打印指定进程的日志
--wrapSleep for 2 hours or when the buffer is about to wrap whichever comes first. Improves efficiency of polling by providing an about-to-wrap wakeup.

参看

Logcat command-line tool

Write and View Logs with Logcat

https://developer.android.com/ndk/reference/group/logging

2022年3月25日星期五

Android项目使用预编译库

 

APP使用预编译库

默认路径

预编译库中的so放到src/main/jniLibs/<ABI>下,AndroidStudio打包时会自动将预编译的so文件打包进生成的产物APK/AAR

.
├── build.gradle
└── src
    ├── main
    │   ├── AndroidManifest.xml
    │   ├── java
    │   └── jniLibs
    │       ├── arm64-v8a
    │       │   └── libprebuilt.so
    │       └── armeabi-v7a
    │           └── libprebuilt.so
    

自定义路径

预编译库中的so放置到任意路径:

.
├── build.gradle
├── libs
│   ├── arm64-v8a
│   │   └── libprebuilt.so
│   └── armeabi-v7a
│       └── libprebuilt.so
└── src
    ├── main
    │   ├── AndroidManifest.xml
    │   └── java
    

配置如下:

android {
    sourceSets {
main {
jniLibs.srcDirs 'libs'
}
}
}

NDK引用预编译库

NDK项目中也可以引用预编译库。

这里注意不要将预编译库放到src/main/jniLibs/<ABI>下,或者使用android.sourceSets.main.jniLibs.srcDirs来配置预编译库路径;而是在Android.mdCMakeLists.txt中配置

ndk-build

TODO

cmake

下面是一个简单项目:

.
├── mylib
│   ├── build.gradle
│   └── src
│       └── main
│           └── cpp
│               ├── CMakeLists.txt
│               ├── include
│               │   └── my_lib.h
│               └── my_lib.cpp
└── prebuilt
    ├── include
    │   └── prebuilt.h
    └── lib
        ├── arm64-v8a
        │   └── libprebuilt.so
        └── armeabi-v7a
            └── libprebuilt.so

配置如下:

cmake_minimum_required(VERSION 3.10.2)

# prebuilt lib root dir
set(PREBUILT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../prebuilt)
set(PREBUILT_LIB_DIR ${PREBUILT_DIR}/lib/${ANDROID_ABI})

# libprebuilt.so
add_library(prebuilt STATIC IMPORTED)
set_target_properties(prebuilt PROPERTIES IMPORTED_LOCATION
        ${PREBUILT_LIB_DIR}/libprebuilt.so)

# my_lib project
project("my_lib")

# add source files to my_lib project
add_library(
        my_lib
        SHARED
        my_lib.cpp)

# hide symobols
set_target_properties(my_lib PROPERTIES CXX_VISIBILITY_PRESET hidden)

# find android log library
find_library(
        log-lib
        log)

# include dirs
target_include_directories(
        my_lib
        PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include # include dir of my_lib
        ${PREBUILT_DIR}/include # include dir of prebuilt library
)

# link prebuilt library & android log library to my_lib
target_link_libraries(
        my_lib

        prebuilt

        ${log-lib})

如上,在my_lib项目中引入了libprebuilt.so库,在打包时libprebuilt.so会自动打包到生成的APK/AAR

NDK禁止打包预编译库

譬如我们创建的Android library项目引用了三方库libprebuilt.so,但是我们希望在生成的产物中不包含libprebuilt.so文件,单独提供该文件或由接入方自己导入这个库文件,可以在打包时排除这个文件:

plugins {
id 'com.android.library'
}

android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags ''
}
}
}

externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.18.1'
}
}
packagingOptions {
jniLibs.excludes += '**/libprebuilt.so' // 打包时排除libprebuilt.so文件
}
}

参考

JniLibsPackagingOptions

Android logcat

Log等级 Android log 等级在 android/log.h 中定义如下: typedef   enum   android_LogPriority {    /** For internal use only. */ ANDROID_LOG_UNKNOWN =...