Android5.0如何正确启用isLoggable(二) 理分析

时间:2023-02-22 08:03:47

转自:http://www.it165.net/pro/html/201506/43374.html

概要

在上文《Android 5.0 如何正确启用isLoggable(一)__使用详解》中分析了isLoggable的使用方法,本文主要分析isLoggable实现原理以及user版系统root后永久enable isLoggable的原理,并使用脚本自动设置isLoggable相关属性。

本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处

isLoggable工作原理

isLoggable定义在frameworks/base/core/java/android/util/Log.java中:

01./**
02.* Checks to see whether or not a log for the specified tag is loggable at the specified level.
03.*
04.*  The default level of any tag is set to INFO. This means that any level above and including
05.*  INFO will be logged. Before you make any calls to a logging method you should check to see
06.*  if your tag should be logged. You can change the default level by setting a system property:
07.*      'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>'
08.*  Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
09.*  turn off all logging for your tag. You can also create a local.prop file that with the
10.*  following in it:
11.*      'log.tag.<YOUR_LOG_TAG>=<LEVEL>'
12.*  and place that in /data/local.prop.
13.*
14.* @param tag The tag to check.
15.* @param level The level to check.
16.* @return Whether or not that this is allowed to be logged.
17.* @throws IllegalArgumentException is thrown if the tag.length() > 23.
18.*/
19.public static native boolean isLoggable(String tag, int level);

而isLoggable的native实现则是在frameworks/base/core/jni/android_util_Log.cpp中:

01.static JNINativeMethod gMethods[] = {
02./* name, signature, funcPtr */
03."isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
04."println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
05.};
06. 
07.static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
08.{
09.//... ...省略
10.jboolean result = false;
11.if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
12.char buf2[200];
13.snprintf(buf2, sizeof(buf2), "Log tag "%s" exceeds limit of %zu characters
14.",
15.chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
16.jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
17.else {
18.// 调用本地isLoggalbe方法
19.result = isLoggable(chars, level);
20.}
21.env->ReleaseStringUTFChars(tag, chars);
22.return result;
23.}

从代码中可以看到,android_util_Log_isLoggable()的返回值取决于android_util_Log.cpp中的isLoggable()方法:

01.#define LOG_NAMESPACE "log.tag."
02.static jboolean isLoggable(const char* tag, jint level) {
03.String8 key;
04.key.append(LOG_NAMESPACE);
05.key.append(tag);
06. 
07.char buf[PROPERTY_VALUE_MAX];
08.//获取属性log.tag.<Your_TAG>的值
09.if (property_get(key.string(), buf, "") <= 0) {
10.buf[0] = '';
11.}
12.int logLevel = toLevel(buf);
13.return logLevel >= 0 && level >= logLevel;
14.}

在android_util_Log.cpp中的isLoggable()首先会通过property_get()去获取log.tag.<Your_TAG>的属性值,如果没有设置该属性则将buf[0]设为空,再通过toLevel()方法获取logLevel的值,最终返回值由logLevel >= 0 && level >=logLevel决定。toLevel()方法内容如下:

01.static int toLevel(const char* value)
02.{
03.switch (value[0]) {
04.case 'V'return levels.verbose;
05.case 'D'return levels.debug;
06.case 'I'return levels.info;
07.case 'W'return levels.warn;
08.case 'E'return levels.error;
09.case 'A'return levels.assert;
10.case 'S'return -1// SUPPRESS
11.}
12.return levels.info;
13.}

levels是结构体levels_t的对象,在register_android_util_Log()方法完成赋值,而register_android_util_Log()方法则是在系统启动时通过AndroidRuntime.cpp中的REG_JNI(register_android_util_Log)完成调用,代码如下:

01.struct levels_t {
02.jint verbose;
03.jint debug;
04.jint info;
05.jint warn;
06.jint error;
07.jint assert;
08.};
09.static levels_t levels;
10. 
11.int register_android_util_Log(JNIEnv* env)
12.{
13.jclass clazz = FindClassOrDie(env, "android/util/Log");
14.//获取android.util.Log中VERBOSE/DEBUG/INFO/WARN/ERROR/ASSERT的值,并赋给levels
15.levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE""I"));
16.levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG""I"));
17.levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO""I"));
18.levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN""I"));
19.levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR""I"));
20.levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT""I"));
21.return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
22.}

在toLevel()方法中,如果字符数组value[0]匹配V/D/I/W/E/A/S则返回对应的Int值,如果value[0]为空,则返回默认值levels.info即4。再次回看android_util_Log.cpp中的isLoggable()方法:

1.static jboolean isLoggable(const char* tag, jint level) {
2.//... ...省略
3.int logLevel = toLevel(buf);
4.return logLevel >= 0 && level >= logLevel;
5.}

因为levels结构体中数据均来自android.util.Log中,最小值为2 ( VERBOSE ),最大值为7 ( ASSERT ),因此 logLevel >=0 始终为true。而level >=logLevel则会根据用户指定的level进行判断,如

1.android.util.Log.isLoggable("InCall", android.util.Log.DEBUG);

这里的level是DEBUG也就是3,相当于 level >= logLevel变为3 >= logLevel。如果没有设置属性值log.tag.InCall的值,则logLevel的默认返回为4 ( INFO),因此 3 >= 4不成立返回false,因此logLevel >=0 && level >= logLevel返回false;如果设置log.tag.InCall的值为D或者V,则logLevel返回为 3或者2,因此level >= logLevel成立,从而使得isLoggable返回为true。

Android Property System简介

在isLoggable的调用流程中涉及到属性值的读取,这里简单了解Android Property System的工作流程。如下图所示:

Android5.0如何正确启用isLoggable(二) 理分析

图 1 android property system (Pic From @rxwen)

蓝色表示独立进程,橙色表示共享内存,白色表示属性文件。属性值的获取通过property consumer完成,当系统启动时会将persistent file中的属性值加载到共享内存中,如果需要设置属性值,那么property setter会通过socket将需求提交给property service,由property service将属性值写入到共享内存中。

因为设置属性如log.tag.InCall D后会写入到共享内存中,但设备重启后会重新申请共享内存并加载属性文件,而手动设置的属性并没有写入属性文件,所以重启设备后log.tag.InCall的属性会失效。

local .prop文件加载流程

在前文《Android 5.0 如何正确启用isLoggable(一)__使用详解》中提到,如果想要重启后设置的log.tag.<Your_Tag>属性依然有效,那么需要将log.tag.InCall=D写入/data/local.prop文件中,重启设备后系统会加载该路径下的属性文件。那这一步是如何完成的呢?这就涉及到Android Property System的初始化流程。

Android Property Service是在Init进程中被初始化的,而在初始化过程中会加载指定路径下的属性文件,加载流程如下图所示:

Android5.0如何正确启用isLoggable(二) 理分析

图 2 Property files init flow

图中涉及文件路径:

/system/core/init/init.cpp

/system/core/init/init_parser.cpp

/system/core/init/builtins.cpp

/system/core/init/property_service.cpp

/system/core/rootdir/init.rc

init进程启动后首先执行main()方法,之后通过init_parse_config_file()加载init.rc文件,并对init.rc文件进行解析,最后将解析出来的service、action及其command存入链表中。在完成init.rc解析之后通过execute_one_command()方法,逐个取出链表中的command并执行。init.rc中的properties相关action如下:

1.# Load properties from /system/ + /factory after fs mount.
2.on load_all_props_action
3.load_all_props
4.start logd-reinit

load_all_props的定义在/system/core/init/keywords.h中:

01.#ifndef KEYWORD
02.//... ...
03.#define __MAKE_KEYWORD_ENUM__
04.#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
05.enum {
06.K_UNKNOWN,
07.#endif
08.//... ...
09.KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)
10.KEYWORD(load_all_props,        COMMAND, 0, do_load_all_props)
11.//... ...
12.#ifdef __MAKE_KEYWORD_ENUM__
13.KEYWORD_COUNT,
14.};
15.#undef __MAKE_KEYWORD_ENUM__
16.#undef KEYWORD
17.#endif

最终调用到/system/core/init/property_service.cpp的load_all_props()方法中:

01.void load_all_props() {
02.load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
03.load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
04.load_properties_from_file(PROP_PATH_BOOTIMAGE_BUILD, NULL);
05.load_properties_from_file(PROP_PATH_FACTORY, "ro.*");
06. 
07.load_override_properties();
08. 
09./* Read persistent properties after all default values have been loaded. */
10.load_persistent_properties();
11.}

展开load_override_properties()方法可以看到:

01.static void load_override_properties() {
02.if (ALLOW_LOCAL_PROP_OVERRIDE) {
03.char debuggable[PROP_VALUE_MAX];
04.int ret = property_get("ro.debuggable", debuggable);
05.if (ret && (strcmp(debuggable, "1") == 0)) {
06.load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL);
07.}
08.}
09.}

通过load_properties_from_file()以及load_override_properties()等方法加载属性文件,这些文件的路径定义在/bionic/libc/include/sys/_system_properties.h中:

1.#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"
2.#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"
3.#define PROP_PATH_VENDOR_BUILD     "/vendor/build.prop"
4.#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"
5.#define PROP_PATH_FACTORY          "/factory/factory.prop"

但这里需要注意,/system/build.prop、/vendor/build.prop、/factory/factory.prop这几个文件如果存在都会被加载,而/data/local.prop这个文件,只有在ro.debuggable=1时才会加载,也就是说/data/local.prop只有在userdebug/eng的情况下,才会被系统加载。

user版系统永久开启isLoggable原理

在前文《Android 5.0 如何正确启用isLoggable(一)__使用详解》中,已给出使能isLoggable的方法以及各种方法间的优劣对比。其中,如果当前设备是user版,但在获取root权限之后能够永久开启isLoggable。通过前面的分析可以知道,在user版系统中,/data/local.prop属性文件是不会被property service读取的,但/system/build.prop属性文件无论user还是userdebug/eng版本都会读取,因此直接将log.tag.<Your_Tag>追加到/system/build.prop文件中即可。

也可以使用以下脚本(Windows)设置isLoggable的属性值( 需要adbd获取root权限即能够使用adb remount ):

01.@echo off
02.echo ============= Open Hidden Logs =============
03.echo =============   version 0.2  =============
04.echo =============     20150605   =============
05.echo =============      SEVEN     =============
06. 
07.REM Update:
08.REM 1. Rename the script to OpenHiddenLogs.bat.
09.REM 2. Adaptation of user mode device.
10.REM 3. Add the instructions and steps.
11. 
12.REM Instructions:
13.REM This script is used to enable some hide logs in Android Platforms.
14.REM Android property system provides an approach to enable isLoggable(String tag, int level).
15.REM You'll find some code in Android as below:
16.REM private static final String TAG = "Telecom";
17.REM public static final boolean DEBUG = android.util.Log.isLoggable(TAG, android.util.Log.DEBUG);
18.REM if (DEBUG) {
19.REM    android.util.Log.d(TAG, getPrefix(obj) + str1 + str2);
20.REM }
21.REM If you want to enable the Log.d(), you need to type "adb shell setprop log.tag.Telecom V"
22.REM in your console, and kill the process of com.android.server.telecom, then Log.d() is enabled.
23.REM But if you reboot your device, the Log.d() is disabled, so we write the TAG to property system
24.REM to enable Log.d() forever. If you have any questions, please feel free to let me know.
25.REM Email: yihongyuelan@gmail.com
26. 
27.REM Steps:
28.REM 1. Get your device root permission.
29.REM 2. Running the OpenHideLogs.bat;
30. 
31. 
32.echo.
33.set NOROOTSTR=adbd cannot run as root
34.set ROOTSTR=adbd is already running as root
35.set BUILDTYPE=user
36. 
37.for /f "delims=" %%a in ('adb shell getprop ro.build.type'do set "build_type=%%a"
38.echo Your device is %build_type% Mode
39.echo.
40. 
41.:ISENABLED
42.for /f "delims=" %%c in ('adb shell getprop log.tag.InCall'do set "check=%%c"
43.if "%check%" == "V" (
44.echo Hidden Logs has been enabled!
45.pause
46.exit
47.else (
48.echo Hidden Logs hasn't been enabled!
49.)
50. 
51.echo.
52.for /f "delims=" %%b in ('adb root'do set "str=%%b"
53.REM echo %str%
54.set EXISTS_FLAG=false
55.echo %str%|find "%ROOTSTR%">nul&&set EXISTS_FLAG=true
56.if "%EXISTS_FLAG%"=="true" (
57.echo Checking ROOT permission PASS
58.ping -n 5 127.0.0.1 >nul
59.adb remount
60.if "%build_type%" == "%BUILDTYPE%" (
61.adb shell "echo log.tag.InCall=V                    >> /system/build.prop"
62.adb shell "echo log.tag.Telephony=V                 >> /system/build.prop"
63.adb shell "echo log.tag.Telecom=V                   >> /system/build.prop"
64.adb shell "echo log.tag.TelecomFramework=V          >> /system/build.prop"
65.adb shell "echo log.tag.Mms=V                       >> /system/build.prop"
66.adb shell "echo log.tag.MessageTemplateProvider=V   >> /system/build.prop"
67.adb shell "echo log.tag.CarrierText=V               >> /system/build.prop"
68.else (
69.adb push local.prop /data/
70.adb shell chmod 644 /data/local.prop
71.adb shell chown system:system /data/local.prop
72.)
73.adb reboot
74.adb wait-for-device
75.goto :ISENABLED
76.else (
77.echo Checking ROOT permission FAIL
78.echo Please get the root privileges for adbd and try again
79.pause
80.exit
81.)

其中local.prop内容如下:

1.log.tag.InCall=V
2.log.tag.Telephony=V
3.log.tag.Telecom=V
4.log.tag.TelecomFramework=V
5.log.tag.Mms=V
6.log.tag.MessageTemplateProvider=V
7.log.tag.CarrierText=V

脚本同步更新到github上,后续如有更新请查看github

小结

起初,看到android.util.Log.isLoggable(TAG, android.util.Log.DEBUG)代码,想当然的认为如果在userdebug的版本中isLoggable会返回true,结果查看后发现相关log并没有打印,进一步的分析后发现isLoggable背后的实现原理,同时也体会到了使用isLoggable控制log输出的灵活性。对于开发者来说,可以很好的利用isLoggable开启user版系统中的隐藏log,从而为相关问题提供更详细的log。对于isLoggable的重要知识点,总结如下:

1. isLoggable默认阈值是4 (INFO)

如果log level小于4 (INFO),即3 (DEBUG) 和2 (VERBOSE),则isLoggable返回false;

2. isLoggable可以通过设置属性值使其返回true

通过设置如log.tag.InCall D的属性,可以使得对应的isLoggable返回true,但需要注意的是,在设置属性之后需要重启相关进程,也可以通过adb shell stop & adb shell start重启Zygote及其子进程,不过该方法在完全重启设备后失效;

3. 设置属性文件可以永久使isLoggable返回true

在userdebug/eng版本中,可以将属性值log.tag.InCall=D写入/data/local.prop文件,这样isLoggable返回为true,并且在设备重启之后依然有效。如果在user版的系统中已经获取到root权限,可以向/system/build.prop中追加属性值,也可以达到重启后永久使isLoggable返回true的目的;

参考:

深入讲解Android Property机制 :本文详细分析了Android 4.4 Android Property的各个流程

Android的init过程(二):初始化语言(init.rc)解析:本文详细分析init.rc的解析过程,注意nargs在解析是会先执行nargs++

Android SystemProperties设置/取得系统属性的用法总结:本文是Android Property系统的集合贴

下载:http://www.it165.net/uploadfile/files/2015/0609/OpenHiddenLogs.zip

Android5.0如何正确启用isLoggable(二) 理分析的更多相关文章

  1. Android 5&period;0 怎样正确启用isLoggable&lpar;二&rpar;&lowbar;&lowbar;原理分析

    前置文章 <Android 5.0 怎样正确启用isLoggable(一)__使用具体解释> 概要 在上文<Android 5.0 怎样正确启用isLoggable(一)__使用具体 ...

  2. Android 5&period;0 如何正确启用isLoggable&lpar;一&rpar;&lowbar;&lowbar;使用详解

    转自:http://blog.csdn.net/yihongyuelan/article/details/46409389 isLoggable是什么 在Android源码中,我们经常可以看到如下代码 ...

  3. Android 5&period;0 怎样正确启用isLoggable&lpar;一&rpar;&lowbar;&lowbar;使用具体解释

    isLoggable是什么 在Android源代码中,我们常常能够看到例如以下代码: //packages/apps/InCallUI/src/com/android/incallui/Log.jav ...

  4. Android5&period;0&lpar;Lollipop&rpar; BLE蓝牙4&period;0&plus;浅析code(二)

    作者:Bgwan链接:https://zhuanlan.zhihu.com/p/23347612来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. Android5.0(L ...

  5. Android5&period;0新特性:RecyclerView实现上拉加载更多

    RecyclerView是Android5.0以后推出的新控件,相比于ListView可定制性更大,大有取代ListView之势.下面这篇博客主要来实现RecyclerView的上拉加载更多功能. 基 ...

  6. 解决Android5&period;0以下Dialog引起的内存泄漏

    最近项目开发中,开发人员和测试人员均反应在android5.0以下手机上LeakCanary频繁监控到内存泄漏,如下图所示,但凡用到Dialog或DialogFragment地方均出现了内存泄漏. 如 ...

  7. 实现Android5&period;0过渡动画兼容库

    Android5.0之后为我们提供了许多炫酷的界面过渡效果,其*享元素过渡也是很有亮点的一个效果,但这个效果只能在Android5.0之后使用,那今天我们就来将共享元素过渡效果兼容到Android4 ...

  8. Android5&period;0 ListView特效的简单实现

    Android5.0中对于动画可所谓是情有独钟,在设计规范中大量展现了listview的动画,其实也就是一个目的:将items动画显示出来.这个看起来很炫的效果,其实实现也蛮简单的,我下面就来用动画简 ...

  9. Springboot 打jar包分离lib,配置文件正确方式(二)

    Springboot 打jar包分离lib,配置文件正确方式(二) 背景 从<Springboot 打jar包分离lib,配置文件正确方式>中,可以达到把配置文件和依赖第三方的jar包分离 ...

随机推荐

  1. Sql&sol;Plus连接Oracle时候出现sql&ast;net not properly installed 解决办法

    在PLSQL Developer选择Tools > Preferences > options > 下的如图所示:"Oracle Home" and " ...

  2. CCCallFuncN误用导致引用计数循环引用

    昨天测试“角色被遮挡部分透明显示”功能时,发现角色死亡后,其轮廓精灵不会消失.调试发现,角色在死亡时,其引用计数retain_count居然是9.这是由引用计数混乱引起的内存泄露. 加了很多日志跟踪r ...

  3. 在MessageBox的Show方法中如何无限使用参数值?

    今天发现在show方法中不能使用花括号的方式使用多个可变参数,经过查询得出结果.在show方法中是不存在花括号的方式使用参数的.在Console.WriteLine中是存在的,如下: 那么在show方 ...

  4. ubuntu设置系统时间与网络时间同步和时区

    Linux的时间分为System Clock(系统时间)和Real Time Clock (硬件时间,简称RTC). 系统时间:指当前Linux Kernel中的时间. 硬件时间:主板上有电池供电的时 ...

  5. 程序员如何巧用Excel提高工作效率

    作为一名程序员,我们可能很少使用Excel,但是公司的一些职能部门,比如HR,财务等,使用Excel真的是太熟练了,以至于一些系统开发出来,导入和导出功能是使用最频繁的,哈哈. 其实在程序开发的过程中 ...

  6. MMORPG战斗系统随笔(一)、战斗系统流程简介

    前言 转载请标明出处http://www.cnblogs.com/zblade/ 很久没有更新博客,中间迁移过一次博客,后来一直忙于项目的开发,忙的晚上回去没时间写博客,周日又要自我调整一下,所以空闲 ...

  7. gcc-linaro-arm-linux-gnueabihf交叉编译器配置

    系统Ubuntu14.04 版本:gcc 版本 4.7.3 20130328 (prerelease) (crosstool-NG linaro-1.13.1-4.7-2013.04-20130415 ...

  8. SharePoint2013与SharePoint2016语言切换原理以及如何使用代码进行语言切换

    1.前言 在SharePoint 2010版本,在首页面直接"选择显示语言"的菜单(如下图所示),如下图 : 在sharepoint2013和sharepoint2016并非如此. ...

  9. django套用模板404报错小结

    首先,我的项目名是MyProject.每次当我运行,然后测试页面的时候,总是弹出 其实根据*上某大佬的解释大意就是在setting.py和urls.py的匹配上出了问题 此处放 ...

  10. 知物由学 &vert; 如何利用人工智能来对抗DDoS攻击?

    欢迎访问网易云社区,了解更多网易技术产品运营经验. "知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理 ...