使用AOP统一封装Android应用内的日志类
安卓的日志类(android.util.Log)只提供最基本的日志输出功能,并无提供日志过滤、文件记录等常用功能,所以很多库和应用都自行封装了自己的日志类。比如Volley库中的com.android.volley.VolleyLog类就封装了系统日志类并提供字符串格式化参数的功能,另外一些库一般提供了设置公共Tag或者日志输出Level的功能。
如果项目里面引用了多个库,每个库都使用了自己的日志类,那么控制日志输出就比较麻烦,一般有以下手段:
- 在应用初始化时调用各个日志类的设置Api设定到统一的环境
- 在release前,使用Proguard删除所有日志输出语句
- 直接修改开源库代码,将所有日志类的内容改为自己应用的日志类的封装
经过实践可以知道,无论采用哪种方法均不完美,最好的方案是可以像j2ee开发那样使用slf4j公共日志接口将不同的日志库输出到一个统一的后端(比如log4j和logback),并通过该后端提供日志过滤和文件记录功能。slf4j是通过直接替换日志类的方式实现的(比如使用log4j-over-slf4j.jar直接替换log4j.jar),我们可以在android上使用aspectj在编译期更改日志类的字节码达到相似目的,好处是不需要手动更改开源库的源代码,方便保持更新。
下面以在ADT环境下使用android-maven-plugin和aspectj-maven-plugin修改VolleyLog为例进行说明。
统一的后端:https://github.com/allenz8512/zlog,笔者开发的类似log4j的日志库,提供日志过滤配置、自动使用类名作为Tag和日志文件输出功能。
pom.xml片段:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.6</complianceLevel><!-- 编译版本为1.6,如果使用aspectj注解必须配置 -->
<source>1.6</source>
<target>1.6</target>
<weaveDependencies><!-- 织入已经打包成jar的类,指定依赖包的坐标 -->
<weaveDependency>
<groupId>com.android</groupId>
<artifactId>volley</artifactId>
</weaveDependency>
</weaveDependencies>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>me.allenz</groupId>
<artifactId>zlog</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>com.android</groupId>
<artifactId>volley</artifactId>
<version>1.0.0</version>
<!-- 必须将scope配置为provided,避免android-maven-plugin重复将jar包内的类转为dex导致构建失败 -->
<!-- 因为上面织入时已经将jar下所有的类添加过了 -->
<scope>provided</scope>
</dependency>
</dependencies>Aspectj代码:
@Aspect
public class OtherLoggerAspect {
@Pointcut("execution(public void com.android.volley.VolleyLog.*(..))")
public void pointcut() {
}
@Around("pointcut()")//使用Around Advise但不调用原方法,等同于覆盖
public void weaveJointPoint(final ProceedingJoinPoint joinPoint) {
final String method = joinPoint.getSignature().getName();
final Object[] args = joinPoint.getArgs();
final String caller = Utils.getCallerClassName(
VolleyLog.class.getName(), 2);//通过调用堆栈获取VolleyLog调用者的类名
final Logger logger = LoggerFactory.getLogger(caller);//获取该类的Logger
if (method.equals("d")) {//覆盖VolleyLog.d(String,Object...)输出,重定向到zlog
final String message = (String) args[0];
final Object[] messageArgs = (Object[]) args[1];
if (messageArgs == null || messageArgs.length == 0) {
logger.debug(message);
} else {
logger.debug(message, messageArgs);
}
}
}
}测试代码:
VolleyLog.d("Hello World!");//调用类为HelloAndroidActivity
VolleyLog.d("Number is %d", 123);执行以下maven命令打包编译apk并安装运行:
mvn package android:deploy android:run
logcat输出为:
10-09 11:19:07.320: D/HelloAndroidActivity(1524): Hello World! 10-09 11:19:07.330: D/HelloAndroidActivity(1524): Number is 123
默认的VolleyLog的Tag应该为“Volley”,可以看到已经变成调用类的名称,并且可以通过配置文件控制是否输出