Android 5.0 怎样正确启用isLoggable(二)__原理分析
前置文章
《Android 5.0 怎样正确启用isLoggable(一)__使用具体解释》
概要
在上文《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中:
/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
*
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.<YOUR_LOG_TAG> <LEVEL>'
* Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
* turn off all logging for your tag. You can also create a local.prop file that with the
* following in it:
* 'log.tag.<YOUR_LOG_TAG>=<LEVEL>'
* and place that in /data/local.prop.
*
* @param tag The tag to check.
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23.
*/
public static native boolean isLoggable(String tag, int level);
而isLoggable的native实现则是在frameworks/base/core/jni/android_util_Log.cpp中:
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
}; static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
//... ...省略
jboolean result = false;
if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
char buf2[200];
snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %zu characters\n",
chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
jniThrowException(env, "java/lang/IllegalArgumentException", buf2);
} else {
// 调用本地isLoggalbe方法
result = isLoggable(chars, level);
}
env->ReleaseStringUTFChars(tag, chars);
return result;
}
从代码中能够看到。android_util_Log_isLoggable()的返回值取决于android_util_Log.cpp中的isLoggable()方法:
#define LOG_NAMESPACE "log.tag."
static jboolean isLoggable(const char* tag, jint level) {
String8 key;
key.append(LOG_NAMESPACE);
key.append(tag); char buf[PROPERTY_VALUE_MAX];
//获取属性log.tag.<Your_TAG>的值
if (property_get(key.string(), buf, "") <= 0) {
buf[0] = '\0';
}
int logLevel = toLevel(buf);
return logLevel >= 0 && level >= logLevel;
}
在android_util_Log.cpp中的isLoggable()首先会通过property_get()去获取log.tag.<Your_TAG>的属性值。假设没有设置该属性则将buf[0]设为空。再通过toLevel()方法获取logLevel的值,终于返回值由logLevel
>= 0 && level >=logLevel决定。toLevel()方法内容例如以下:
static int toLevel(const char* value)
{
switch (value[0]) {
case 'V': return levels.verbose;
case 'D': return levels.debug;
case 'I': return levels.info;
case 'W': return levels.warn;
case 'E': return levels.error;
case 'A': return levels.assert;
case 'S': return -1; // SUPPRESS
}
return levels.info;
}
levels是结构体levels_t的对象,在register_android_util_Log()方法完毕赋值,而register_android_util_Log()方法则是在系统启动时通过AndroidRuntime.cpp中的REG_JNI(register_android_util_Log)完毕调用。代码例如以下:
struct levels_t {
jint verbose;
jint debug;
jint info;
jint warn;
jint error;
jint assert;
};
static levels_t levels; int register_android_util_Log(JNIEnv* env)
{
jclass clazz = FindClassOrDie(env, "android/util/Log");
//获取android.util.Log中VERBOSE/DEBUG/INFO/WARN/ERROR/ASSERT的值,并赋给levels
levels.verbose = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "VERBOSE", "I"));
levels.debug = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "DEBUG", "I"));
levels.info = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "INFO", "I"));
levels.warn = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "WARN", "I"));
levels.error = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ERROR", "I"));
levels.assert = env->GetStaticIntField(clazz, GetStaticFieldIDOrDie(env, clazz, "ASSERT", "I"));
return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
}
在toLevel()方法中。假设字符数组value[0]匹配V/D/I/W/E/A/S则返回相应的Int值,假设value[0]为空,则返回默认值levels.info即4。
再次回看android_util_Log.cpp中的isLoggable()方法:
static jboolean isLoggable(const char* tag, jint level) {
//... ...省略
int logLevel = toLevel(buf);
return logLevel >= 0 && level >= logLevel;
}
由于levels结构体中数据均来自android.util.Log中,最小值为2 ( VERBOSE ),最大值为7 ( ASSERT ),因此 logLevel >=0 始终为true。
而level >=logLevel则会依据用户指定的level进行推断。如
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的工作流程。例如以下图所看到的:
图 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进程中被初始化的,而在初始化过程中会载入指定路径下的属性文件。载入流程例如以下图所看到的:
图 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例如以下:
# Load properties from /system/ + /factory after fs mount.
on load_all_props_action
load_all_props
start logd-reinit
load_all_props的定义在/system/core/init/keywords.h中:
#ifndef KEYWORD
//... ...
#define __MAKE_KEYWORD_ENUM__
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
K_UNKNOWN,
#endif
//... ...
KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props)
KEYWORD(load_all_props, COMMAND, 0, do_load_all_props)
//... ...
#ifdef __MAKE_KEYWORD_ENUM__
KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif
终于调用到/system/core/init/property_service.cpp的load_all_props()方法中:
void load_all_props() {
load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL);
load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL);
load_properties_from_file(PROP_PATH_BOOTIMAGE_BUILD, NULL);
load_properties_from_file(PROP_PATH_FACTORY, "ro.*"); load_override_properties(); /* Read persistent properties after all default values have been loaded. */
load_persistent_properties();
}
展开load_override_properties()方法能够看到:
static void load_override_properties() {
if (ALLOW_LOCAL_PROP_OVERRIDE) {
char debuggable[PROP_VALUE_MAX];
int ret = property_get("ro.debuggable", debuggable);
if (ret && (strcmp(debuggable, "1") == 0)) {
load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL);
}
}
}
通过load_properties_from_file()以及load_override_properties()等方法载入属性文件,这些文件的路径定义在/bionic/libc/include/sys/_system_properties.h中:
#define PROP_PATH_RAMDISK_DEFAULT "/default.prop"
#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_VENDOR_BUILD "/vendor/build.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#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 ):
@echo off
echo ============= Open Hidden Logs =============
echo ============= version 0.2 =============
echo ============= 20150605 =============
echo ============= SEVEN ============= REM Update:
REM 1. Rename the script to OpenHiddenLogs.bat.
REM 2. Adaptation of user mode device.
REM 3. Add the instructions and steps. REM Instructions:
REM This script is used to enable some hide logs in Android Platforms.
REM Android property system provides an approach to enable isLoggable(String tag, int level).
REM You'll find some code in Android as below:
REM private static final String TAG = "Telecom";
REM public static final boolean DEBUG = android.util.Log.isLoggable(TAG, android.util.Log.DEBUG);
REM if (DEBUG) {
REM android.util.Log.d(TAG, getPrefix(obj) + str1 + str2);
REM }
REM If you want to enable the Log.d(), you need to type "adb shell setprop log.tag.Telecom V"
REM in your console, and kill the process of com.android.server.telecom, then Log.d() is enabled.
REM But if you reboot your device, the Log.d() is disabled, so we write the TAG to property system
REM to enable Log.d() forever. If you have any questions, please feel free to let me know.
REM Email: yihongyuelan@gmail.com REM Steps:
REM 1. Get your device root permission.
REM 2. Running the OpenHideLogs.bat; echo.
set NOROOTSTR=adbd cannot run as root
set ROOTSTR=adbd is already running as root
set BUILDTYPE=user for /f "delims=" %%a in ('adb shell getprop ro.build.type') do set "build_type=%%a"
echo Your device is %build_type% Mode
echo. :ISENABLED
for /f "delims=" %%c in ('adb shell getprop log.tag.InCall') do set "check=%%c"
if "%check%" == "V" (
echo Hidden Logs has been enabled!
pause
exit
) else (
echo Hidden Logs hasn't been enabled!
) echo.
for /f "delims=" %%b in ('adb root') do set "str=%%b"
REM echo %str%
set EXISTS_FLAG=false
echo %str%|find "%ROOTSTR%">nul&&set EXISTS_FLAG=true
if "%EXISTS_FLAG%"=="true" (
echo Checking ROOT permission PASS
ping -n 5 127.0.0.1 >nul
adb remount
if "%build_type%" == "%BUILDTYPE%" (
adb shell "echo log.tag.InCall=V >> /system/build.prop"
adb shell "echo log.tag.Telephony=V >> /system/build.prop"
adb shell "echo log.tag.Telecom=V >> /system/build.prop"
adb shell "echo log.tag.TelecomFramework=V >> /system/build.prop"
adb shell "echo log.tag.Mms=V >> /system/build.prop"
adb shell "echo log.tag.MessageTemplateProvider=V >> /system/build.prop"
adb shell "echo log.tag.CarrierText=V >> /system/build.prop"
) else (
adb push local.prop /data/
adb shell chmod 644 /data/local.prop
adb shell chown system:system /data/local.prop
)
adb reboot
adb wait-for-device
goto :ISENABLED
) else (
echo Checking ROOT permission FAIL
echo Please get the root privileges for adbd and try again
pause
exit
)
当中local.prop内容例如以下:
log.tag.InCall=V
log.tag.Telephony=V
log.tag.Telecom=V
log.tag.TelecomFramework=V
log.tag.Mms=V
log.tag.MessageTemplateProvider=V
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系统的集合贴
相关资源免积分下载:戳这里
Android 5.0 怎样正确启用isLoggable(二)__原理分析的更多相关文章
- Android 5.0 如何正确启用isLoggable(一)__使用详解
转自:http://blog.csdn.net/yihongyuelan/article/details/46409389 isLoggable是什么 在Android源码中,我们经常可以看到如下代码 ...
- Android 5.0 怎样正确启用isLoggable(一)__使用具体解释
isLoggable是什么 在Android源代码中,我们常常能够看到例如以下代码: //packages/apps/InCallUI/src/com/android/incallui/Log.jav ...
- Android5.0如何正确启用isLoggable(二) 理分析
转自:http://www.it165.net/pro/html/201506/43374.html 概要 在上文<Android 5.0 如何正确启用isLoggable(一)__使用详解&g ...
- Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(二)__原理分析
前置文章: <Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释> 转载请务必注明出处:http://b ...
- Android 4.0以后正确的获取外部sd卡存储目录
刚解决这个棘手的问题 找了很久,随笔记下. 网上搜索 android 获取外部sd卡存储目录 普遍都是: 1) Environment.getExternalStorageDirectory() 这个 ...
- Android 4.4 KitKat NotificationManagerService使用具体解释与原理分析(一)__使用具体解释
概况 Android在4.3的版本号中(即API 18)增加了NotificationListenerService,依据SDK的描写叙述(AndroidDeveloper)能够知道,当系统收到新的通 ...
- Android插件化与热修复(六)-微信Tinker原理分析
Tinker热修复原理分析 热补丁技术是在用户不需要重新安装应用的情况下实现应用更新,可快速解决一些线上问题.热补丁省去了Android应用发布版本的成本,而且用户端的更新也是无感知的. Tinker ...
- 【构建Android缓存模块】(一)吐槽与原理分析
http://my.oschina.net/ryanhoo/blog/93285 摘要:在我翻译的Google官方系列教程中,Bitmap系列由浅入深地介绍了如何正确的解码Bitmap,异步线程操作以 ...
- Android 5.0新特性了解(二)----RippleEffect
1.本文介绍的是Android5.0中其中一个炫酷的效果,点击水波纹扩散效果( RippleEffect),以下介绍的实现方式都是调用Android5.0的新API,并非自定义实现,所以支持在Andr ...
随机推荐
- bzoj1705[Usaco2007 Nov]Telephone Wire 架设电话线(dp优化)
1705: [Usaco2007 Nov]Telephone Wire 架设电话线 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 441 Solved: ...
- springboot配置过滤器和拦截器
import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Http ...
- C#格式化年月日截取
//if (bm.Name == "DateYear") //年 //{ // bm.Select(); ...
- hbase无法启动,The node /hbase is not in ZooKeeper
问题详细描述如下: 2016-12-09 15:10:39,160 ERROR [org.apache.hadoop.hbase.client.ConnectionManager$HConnectio ...
- office 2010 破解
使用Rearm命令激活延迟重置Office 20101.安装Offcie 2010 安装Offcie 2010,默认30天的试用期,这里要注意,上文提供的Office 2010是零售版,所以没有序列号 ...
- ListView中动态显示隐藏HeaderView和FooterView
ListView中动态显示和隐藏Header&Footer 解决思路: 直接设置HeaderView和FooterView.setVisibility(View.GONE)无效, 布局仍然存在 ...
- C++编译错误fatal error C1004: 发现意外的文件尾
出现这种情况就是类或者结构体的定义后面没有加“;”导致的. 而且这种问题好难排查.
- 利用string 字符串拷贝
序言:对于laws的代码,完全从Matlab中转来.其中用到了字符串复制和对比的函数. C++要求: 输入字符串,根据字符串,来确定选择数组,用于下一过程 MatLab代码: (1).文件calLaw ...
- C++泛型 && Java泛型实现机制
C++泛型 C++泛型跟虚函数的运行时多态机制不同,泛型支持的静态多态,当类型信息可得的时候,利用编译期多态能够获得最大的效率和灵活性.当具体的类型信息不可得,就必须诉诸运行期多态了,即虚函数支持的 ...
- iOS https 证书链获取
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)chall ...