欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android源码解析之应用程序框架层和系统运行库层日志系统分析

程序员文章站 2022-12-04 22:18:28
Android源码解析之应用程序框架层和系统运行库层日志系统分析。 在开发Android应用程序时,少不了使用Log来监控和调试程序的执行。我们在Android应用程序,一般是调...

Android源码解析之应用程序框架层和系统运行库层日志系统分析。

在开发Android应用程序时,少不了使用Log来监控和调试程序的执行。我们在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接口:

[java]view plaincopy

................................................

publicfinalclassLog{

................................................

/**

*Priorityconstantfortheprintlnmethod;useLog.v.

*/

publicstaticfinalintVERBOSE=2;

/**

*Priorityconstantfortheprintlnmethod;useLog.d.

*/

publicstaticfinalintDEBUG=3;

/**

*Priorityconstantfortheprintlnmethod;useLog.i.

*/

publicstaticfinalintINFO=4;

/**

*Priorityconstantfortheprintlnmethod;useLog.w.

*/

publicstaticfinalintWARN=5;

/**

*Priorityconstantfortheprintlnmethod;useLog.e.

*/

publicstaticfinalintERROR=6;

/**

*Priorityconstantfortheprintlnmethod.

*/

publicstaticfinalintASSERT=7;

.....................................................

publicstaticintv(Stringtag,Stringmsg){

returnprintln_native(LOG_ID_MAIN,VERBOSE,tag,msg);

}

publicstaticintv(Stringtag,Stringmsg,Throwabletr){

returnprintln_native(LOG_ID_MAIN,VERBOSE,tag,msg+'\n'+getStackTraceString(tr));

}

publicstaticintd(Stringtag,Stringmsg){

returnprintln_native(LOG_ID_MAIN,DEBUG,tag,msg);

}

publicstaticintd(Stringtag,Stringmsg,Throwabletr){

returnprintln_native(LOG_ID_MAIN,DEBUG,tag,msg+'\n'+getStackTraceString(tr));

}

publicstaticinti(Stringtag,Stringmsg){

returnprintln_native(LOG_ID_MAIN,INFO,tag,msg);

}

publicstaticinti(Stringtag,Stringmsg,Throwabletr){

returnprintln_native(LOG_ID_MAIN,INFO,tag,msg+'\n'+getStackTraceString(tr));

}

publicstaticintw(Stringtag,Stringmsg){

returnprintln_native(LOG_ID_MAIN,WARN,tag,msg);

}

publicstaticintw(Stringtag,Stringmsg,Throwabletr){

returnprintln_native(LOG_ID_MAIN,WARN,tag,msg+'\n'+getStackTraceString(tr));

}

publicstaticintw(Stringtag,Throwabletr){

returnprintln_native(LOG_ID_MAIN,WARN,tag,getStackTraceString(tr));

}

publicstaticinte(Stringtag,Stringmsg){

returnprintln_native(LOG_ID_MAIN,ERROR,tag,msg);

}

publicstaticinte(Stringtag,Stringmsg,Throwabletr){

returnprintln_native(LOG_ID_MAIN,ERROR,tag,msg+'\n'+getStackTraceString(tr));

}

..................................................................

/**@hide*/publicstaticnativeintLOG_ID_MAIN=0;

/**@hide*/publicstaticnativeintLOG_ID_RADIO=1;

/**@hide*/publicstaticnativeintLOG_ID_EVENTS=2;

/**@hide*/publicstaticnativeintLOG_ID_SYSTEM=3;

/**@hide*/publicstaticnativeintprintln_native(intbufID,

intpriority,Stringtag,Stringmsg);

} 定义了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:

view plaincopy

/*//device/libs/android_runtime/android_util_Log.cpp

**

**Copyright2006,TheAndroidOpenSourceProject

**

**LicensedundertheApacheLicense,Version2.0(the"License");

**youmaynotusethisfileexceptincompliancewiththeLicense.

**YoumayobtainacopyoftheLicenseat

**

**https://www.apache.org/licenses/LICENSE-2.0

**

**Unlessrequiredbyapplicablelaworagreedtoinwriting,software

**distributedundertheLicenseisdistributedonan"ASIS"BASIS,

**WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.

**SeetheLicenseforthespecificlanguagegoverningpermissionsand

**limitationsundertheLicense.

*/

#defineLOG_NAMESPACE"log.tag."

#defineLOG_TAG"Log_println"

#include

#include

#include

#include

#include"jni.h"

#include"utils/misc.h"

#include"android_runtime/AndroidRuntime.h"

#defineMIN(a,b)((a

namespaceandroid{

structlevels_t{

jintverbose;

jintdebug;

jintinfo;

jintwarn;

jinterror;

jintassert;

};

staticlevels_tlevels;

staticinttoLevel(constchar*value)

{

switch(value[0]){

case'V':returnlevels.verbose;

case'D':returnlevels.debug;

case'I':returnlevels.info;

case'W':returnlevels.warn;

case'E':returnlevels.error;

case'A':returnlevels.assert;

case'S':return-1;//SUPPRESS

}

returnlevels.info;

}

staticjbooleanandroid_util_Log_isLoggable(JNIEnv*env,jobjectclazz,jstringtag,jintlevel)

{

#ifndefHAVE_ANDROID_OS

returnfalse;

#else/*HAVE_ANDROID_OS*/

intlen;

charkey[PROPERTY_KEY_MAX];

charbuf[PROPERTY_VALUE_MAX];

if(tag==NULL){

returnfalse;

}

jbooleanresult=false;

constchar*chars=env->GetStringUTFChars(tag,NULL);

if((strlen(chars)+sizeof(LOG_NAMESPACE))>PROPERTY_KEY_MAX){

jclassclazz=env->FindClass("java/lang/IllegalArgumentException");

charbuf2[200];

snprintf(buf2,sizeof(buf2),"Logtag\"%s\"exceedslimitof%dcharacters\n",

chars,PROPERTY_KEY_MAX-sizeof(LOG_NAMESPACE));

//releasethechars!

env->ReleaseStringUTFChars(tag,chars);

env->ThrowNew(clazz,buf2);

returnfalse;

}else{

strncpy(key,LOG_NAMESPACE,sizeof(LOG_NAMESPACE)-1);

strcpy(key+sizeof(LOG_NAMESPACE)-1,chars);

}

env->ReleaseStringUTFChars(tag,chars);

len=property_get(key,buf,"");

intlogLevel=toLevel(buf);

return(logLevel>=0&&level>=logLevel)?true:false;

#endif/*HAVE_ANDROID_OS*/

}

/*

*Inclassandroid.util.Log:

*publicstaticnativeintprintln_native(intbuffer,intpriority,Stringtag,Stringmsg)

*/

staticjintandroid_util_Log_println_native(JNIEnv*env,jobjectclazz,

jintbufID,jintpriority,jstringtagObj,jstringmsgObj)

{

constchar*tag=NULL;

constchar*msg=NULL;

if(msgObj==NULL){

jclassnpeClazz;

npeClazz=env->FindClass("java/lang/NullPointerException");

assert(npeClazz!=NULL);

env->ThrowNew(npeClazz,"printlnneedsamessage");

return-1;

}

if(bufID<0||bufID>=LOG_ID_MAX){

jclassnpeClazz;

npeClazz=env->FindClass("java/lang/NullPointerException");

assert(npeClazz!=NULL);

env->ThrowNew(npeClazz,"badbufID");

return-1;

}

if(tagObj!=NULL)

tag=env->GetStringUTFChars(tagObj,NULL);

msg=env->GetStringUTFChars(msgObj,NULL);

intres=__android_log_buf_write(bufID,(android_LogPriority)priority,tag,msg);

if(tag!=NULL)

env->ReleaseStringUTFChars(tagObj,tag);

env->ReleaseStringUTFChars(msgObj,msg);

returnres;

}

/*

*JNIregistration.

*/

staticJNINativeMethodgMethods[]={

/*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},

};

intregister_android_util_Log(JNIEnv*env)

{

jclassclazz=env->FindClass("android/util/Log");

if(clazz==NULL){

LOGE("Can'tfindandroid/util/Log");

return-1;

}

levels.verbose=env->GetStaticIntField(clazz,env->GetStaticFieldID(clazz,"VERBOSE","I"));

levels.debug=env->GetStaticIntField(clazz,env->GetStaticFieldID(clazz,"DEBUG","I"));

levels.info=env->GetStaticIntField(clazz,env->GetStaticFieldID(clazz,"INFO","I"));

levels.warn=env->GetStaticIntField(clazz,env->GetStaticFieldID(clazz,"WARN","I"));

levels.error=env->GetStaticIntField(clazz,env->GetStaticFieldID(clazz,"ERROR","I"));

levels.assert=env->GetStaticIntField(clazz,env->GetStaticFieldID(clazz,"ASSERT","I"));

returnAndroidRuntime::registerNativeMethods(env,"android/util/Log",gMethods,NELEM(gMethods));

}

};//namespaceandroid 在gMethods变量中,定义了println_native本地方法对应的函数调用是android_util_Log_println_native。在android_util_Log_println_native函数中,通过了各项参数验证正确后,就调用运行时库函数__android_log_buf_write来实现Log的写入操作。__android_log_buf_write函实实现在liblog库中,它有4个参数,分别缓冲区ID、优先级别ID、Tag字符串和Msg字符串。下面运行时库liblog中的__android_log_buf_write的实现。

三.系统运行库层日志系统的实现。

在系统运行库层liblog库的实现中,内容比较多,这里,我们只关注日志写入操作__android_log_buf_write的相关实现:

view plaincopy

int__android_log_buf_write(intbufID,intprio,constchar*tag,constchar*msg)

{

structiovecvec[3];

if(!tag)

tag="";

/*XXX:Thisneedstogo!*/

if(!strcmp(tag,"HTC_RIL")||

!strncmp(tag,"RIL",3)||/*Anylogtagwith"RIL"astheprefix*/

!strcmp(tag,"AT")||

!strcmp(tag,"GSM")||

!strcmp(tag,"STK")||

!strcmp(tag,"CDMA")||

!strcmp(tag,"PHONE")||

!strcmp(tag,"SMS"))

bufID=LOG_ID_RADIO;

vec[0].iov_base=(unsignedchar*)&prio;

vec[0].iov_len=1;

vec[1].iov_base=(void*)tag;

vec[1].iov_len=strlen(tag)+1;

vec[2].iov_base=(void*)msg;

vec[2].iov_len=strlen(msg)+1;

returnwrite_to_log(bufID,vec,3);

}

函数首先是检查传进来的tag参数是否是为HTC_RIL、RIL、AT、GSM、STK、CDMA、PHONE和SMS中的一个,如果是,就无条件地使用ID为LOG_ID_RADIO的日志缓冲区作为写入缓冲区,接着,把传进来的参数prio、tag和msg分别存放在一个向量数组中,调用write_to_log函数来进入下一步操作。write_to_log是一个函数指针,定义在文件开始的位置上:

view plaincopy

staticint__write_to_log_init(log_id_t,structiovec*vec,size_tnr);

staticint(*write_to_log)(log_id_t,structiovec*vec,size_tnr)=__write_to_log_init; 并且初始化为__write_to_log_init函数:

view plaincopy

staticint__write_to_log_init(log_id_tlog_id,structiovec*vec,size_tnr)

{

#ifdefHAVE_PTHREADS

pthread_mutex_lock(&log_init_lock);

#endif

if(write_to_log==__write_to_log_init){

log_fds[LOG_ID_MAIN]=log_open("/dev/"LOGGER_LOG_MAIN,O_WRONLY);

log_fds[LOG_ID_RADIO]=log_open("/dev/"LOGGER_LOG_RADIO,O_WRONLY);

log_fds[LOG_ID_EVENTS]=log_open("/dev/"LOGGER_LOG_EVENTS,O_WRONLY);

log_fds[LOG_ID_SYSTEM]=log_open("/dev/"LOGGER_LOG_SYSTEM,O_WRONLY);

write_to_log=__write_to_log_kernel;

if(log_fds[LOG_ID_MAIN]<0||log_fds[LOG_ID_RADIO]<0||

log_fds[LOG_ID_EVENTS]<0){

log_close(log_fds[LOG_ID_MAIN]);

log_close(log_fds[LOG_ID_RADIO]);

log_close(log_fds[LOG_ID_EVENTS]);

log_fds[LOG_ID_MAIN]=-1;

log_fds[LOG_ID_RADIO]=-1;

log_fds[LOG_ID_EVENTS]=-1;

write_to_log=__write_to_log_null;

}

if(log_fds[LOG_ID_SYSTEM]<0){

log_fds[LOG_ID_SYSTEM]=log_fds[LOG_ID_MAIN];

}

}

#ifdefHAVE_PTHREADS

pthread_mutex_unlock(&log_init_lock);

#endif

returnwrite_to_log(log_id,vec,nr);

} 这里我们可以看到,如果是第一次调write_to_log函数,write_to_log == __write_to_log_init判断语句就会true,于是执行log_open函数打开设备文件,并把文件描述符保存在log_fds数组中。如果打开/dev/LOGGER_LOG_SYSTEM文件失败,即log_fds[LOG_ID_SYSTEM] < 0,就把log_fds[LOG_ID_SYSTEM]设置为log_fds[LOG_ID_MAIN],这就是我们上面描述的如果不存在ID为LOG_ID_SYSTEM的日志缓冲区,就把LOG_ID_SYSTEM设置为和LOG_ID_MAIN对应的日志缓冲区了。LOGGER_LOG_MAIN、LOGGER_LOG_RADIO、LOGGER_LOG_EVENTS和LOGGER_LOG_SYSTEM四个宏定义在system/core/include/cutils/logger.h文件中:

view plaincopy

#defineLOGGER_LOG_MAIN"log/main"

#defineLOGGER_LOG_RADIO"log/radio"

#defineLOGGER_LOG_EVENTS"log/events"

#defineLOGGER_LOG_SYSTEM"log/system" 接着,把write_to_log函数指针指向__write_to_log_kernel函数:

view plaincopy

staticint__write_to_log_kernel(log_id_tlog_id,structiovec*vec,size_tnr)

{

ssize_tret;

intlog_fd;

if(/*(int)log_id>=0&&*/(int)log_id<(int)LOG_ID_MAX){

log_fd=log_fds[(int)log_id];

}else{

returnEBADF;

}

do{

ret=log_writev(log_fd,vec,nr);

}while(ret<0&&errno==EINTR);

returnret;

} 函数调用log_writev来实现Log的写入,注意,这里通过一个循环来写入Log,直到写入成功为止。这里log_writev是一个宏,在文件开始的地方定义为:

view plaincopy

#ifFAKE_LOG_DEVICE

//Thiswillbedefinedwhenbuildingforthehost.

#definelog_open(pathname,flags)fakeLogOpen(pathname,flags)

#definelog_writev(filedes,vector,count)fakeLogWritev(filedes,vector,count)

#definelog_close(filedes)fakeLogClose(filedes)

#else

#definelog_open(pathname,flags)open(pathname,flags)

#definelog_writev(filedes,vector,count)writev(filedes,vector,count)

#definelog_close(filedes)close(filedes)

#endif 这里,我们看到,一般情况下,log_writev就是writev了,这是个常见的批量文件写入函数,就不多说了。

至些,整个调用过程就结束了。总结一下,首先是从应用程序层调用应用程序框架层的Java接口,应用程序框架层的Java接口通过调用本层的JNI方法进入到系统运行库层的C接口,系统运行库层的C接口通过设备文件来访问内核空间层的Logger驱动程序。