在開發Android應用程式時,少不了使用Log來監控和偵錯工具的執行。在上一篇文章Android日誌系統驅動程式Logger原始碼分析中,我們分析了驅動程式Logger的原始碼,在前面的文章淺談Android系統開發中Log的使用一文,我們也簡單介紹在應用程式中使Log的方法,在這篇文章中,我們將詳細介紹Android應用程式架構層和系統運行庫存層日誌系統的原始碼,使得我們可以更好地理解Android的日誌系統的實現。
我們在Android應用程式,一般是調用應用程式架構層的Java介面(android.util.Log)來使用日誌系統,這個Java介面通過JNI方法和系統運行庫最終調用核心驅動程式Logger把Log寫到核心空間中。按照這個調用過程,我們一步步介紹Android應用程式架構層日誌系統的原始碼。學習完這個過程之後,我們可以很好地理解Android系統的架構,即應用程式層(Application)的介面是如何一步一步地調用到核心空間的。
一. 應用程式架構層日誌系統Java介面的實現。
在淺談Android系統開發中Log的使用一文中,我們曾經介紹過Android應用程式架構層日誌系統的原始碼介面。這裡,為了描述方便和文章的完整性,我們重新貼一下這部份的代碼,在frameworks/base/core/java/android/util/Log.java檔案中,實現日誌系統的Java介面:
[color=amily:Consolas,'Courier]
[java] view plaincopy
................................................
public final class Log {
................................................
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
/**
* Priority constant for the println method.
*/
public static final int ASSERT = 7;
.....................................................
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
public static int v(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
public static int d(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
public static int i(String tag, String msg) {
return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
public static int i(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
public static int w(String tag, String msg) {
return println_native(LOG_ID_MAIN, WARN, tag, msg);
}
public static int w(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
public static int w(String tag, Throwable tr) {
return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
public static int e(String tag, String msg) {
return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}
public static int e(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
..................................................................
/** @hide */ public static native int LOG_ID_MAIN = 0;
/** @hide */ public static native int LOG_ID_RADIO = 1;
/** @hide */ public static native int LOG_ID_EVENTS = 2;
/** @hide */ public static native int LOG_ID_SYSTEM = 3;
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);
}
定義了2~7一共6個日誌優先順序別ID和4個日誌緩衝區ID。回憶一下Android日誌系統驅動程式Logger原始碼分析一文,在Logger驅動程式模組中,定義了log_main、log_events和log_radio三個日誌緩衝區,分別對應三個裝置檔案/dev/log/main、/dev/log/events和/dev/log/radio。這裡的4個日誌緩衝區的前面3個ID就是對應這三個裝置檔案的檔案描述符了,在下面的章節中,我們將看到這三個檔案描述符是如何建立的。在下載下來的Android核心原始碼中,第4個日誌緩衝區LOG_ID_SYSTEM並沒有對應的裝置檔案,在這種情況下,它和LOG_ID_MAIN對應同一個緩衝區ID,在下面的章節中,我們同樣可以看到這兩個ID是如何對應到同一個裝置檔案的。
在整個Log介面中,最關鍵的地方聲明了println_native本地方法,所有的Log介面都是通過調用這個本地方法來實現Log的定入。下面我們就繼續分析這個本地方法println_native。
二. 應用程式架構層日誌系統JNI方法的實現。
在frameworks/base/core/jni/android_util_Log.cpp檔案中,實現JNI方法println_native:
[color=amily:Consolas,'Courier]
[cpp] view plaincopy
/* //device/libs/android_runtime/android_util_Log.cpp
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#define LOG_NAMESPACE "log.tag."
#define LOG_TAG "Log_println"
#include <assert.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include "jni.h"
#include "utils/misc.h"
#include "android_runtime/AndroidRuntime.h"
#define MIN(a,b) ((a<b)?a:b)
namespace android {
struct levels_t {
jint verbose;
jint debug;
jint info;
jint warn;
jint error;
jint assert;
};
static levels_t levels;
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;
}
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
#ifndef HAVE_ANDROID_OS
return false;
#else /* HAVE_ANDROID_OS */
int len;
char key[PROPERTY_KEY_MAX];
char buf[PROPERTY_VALUE_MAX];
if (tag == NULL) {
return false;
}
jboolean result = false;
const char* chars = env->GetStringUTFChars(tag, NULL);
if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
char buf2[200];
snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
// release the chars!
env->ReleaseStringUTFChars(tag, chars);
env->ThrowNew(clazz, buf2);
return false;
} else {