AndroidStudio3 支持 Java8 了,就问你敢用吗



对本文有任何问题,可加我的个人微信询问:kymjs666

支持 Java 8

kotlin 相关的东西很早以前我就在讲了,这里就不再细说了。AS3里面有一个亮眼的特性就是支持J8。首先说一下为什么以前我们不能用Java8的新特性,最主要的原因就是 lambda 语法。在 JVM 中,Java8 的语法是通过一个叫做invokedynamic的字节码操作命令完成的,但是这东西在 dalvik 中并没有,因此一直不能用。
现在AS3.0之所以能用,实际上是在新的Android Studio中加入了一个desugar的东西,他就类似JVM上的invokedynamic,把Java8的字节码翻译成dalvik可识别的。

官网介绍:
Android Studio provides built-in support for using certain Java 8 language features and third-party libraries that use them. As shown in figure 1, the default toolchain implements the new language features by performing bytecode transformations, called desugar, on the output of the javac compiler.

desugar 能干啥

首先看张官方图:

gradle, android, kotlin, desugar

在 javac 执行后,desugar 会对 class 做操作,将内部的lambda相关的语法转换为dalvik 可识别的语法。
说的太抽象具体表现我们看代码。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    new Thread(() -> {
        Log.d("kymjs", "========");
    }).start();
}

一个这样的Java8 lambda语法的代码被编译以后,反编译它,可以看到变成了这样:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(2131296283);
    (new Thread(MainActivity$$Lambda$0.$instance)).start();
}

// $FF: synthetic class
final class MainActivity$$Lambda$0 implements Runnable {
    static final Runnable $instance = new MainActivity$$Lambda$0();

    private MainActivity$$Lambda$0() {
    }

    public void run() {
        MainActivity.lambda$onCreate$0$MainActivity();
    }
}

我们看到上面的代码,在编译后的run()方法内有一句MainActivity.lambda$onCreate$0$MainActivity();其实这一句就是原本lambda body,他被转换成了 MainActivity 类中的一个 static method。在最终编译成 dex 后会再次优化,减少一次方法调用直接变成run方法的body(相当于内联)。具体原理操作请见源码的visitInvokeDynamicInsn方法:GoogleCode请自备梯子

上面的代码演示了纯函数(什么是纯函数自己wiki)的操作,下面看一个非纯函数的。
编译前:

public class MainActivity extends AppCompatActivity {

    String mString = "hello";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(() -> {
            Log.d("kymjs", "========" + mString);
        }).start();
    }
}

编译后:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(2131296283);
    (new Thread(new MainActivity$$Lambda$0(this))).start();
}

// $FF: synthetic class
final class MainActivity$$Lambda$0 implements Runnable {
    private final MainActivity arg$1;

    MainActivity$$Lambda$0(MainActivity var1) {
        this.arg$1 = var1;
    }

    public void run() {
        this.arg$1.lambda$onCreate$0$MainActivity();
    }
}

原本的 lambda 静态对象不再是静态的了;lambda 类的构造方法多了一个外部类对象的引用。因此,如果 lambda body 不是一个非纯函数,是有可能会造成内存泄漏的(原因跟内部类持有外部类对象是一样)。
最后:附一篇官方介绍:https://developer.android.com/studio/write/java8-support.html

方法引用

这个就实在是让我哭笑不得了。官网标注,Method References 完全支持了,原本想到kotlin 的高阶函数会有性能问题,还想看看Java8会不会有这个问题。但是我用了一下,槽点满满。不管是Supplier还是Predicate,Function 所有的方法调用都得要最低 API24,我靠现在普遍都是兼容到14的吧,你这让我怎么用高阶方法。不过我也尝试不考虑低版本写了一个,看了一下效果。

首先是Java8编译前代码:

public String str = "hello";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    test(() -> str);
}

public void test(Supplier<String> block) {
    System.out.println("=======" + block.get());
}

Java8编译后,貌似换汤不换药,只替换lambda部分,方法内依旧是普通对象方法调用:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(2131296283);
    this.test(new MainActivity$$Lambda$0(this));
}

public void test(Supplier<String> block) {
    System.out.println("==========" + (String)block.get());
}

// $FF: synthetic class
final class MainActivity$$Lambda$0 implements Supplier {
    private final MainActivity arg$1;

    MainActivity$$Lambda$0(MainActivity var1) {
        this.arg$1 = var1;
    }

    public Object get() {
        return this.arg$1.lambda$onCreate$0$MainActivity();
    }
}

kotlin编译前代码:

val str: String = "hello"

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    test {
        str
    }
}

fun test(block: () -> String) {
    println("=========${block.invoke()}")
}

kotlin编译后的代码:

protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView((int) R.layout.activity_main);
    test(new MainActivity$onCreate$1(this));
}

public final void test(@NotNull Function0<String> block) {
    Intrinsics.checkParameterIsNotNull(block, "block");
    System.out.println("=========" + ((String) block.invoke()));
}

/* compiled from: MainActivity.kt */
final class MainActivity$onCreate$1 extends Lambda implements Function0<String> {
    final /* synthetic */ MainActivity this$0;

    MainActivity$onCreate$1(MainActivity mainActivity) {
        this.this$0 = mainActivity;
        super(0);
    }

    @NotNull
    public final String invoke() {
        return this.this$0.getStr();
    }
}

所以说,kotlin在实现上跟Java也依旧是一模一样,首先生成一个类,把lambda转换成对象,再调用这个对象的invoke()方法。但是别忘了,kotlin有神奇的inline关键字,就专门用来解决这种莫名其妙的多生成一大堆对象的情况。
假设给上面的test()方法加上inline关键字后,编译后的代码就变成了这样,相当于并没有调用test()方法,因此也就不存在多生成的Function0 对象了:

protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView((int) R.layout.activity_main);
    MainActivity this_$iv = this;
    System.out.println("=========" + this.str);
}

public final void test(@NotNull Function0<String> block) {
    Intrinsics.checkParameterIsNotNull(block, "block");
    System.out.println("=========" + ((String) block.invoke()));
}

原本我是想夸一夸J8的支持,因为我最初以为他是将 lambda body 的纯函数转换成静态方法,直接将 lambda 改成静态方法调用来做的,结果没想到还不如 kotlin。感觉就是 Google 为了 KPI 去加了一个 Java8 支持的噱头。