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.out
和System.err
。
缺点在于,它不能自定义 tag,而且这种方法是在 SystemServer 进程创建之后、启动之前进行重定向的,在这之前无法打印 Log。
// 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> [--] <start class name> <args>
* </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.out
和System.err
进行打印时,最终是调用的是 android.util.Log:
// 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 的源码中添加:
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()
方法:
// 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 然后丢弃。
// 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)
方法:
// 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()
方法:
// 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 方法
// 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()
方法即可,创建文件:
// 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 中注册:
// 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。
/**
* @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())
即可得到栈信息了。