使用AOP统一封装Android应用内的日志类

安卓的日志类(android.util.Log)只提供最基本的日志输出功能,并无提供日志过滤、文件记录等常用功能,所以很多库和应用都自行封装了自己的日志类。比如Volley库中的com.android.volley.VolleyLog类就封装了系统日志类并提供字符串格式化参数的功能,另外一些库一般提供了设置公共Tag或者日志输出Level的功能。

如果项目里面引用了多个库,每个库都使用了自己的日志类,那么控制日志输出就比较麻烦,一般有以下手段:

  1. 在应用初始化时调用各个日志类的设置Api设定到统一的环境
  2. 在release前,使用Proguard删除所有日志输出语句
  3. 直接修改开源库代码,将所有日志类的内容改为自己应用的日志类的封装

经过实践可以知道,无论采用哪种方法均不完美,最好的方案是可以像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”,可以看到已经变成调用类的名称,并且可以通过配置文件控制是否输出

相关推荐