在 Android Java 核心库 libcore 中打印 Log

Android Java 核心库中是无法直接使用 android.util.Log 的,添加后编译不通过,因为 framework 中的 Java API 依赖于 Java 核心库。

在 Android 7.0 之前,Java 核心库源码在libcore/luni/下,luni 代表 lang、util、net、io,是 Java 中最常见的包;Android 7.0 中,核心库在libcore/ojluni/下,oj 代表 OpenJDK。

本文简单介绍在核心库中打印 Log 的几种方法。

使用 System.out 和 System.err

这是很常见的方法,在 Android 中它被重定向到本地的 Log 系统,tag 分别为System.outSystem.err

缺点在于,它不能自定义 tag,而且这种方法是在 SystemServer 进程创建之后、启动之前进行重定向的,在这之前无法打印 Log。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

/**
* The main function called when started through the zygote process. This
* could be unified with main(), if the native code in nativeFinishInit()
* were rationalized with Zygote startup.<p>
*
* Current recognized args:
* <ul>
* <li> <code> [--] &lt;start class name&gt; &lt;args&gt;
* </ul>
*
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
redirectLogStreams();

commonInit();
nativeZygoteInit();
applicationInit(targetSdkVersion, argv, classLoader);
}

// ...

/**
* Redirect System.out and System.err to the Android log.
*/
public static void redirectLogStreams() {
System.out.close();
System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
System.err.close();
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}

RuntimeInit.zygoteInit()方法中,调用redirectLogStreams()方法,设置输出流为 AndroidPrintStream,当使用System.outSystem.err进行打印时,最终是调用的是 android.util.Log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// frameworks/base/core/java/com/android/internal/os/AndroidPrintStream.java

/**
* Print stream which log lines using Android's logging system.
*
* {@hide}
*/
class AndroidPrintStream extends LoggingPrintStream {

private final int priority;
private final String tag;

/**
* Constructs a new logging print stream.
*
* @param priority from {@link android.util.Log}
* @param tag to log
*/
public AndroidPrintStream(int priority, String tag) {
if (tag == null) {
throw new NullPointerException("tag");
}

this.priority = priority;
this.tag = tag;
}

protected void log(String line) {
Log.println(priority, tag, line);
}
}

使用 java.util.logging.Logger

Java 核心库中有 java.util.logging.Logger,在 Android 中它也被重定向到 Android 本地的 Log 系统。

使用方法很简单,在需要打印 Log 的源码中添加:

1
2
3
4
5
private static final Logger sLogger = Logger.getLogger("MyTag");

private static void logi(String msg) {
sLogger.info(msg);
}

使用时只需调用logi()方法即可。

这里使用的是 Level 为 INFO 的 Log。你也可以自定义 Level,核心库 java.util.logging.Logger 与 Android 本地 android.util.Log 的 Level 对应关系,可以参考 java.util.logging.Level 和 com.android.internal.logging.AndroidHandler。

Logger 的重定向位置和System.out接近,在这之前也无法打印 Log。

事实上,这里 Log 实际上是调用了 Logger 中注册的 Handler(java.util.logging.Handler,不是 android.os.Handler)。

Handler 的注册,紧随RuntimeInit.zygoteInit()方法中redirectLogStreams()后,调用commonInit()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

private static final void commonInit() {

// ...

/*
* Sets handler for java.util.logging to use Android log facilities.
* The odd "new instance-and-then-throw-away" is a mirror of how
* the "java.util.logging.config.class" system property works. We
* can't use the system property here since the logger has almost
* certainly already been initialized.
*/
LogManager.getLogManager().reset();
new AndroidConfig();

// ...

}

这里实例化了 AndroidConfig 然后丢弃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// frameworks/base/core/java/com/android/internal/logging/AndroidConfig.java

/**
* Implements the java.util.logging configuration for Android. Activates a log
* handler that writes to the Android log.
*/
public class AndroidConfig {

/**
* This looks a bit weird, but it's the way the logging config works: A
* named class is instantiated, the constructor is assumed to tweak the
* configuration, the instance itself is of no interest.
*/
public AndroidConfig() {
super();

try {
Logger rootLogger = Logger.getLogger("");
rootLogger.addHandler(new AndroidHandler());
rootLogger.setLevel(Level.INFO);

// Turn down logging in Apache libraries.
Logger.getLogger("org.apache").setLevel(Level.WARNING);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

实例化过程中注册了 AndroidHandler。

当使用 Logger 打印 Log 时,最终调用的是Logger#log(LogRecord)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// libcore/ojluni/src/main/java/java/util/logging/Logger.java

public void log(LogRecord record) {

// ...

Logger logger = this;
while (logger != null) {
for (Handler handler : logger.getHandlers()) {
handler.publish(record);
}

if (!logger.getUseParentHandlers()) {
break;
}

logger = logger.getParent();
}
}

对已经注册的 Handler 回调其publish()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// frameworks/base/core/java/com/android/internal/logging/AndroidHandler.java

@Override
public void publish(Logger source, String tag, Level level, String message) {
// TODO: avoid ducking into native 2x; we aren't saving any formatter calls
int priority = getAndroidLevel(level);
if (!Log.isLoggable(tag, priority)) {
return;
}

try {
Log.println(priority, tag, message);
} catch (RuntimeException e) {
Log.e("AndroidHandler", "Error logging message.", e);
}
}

可见最终还是调用的 android.util.Log。

如果需要在开机流程中较早的位置打印 Log,则此方法同样无效。

移植 android.util.Log

以上方法使用简单,可以满足大部分需要,但都有一些缺陷。其实也可以把 android.util.Log 核心部分移植过来,只不过有些繁琐,需要以 JNI 方式调用 liblog 中的 Log 函数。

例如,要使java.util.logging.Logger也能像android.util.Log那样方便地打印 Log,可以在 Logger 类中添加一个静态方法d(),对应android.util.Log.d()

在 Java 代码中声明 native 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// libcore/ojluni/src/main/java/java/util/logging/Logger.java

/**
* @hide
*/
public static int d(String tag, String msg) {
return println_native(0, 3, tag, msg);
}

/**
* @hide
*/
public static native int println_native(int bufID,
int priority, String tag, String msg);

注意要使用hide修饰。

然后在需要的地方调用Logger.d()方法。

编译生成 core-oj.jar,把它 push 到 /system/framework/ 中。要使此核心库生效,可能需要删除 /system/framework/arm/ 或 /system/framework/arm64/ 下的 boot.art、boot.oat(取决于手机,可都删除,删除后重启会比较慢)。

实现 JNI 层

只要移植 android_util_Log.cpp 中的android_util_Log_println_native()方法即可,创建文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// libcore/ojluni/src/main/native/java_util_logging_Logger.cpp

#include "jni.h"
#include "JNIHelp.h"

#include <cutils/log.h>

#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, (void*)(className ## _ ## functionName) }

/*******************************************************************/
/* BEGIN JNI ********* BEGIN JNI *********** BEGIN JNI ************/
/*******************************************************************/

static jint Logger_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;

if (msgObj == NULL) {
jniThrowNullPointerException(env, "println needs a message");
return -1;
}

if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID");
return -1;
}

if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);

int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);

return res;
}

static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Logger, println_native, "(IILjava/lang/String;Ljava/lang/String;)I"),
};

void register_java_util_logging_Logger(JNIEnv* env) {
jniRegisterNativeMethods(env, "java/util/logging/Logger", gMethods, NELEM(gMethods));
}

在 Regisger.cpp 中注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// libcore/ojluni/src/main/native/Register.cpp

// ...

extern void register_java_util_logging_Logger(JNIEnv* env);

// ...

jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv* env;

// ...

register_java_util_logging_Logger(env);

// ...
}

接着在 libcore/ojluni/src/main/native/openjdksub.mk 中添加相应的 C++ 文件。

编译生成 libopenjdk.so,把它 push 到 /system/lib/ 或 /system/lib64/ 下(取决于手机),然后重启即可打印出我们想要的 Log 了。

但我在java.io.File#delete()中使用这个 Log 时,发现了一个奇怪的问题:一些第三方 APP 会报 java.lang.UnsatisfiedLinkError 错误(如 Chrome 等),而 Android 系统本身,以及其它 APP,包括自己写的 demo 都没有问题,尝试网上各种方法无果,希望有人能指点迷津……

打印栈信息 Stack Trace

同样我们可以把 android.util.Log 中的getStackTraceString()方法移植过来。

需要注意的是,FastPrintWriter 也是 framework 中的,这里可以替换为 Java 核心库中的 PrintWriter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @hide
*/
public static String getStackTraceString(Throwable tr) {
if (tr == null) {
return "";
}

// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
return "";
}
t = t.getCause();
}

StringWriter sw = new StringWriter();
// PrintWriter pw = new FastPrintWriter(sw, false, 256);
PrintWriter pw = new PrintWriter(sw, false);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
}

使用getStackTraceString(new Throwable())即可得到栈信息了。