之前写过两篇providedAar系列的文章,见:

  • Android application中使用provided aar并没有那么简单
  • 再谈Application ProvidedAar

基本上已经完美的实现了Android Gradle Plugin [1.3.0,3.2.0+)版本com.android.application中使用provided aar的功能,支持代码和资源的引用,同时不将代码和资源编译进去,但是还遗留了最后一个比较头疼的问题,那就是configurate阶段和构建阶段会被警告日志刷屏。

具体现象如下:

依赖少的时候还好,但是依赖一多或者传递依赖一多,整个控制台就被这种日志刷屏了,所以必须解决一下这个问题。具体解决思路就是把这种日志的级别调整为info级别,由于正常构建不会输出info级别的日志,所以这种日志就不会刷屏了。

这个问题只有在Android Gradle Plugin 2.3.3及以下会存在,3.0.0及以上不存在这个问题,因此我们只要解决2.3.3及以下版本即可。

一开始的解决思路是利用groovy的MOP,运行期动态修改函数指向我们自己的调用,具体的伪代码如下

1
2
3
4
5
6
7
project.getLogger().getMetaClass().warn = {String msg ->
if (msg 满足我们拦截的信息){
originalLogger.info(msg)
return
}
调用原来的逻辑
}

结果发现试了n多种MOP方式,都无法完成方法替换,结论是groovy的MOP无法修改java中的代码调用,只能对groovy中的代码调用生效,而Android Gradle Plugin的代码和gradle的相关部分代码是由java实现的,因此MOP这种场景下不适用。

于是换一种思路。通过查看gradle的代码发现,我们可以拦截到具体的日志输出逻辑,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
private void log(LogLevel logLevel, Throwable throwable, String message) {
Object buildOperationId = BuildOperationIdentifierRegistry.getCurrentOperationIdentifier();
LogEvent logEvent = new LogEvent(clock.getCurrentTime(), name, logLevel, message, throwable, buildOperationId);
OutputEventListener outputEventListener = context.getOutputEventListener();
try {
outputEventListener.onOutput(logEvent);
} catch (Throwable e) {
// fall back to standard out
e.printStackTrace(System.out);
}
}

可以发现最终日志输出是调用outputEventListener的onOutput函数,传递一个LogEvent携带日志相关信息进行输出的,而outputEventListener是运行期由Android Gradle Plugin设置进去的,具体的设置相关方法在logger的context中,如下:

1
2
3
4
5
6
7
public void setOutputEventListener(OutputEventListener outputEventListener) {
this.outputEventListener.set(outputEventListener);
}
public OutputEventListener getOutputEventListener() {
return (OutputEventListener)this.outputEventListener.get();
}

所以我们其实只要设置OutputEventListener对象为我们自己的对象,并且将原始的OutputEventListener对象保存起来,就可以实现日志输出逻辑的自定义,从而达到我们的效果。大致的伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext listenerBackedLoggerContext = project.getLogger().getMetaClass().getProperty(project.getLogger(), "context")
org.gradle.internal.logging.events.OutputEventListener originalOutputEventListener = listenerBackedLoggerContext.getOutputEventListener()
org.gradle.api.logging.LogLevel originalOutputEventLevel = listenerBackedLoggerContext.getLevel()
listenerBackedLoggerContext.setOutputEventListener(new org.gradle.internal.logging.events.OutputEventListener() {
@Override
void onOutput(org.gradle.internal.logging.events.OutputEvent outputEvent) {
if (msg 满足我们拦截的信息){
修改日志级别为info并输出
return
}
调用原来的逻辑
}
})

但是不幸的是OutputEventListenerBackedLoggerContext,OutputEventListener和LogLevel这三个类,在不同的gradle版本中包名都不一样,除了包名,其他逻辑完全一样,这就会带来一个问题,即使进行了兼容,也无法编译成功,编译过程中必然会报类找不到,那么有没有办法解决这个问题呢。

最终的解决方法是使用def关键字和lambda表达式,这样包名相关的信息就可以完全被屏蔽了,具体的实现逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//redirect warning log to info log
def listenerBackedLoggerContext = project.getLogger().getMetaClass().getProperty(project.getLogger(), "context")
def originalOutputEventListener = listenerBackedLoggerContext.getOutputEventListener()
def originalOutputEventLevel = listenerBackedLoggerContext.getLevel()
listenerBackedLoggerContext.setOutputEventListener({def outputEvent ->
def logLevel = originalOutputEventLevel.name()
if (!("QUIET".equalsIgnoreCase(logLevel) || "ERROR".equalsIgnoreCase(logLevel))) {
if ("WARN".equals(outputEvent.getLogLevel().name())) {
String message = outputEvent.getMessage()
//Provided dependencies can only be jars.
//provided dependencies can only be jars.
if (message != null && (message.contains("Provided dependencies can only be jars.") || message.contains("provided dependencies can only be jars. "))) {
project.logger.info(message)
return
}
}
if (originalOutputEventListener != null) {
originalOutputEventListener.onOutput(outputEvent)
}
}
})

这就完成了日志逻辑的替换,具体思路就是获取logger context对象,拿到原始的OutputEventListener对象,然后拿到原始的日志输出级别,在日志级别大于WARN时,不会输出WARN的日志,因此只要处理日志级别小于等于WARN的情况(即不等于QUIE和ERROR),才需要进行这个逻辑处理,处理的对应报错的日志的级别是WARN的,然后判断输出的日志里面是否含有关键字Provided dependencies can only be jars,这里特别注意一下,有些版本可能Provided是小写的,即provided,当条件满足后,将日志调整为info进行输出,否则,调用原始日志输出逻辑。

于是乎,10来行代码,将控制台的刷屏日志彻底消除了,整个世界都清净了。

http://fucknmb.com/2018/03/31/Application-ProvidedAar%E5%AE%8C%E7%BB%93%E7%AF%87/

Application ProvidedAar 完结篇相关推荐

  1. 从无到有写一个运维APP(三)完结篇

    前言:自己的挖的坑还得填,此篇为完结篇. 环境的搭建参考第一篇 从无到有写一个运维APP(一),至于第二篇就跳过吧,写个 APP 没那么复杂. 由于自己现在无业游民,所以没有什么现成的环境,环境就随便 ...

  2. ASP.NET 5系列教程(七)完结篇-解读代码

     在本文中,我们将一起查看TodoController 类代码. [Route] 属性定义了Controller的URL 模板: [Route("api/[controller]&quo ...

  3. KlayGE 4.0中Deferred Rendering的改进(五)完结篇:Post process

    转载请注明出处为KlayGE游戏引擎 上一篇分析了KlayGE中实现实时全动态GI的方法,本篇是这个系列的完结篇,主要讲流水线的最后一段:Post process. Post process 在Kla ...

  4. Android Service(7)--完结篇

    傻蛋在Android Service(4) 中讲述了使用AIDL语言,来让ADT帮助我们自动生成一个Stub类(Binder的子类),来实现不同进程中Service的调用.通过研究ADT自动生成的代码 ...

  5. .NET 并行(多核)编程系列之六 Task基础部分完结篇

    .NET 并行(多核)编程系列之六 Task基础部分完结篇 前言:之前的文章介绍了了并行编程的一些基本的,也注重的讲述了Task的一些使用方法,本篇很短,将会结束Task的基础知识的介绍. 本篇的主要 ...

  6. [推荐] TechNet 广播 SQL Server 2000完结篇

      TechNet中文网络广播在之前已经推出了SQL Server 2000的基础系列和管理专家系列,使广大听众认识并掌握了SQL Server 2000的管理技巧.本次系列作为前两次系列课程的完结篇 ...

  7. 用python提取图片主要颜色_Python可视化|09-使用python和R提取图片颜色绘图(五-颜色使用完结篇)...

    本文是继前面四篇python可视化颜色使用的完结篇,介绍如何使用python提取图片中的颜色绘图: 如果你不想使用前人设定好的色号或者colormap,想自己从好看的图片中提取颜色,请往下看: 1.颜 ...

  8. mysql 主从 通俗易懂_MySQL 主从同步架构中你不知道的“坑”(完结篇)

    MySQL 主从同步架构中你不知道的"坑"(完结篇) 收录于话题 #MySQL从入门到放弃 26个 点击上方蓝字,关注我们哟! 前言导读 之前写出一篇文章也是关于这个主从同步架构的 ...

  9. curviloft插件怎么用_完结篇——你想要的逆天插件系列这里都有

    原标题:完结篇--你想要的逆天插件系列这里都有 十一前的一段时间,马克笔设计留学的安老师跟大家持续分享了几个非常实用的小插件,不知道大家用起来怎么样呢,是不是建模效率有了很大的提高.不过有些同学可能还 ...

最新文章

  1. 理解Linux中断 (2)【转】
  2. Dockerfile 部署Djano项目
  3. ifstat,iftop
  4. win10红警2黑屏_win10系统如何通过U盘安装系统呢?
  5. JUnit,Logback,带有Maven 3的Maven
  6. Android之Notification制作多媒体控制器
  7. java复制一个对象_Java中对象的复制
  8. 湖南工程学院+c语言程序设计人事档案管理系统,程序设计人事档案管理系统.doc...
  9. mysql date timestamp_【Mysql】Datetime和Timestamp区别,及mysql中各种时间的使用
  10. 开源的人品测试机 (windows版)
  11. azure centos 7安装mariadb
  12. Windows系统单网卡配置双IP双网关
  13. react进行状态管理的几种方式
  14. 全国省市区SQL语句(mysql)
  15. JSch连接不上Linux服务器,JSch链接linux服务器问题解决方案:Session.connect: java.io.IOException: End of IO Stream Read...
  16. oracle存储过程实例
  17. java 设置图标_设置java窗口的图标
  18. Vue 爬坑之旅 -- 用自定义指令解决 IOS 12 中键盘收起后页面底部有留白的问题
  19. 单词数(HDU 2072)
  20. 一张30年前的大学排行榜!

热门文章

  1. 利用栈将中缀表达式转化成后缀表达式
  2. Java语言基础--字符串
  3. android中关于点击屏幕,实现破碎的效果的实现
  4. 快慢指针寻找循环节点
  5. 云炬Android开发笔记 13购物车,订单,支付功能开发(包含支付宝支付和微信支付)
  6. 手把手教你在 Ubuntu16.04 安装 GPU 驱动 + CUDA9.0 + cuDNN7
  7. dev c++怎么调试_「正点原子NANO STM32开发板资料连载」第十八章 USMART 调试组件...
  8. Matlab神经网络十讲(2): Create Configuration Train NeuralNet
  9. VTK修炼之道60:体绘制_体绘制管线图形渲染管线
  10. 短时傅里叶分析:spectrogram函数