使用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”,可以看到已经变成调用类的名称,并且可以通过配置文件控制是否输出