在上一问,我们了解了 Java 中 lambda 表达式的原理:
每日一问 | Java中匿名内部类写成 lambda,真的只是语法糖吗?
新的问题来了,看下面一段简单的 lambda 代码:
Runnable r = ()->{
Log.d("test","hello, lambda");
};
r.run();
- 上述代码在 Android 中与 Java 中,运行时原理有何不同?
- 与 Java 的 lambda 相比,哪个更像是语法糖?
- transformClassesWithDesugarForDebug 这个任务是做什么的?
更多问答 >>
-
每日一问 | Android 中两种设置线程优先级的方式,有何区别?
2021-01-27 23:59 -
2021-01-31 16:58
-
每日一问 | 类要先加载、链接、初始化才能实例化,有特殊Case吗?
2021-02-21 20:15 -
每日一问 | 今天还探索一个 View 的方法 hasOverlappingRendering()
2021-02-21 20:16 -
2021-03-18 23:20
-
每日一问 | Java中匿名内部类写成 lambda,真的只是语法糖吗?
2021-01-11 00:00 -
每日一问 | RxJava中Observable、Flowable、Single、Maybe 有何区别?
2021-01-03 20:34 -
每日一问 | View invalidate() 相关的一些细节探究~
2020-12-27 22:38 -
每日一问 | Call requires API level 23 (current min is 14) 扫描出来的原理是?
2020-12-27 22:39 -
每日一问 | 当Unsafe遇上final,超神奇的事情发生了?
2020-11-02 00:16
像上次那样,在打印日志后,抛个异常看下堆栈信息:
log:
这次居然能直接在堆栈中看到那个自动生成的实现类!
打开编译之后的apk看下dex:lambda$onCreate$0
方法,看下它的smali代码:大致解释一下:
先注册了2个寄存器;
把字符串常量 "Application" 赋值给寄存器
v0
;把字符串常量 "hello, lambda" 赋值给寄存器
v1
;调用android.util.Log的静态方法
d
,把寄存器v0
和v1
的值传了进去(Log.d("Application", "hello, lambda"););创建java.lang.RuntimeException对象实例,并赋值到寄存器
v0
上;调用java.lang.RuntimeException的
<init>()
方法(即无参构造函数),目标对象是寄存器v0
储存的值;抛出异常,目标对象是寄存器
v0
储存的值(throw new RuntimeException(););有没有发现,这里跟java的做法是一样的(把lambda主体移到了一个额外生成的方法里)!
再看看onCreate
方法:第15行,获取那个自动生成的实现类的静态变量
19行,声明局部变量INSTANCE
(类型是这个类本身,也就是说,INSTANCE
是【自动生成的实现类】的实例),并赋值到寄存器v0
上;"r"
,类型是java.lang.Runnable,初始值是寄存器v0
的值;然后调用java.lang.Runnable接口方法run
,目标对象是寄存器v0
储存的值。它这里把原来的【创建对象实例】改成了【获取静态字段】。其实这只是一个优化,因为lambda函数体没有依赖CallerClass的变量或方法,完全可以作为一个静态变量存在,这样能避免反复创建对象,但如果有使用到依赖CallerClass里的东西,还是会变成new-instance指令的。
好,来看下那个实现类的smali:在类被加载的时候,就创建了它自己的对象实例,并赋值给静态字段
实现的INSTANCE
;run
方法,里面也是直接调用Application的静态方法lambda$onCreate$0
(跟java的做法一样)。嗯,那就是说:
Java的lambda,在编译成dex时会直接生成对应的实现类,相比于Java本身的做法,不同的只是【生成实现类】的时机。但这已经跟直接使用【匿名内部类】的效果无区别了(忽略那个静态实例的优化)。至于第三问,transformClassesWithDesugarForDebug这个任务是做什么的?
刚开始的时候,我翻遍了gradle的源码也没找到这个Task。。。后来才发现,是版本原因,grep了一下项目目录,发现只有几个gradle版本比较旧的项目里才有这样的字眼:transformClassesWithDesugarForDebug
这个Task了,取而代之的是desugarDebug(Release)FileDependencies
和dexBuilderDebug(Release)
,前者是把项目依赖的所有jar包脱糖,后者是将项目自己的class脱糖。这两个Task都会调用到D8DexArchiveBuilder的convert
方法,在这里会借助com.android.tools.r8.D8(暂时看不到源码)这个类去完成!注意!其实lambda在编译成class时,还是会按Java原本的方式(即invokedynamic)处理的。
打开app/build/intermediates/javac/debug/classes/ 目录:我去看戏了!!!想知道我是怎么知道D8DexArchiveBuilder这些的同学,留言,我周末补充下,关机!!
又是大佬你啊
快开机看留言
都没人说想看
想看
想看
哈哈哈好的,周末补充下
我是怎么发现D8DexArchiveBuilder这些类的呢?
其实找到这些类并不是最终目的,我们这一次主要是想弄清楚在build过程中那些class何时desugar,何时转成dex。新建一个项目,看下你的gradle版本是多少:
你的gradle目录/caches/modules-2/files-2.1/com.android.tools.build/gradle/对应的gradle版本号/
这个目录,把带有sources
字眼的那个jar解压出来。然后搜一下关键字"desugar"
:grep -rni desugar
:先看第一个吧,在刚刚新建的项目中创建一个library(为了等下不会因编译错误而中断debug),然后library中添加你项目对应gradle版本的依赖,比如我的:
sync之后就能通过全局搜(CTRL + SHIFT + F)定位到DexArchiveBuilderTask这个类了:
doTaskAction
方法里打个断点,我们试试等下会不会执行到这里。debug之前,先在app module那边加上lambda代码:
果然执行到了!这个Task的名字就叫dexBuilderDebug !那个inputChanges参数,里面有个Map,记录的都是些class和jar的路径:doProcess
方法:到了DexArchiveBuilderTaskDelegate这边,会看到一个叫
projectClasses
的Set:好,接着往下看:
它创建了一个lambda,分4次调用,lambda里面又调用了
processClassFromInput
方法,跟进去:convertToDexArchive
方法:submit
方法时,把一个叫DexWorkAction的类传了进去:run
方法里面只调用了launchProcessing
方法,在这里打个断点,看看等下会不会执行到这里。launchProcessing
:getDexArchiveBuilder
的方法:咦?!在
点开DexArchiveBuilder,会看到它有2个实现类:getDexArchiveBuilder
方法里,会根据dexer
来判断返回是返回 DxDexBuilder还是 D8DexBuilder !getDexArchiveBuilder
的两个类型。好,回到刚刚的launchProcessing
方法,继续F8下去,发现它走了else那边,也就是调用了processNonIncrementally
方法:主要看
process
方法:convert
方法会在这里面调用,看下面Variables窗口,现在DexArchiveBuilder的实例是D8DexArchiveBuilder。F7跟进去:emmmm,在这里初始化了D8的命令参数之后,就直接调用了D8类的静态方法
run
,但是想直接看run
方法里面的源码是看不到的:还没完,继续:
entryCount
的值为2,估计就是刚刚在app/build/intermediates/javac/debug/classes
目录下的2个class文件(BuildConfig和MainActivity);再看它的output目录为app/build/intermediates/project_dex_archive/debug/out
,打开它:.class
变成了.dex
!慢着!把MainActivity.dex拖进AS里面看看:冲凉睡觉
脱糖器 d8 流程的一部分
我刚刚完成的 lambda 的插装https://github.com/Leifzhang/AndroidAutoTrack我的博客也对这部分做了一个详细的解释了
https://juejin.cn/post/6918733287015841800/先简单回答下,java会用invokeDynmic来处理lambda,而在Android上仅仅是个存语法糖了,transformClassesWithDesugarForDebug会将lambda翻译成匿名内部类