Kotlin系列-01:DSL

原文地址:https://www.jianshu.com/p/0766ede7e917

1. 什么是DSL?

DSL是Domin Specific Language,领域特定语言。指的是专注于某个应用程序领域的计算机语言,比如显示网页的HTML、用于数据库处理的SQL、用于检索或替换文本的正则表达式。与DSL相对的是GPL(General Purpose Language),通用编程语言,如C、Java、Objective-C等。

DSL分为外部DSL和内部DSL。外部DSL是一种可以独立解析的语言,就像SQL,它专注于数据库的操作;内部DSL是通用语言暴露的用来执行特定任务的API,它利用语言本身的特性,将API以特殊的形式暴露出去,例如Android的Gradel和iOS的依赖管理组件CocosPods,Geadle是基于Groovy的,Groovy是一种通用语言,但是Gradle基于Groovy的语言,构建了自己的一套DSL,所以在配置Gradle时,必须遵循Groovy的语法,还要遵循Gradle的DSL标准。

Android的Gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
compileSdkVersion 28

defaultConfig {
applicationId "com.tanjiajun.androidgenericframework"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

androidExtensions {
experimental = true
}

dataBinding {
enabled = true
}

packagingOptions {
pickFirst 'META-INF/kotlinx-io.kotlin_module'
pickFirst 'META-INF/atomicfu.kotlin_module'
pickFirst 'META-INF/kotlinx-coroutines-io.kotlin_module'
}
}

iOS的Podfile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '10.0'

use_frameworks!



target 'GenericFrameworkForiOS' **do**

​ pod 'SnapKit', '~> 4.0'

**end**

2. 实现原理

本文主要讲Kotlin的DSL,先来了解一个概念和语法。

2.1 扩展函数

声明一个扩展函数,需要用到一个接受者类型(receiver type),也就是被扩展的类型作为前缀,如下:

1
fun Activity.getViewModelFactory(): ViewModelFactory = ViewModelFactory((applicationContext as AndroidGenericFrameworkApplication).userRepository)

声明一个getViewModelFactory()函数,它是Activity的扩展函数。

2.2 Lambda表达式

Java 8以下不支持Lambda表达式,Kotlin解决了与Java的互操作性,Kotlin的Lambda表达式以更加简洁的语法实现功能,使开发者从冗余的语法中解放出来。

2.3 Lambda表达式分类

2.3.1 普通Lambda表达式

不接受任何参数返回Uint的Lambda表达式

1
() -> Unit

接受一个可空的TabLayout.Tab参数,返回Unit的Lambda表达式

1
(tab: TabLayout.Tab?) -> Unit

2.3.2 带接收者的Lambda表达式

带有OnTabSlectedListenerBuilder接收者对象,不接受任何参数返回的Unit的Lambda表达式。这种带接收者的在Kotlin的标准库函数中很常见,如下的作用域函数(Scope Functions):

appyl函数

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}

let函数

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*
* For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}

还可以给这些Lambda表达式起别称,叫做类别名(Type aliases)。

1
private typealias OnTabCallback = (tab: TabLayout.Tab?) -> Unit

这里给Lambda表达式起了别称,叫OnTabCallback

Lambda表达式在Kotlin里的这些特性是实现DSL的必备语法糖。

2.4 函数类型实例调用

Kotlin提供invoke函数,可以这样写:f.invoke(x),相当于f(x),例如:

1
onTabReselectedCallback?.invoke(tab) ?: Unit

也可以写成这样:

1
onTabReselectedCallback?.let{ it(tab) } ?: Unit

2.5 中缀表示法

标有infix关键字的函数可以使用中缀表示法(忽略该调用的点和圆括号)调用,中缀函数必须满足以下条件:

  • 1、它们必须是成员函数和扩展函数。
  • 2、它们必须只有一个参数。
  • 3、其参数不得接受可变数量的参数,而且不能有默认值。

例子:

1
2
3
4
5
6
7
infix fun Int.plus(x: Int): Int = this.plus(x)

// 可以中缀表示法调用函数
1 plus 2

// 等同于
1.plus(2)

常用的函数:

until函数用法

1
2
3
for(int 0 until 4){
tlOrder.addTab(tlOrder.newTab().setText("订单$i"))
}

until函数源码

1
2
3
4
5
6
7
8
9
/**
* Returns a range from this value up to but excluding the specified [to] value.
*
* If the [to] value is less than or equal to `this` value, then the returned range is empty.
*/
public infix fun Int.until(to: Int): IntRange{
if(to <= Int.MIN_VALUE) return IntRange.EMPTY
return this .. (to -1).toInt()
}

to函数的用法

1
mapOf<String, Any>("name" to "Tyler", "age" to 30)

to函数的源码

1
2
3
4
5
6
7
/**
* Creates a tuple of type [Pair] from this and [that].
*
* This can be useful for creating [Map] literals with less noise, for example:
* @sample samples.collections.Maps.Instantiation.mapFromPairs
*/
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

3. 实践

Kotlin DSL使代码更加简洁,更加优雅,而且还很有想象力。

3.1 回调处理

3.1.1 Java中的回调实现

实现步骤:

  • 1、定义一个接口
  • 2、在接口定义一些回调方法
  • 3、定义一个设置回调接口的方法,这个方法的参数是回调接口的实例,一般以匿名对象的形式存在。

实现TextWatcher接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EditText etCommonCallbackContent = findViewById(R.id.et_common_callback_content);
etCommonCallbackContent.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// no implementation
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// no implementation
}

@Override
public void afterTextChanged(Editable s) {
// no implementation
}
});

实现TabLayout.OnTabSelectedListener接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TabLayout tlOrder = findViewById(R.id.tl_order);
tlOrder.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
// no implementation
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {
// no implementation
}

@Override
public void onTabReselected(TabLayout.Tab tab) {
// no implementation
}
});

3.1.2 Kotlin中的回调实现

实现TextWatcher接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
findViewById<EditText>(R.id.et_common_callback_content).addTextChangedListener(object :
TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// no implementation
}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// no implementation
}

override fun afterTextChanged(s: Editable?) {
tvCommonCallbackContent.text = s
}
})

实现TabLayout.OnTabSelectedListener接口

1
2
3
4
5
6
7
8
9
10
11
12
13
tlOrder.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener{
override fun onTabReselected(tab: TabLayout.Tab?) {
// no implementation
}

override fun onTabUnselected(tab: TabLayout.Tab?) {
// no implementation
}

override fun onTabSelected(tab: TabLayout.Tab?) {
vpOrder.currentItem = tab?.position ?: 0
}
})

3.1.2 Kotlin DSL

TextWatcherBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.tanjiajun.kotlindsldemo

import android.text.Editable
import android.text.TextWatcher

private typealias BeforeTextChangedCallback =
(s: CharSequence?, start: Int, count: Int, after: Int) -> Unit

private typealias OnTextChangedCallback =
(s: CharSequence?, start: Int, before: Int, count: Int) -> Unit

private typealias AfterTextChangedCallback = (s: Editable?) -> Unit

class TextWatcherBuilder : TextWatcher {

private var beforeTextChangedCallback: BeforeTextChangedCallback? = null
private var onTextChangedCallback: OnTextChangedCallback? = null
private var afterTextChangedCallback: AfterTextChangedCallback? = null

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) =
beforeTextChangedCallback?.invoke(s, start, count, after) ?: Unit

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) =
onTextChangedCallback?.invoke(s, start, before, count) ?: Unit

override fun afterTextChanged(s: Editable?) =
afterTextChangedCallback?.invoke(s) ?: Unit

fun beforeTextChanged(callback: BeforeTextChangedCallback) {
beforeTextChangedCallback = callback
}

fun onTextChanged(callback: OnTextChangedCallback) {
onTextChangedCallback = callback
}

fun afterTextChanged(callback: AfterTextChangedCallback) {
afterTextChangedCallback = callback
}

}

fun registerTextWatcher(function: TextWatcherBuilder.() -> Unit) =
TextWatcherBuilder().also(function)

onTabSelectedListenerBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.tanjiajun.androidgenericframework.utils

import com.google.android.material.tabs.TabLayout

private typealias OnTabCallback = (tab: TabLayout.Tab?) -> Unit

class OnTabSelectedListenerBuilder : TabLayout.OnTabSelectedListener {

private var onTabReselectedCallback: OnTabCallback? = null
private var onTabUnselectedCallback: OnTabCallback? = null
private var onTabSelectedCallback: OnTabCallback? = null

override fun onTabReselected(tab: TabLayout.Tab?) =
onTabReselectedCallback?.invoke(tab) ?: Unit

override fun onTabUnselected(tab: TabLayout.Tab?) =
onTabUnselectedCallback?.invoke(tab) ?: Unit

override fun onTabSelected(tab: TabLayout.Tab?) =
onTabSelectedCallback?.invoke(tab) ?: Unit

fun onTabReselected(callback: OnTabCallback) {
onTabReselectedCallback = callback
}

fun onTabUnselected(callback: OnTabCallback) {
onTabUnselectedCallback = callback
}

fun onTabSelected(callback: OnTabCallback) {
onTabSelectedCallback = callback
}

}

fun registerOnTabSelectedListener(function: OnTabSelectedListenerBuilder.() -> Unit) =
OnTabSelectedListenerBuilder().also(function)

一般步骤:

  • 1、先定义一个类去实现回调接口,并实现它的回调方法
  • 2、观察回调方法的参数,提取成一个函数模型(function type),并且按照需要使用类型别名给函数类型起一个别称,并且用私有修饰。
  • 3、在类里面声明一些可空的函数类型的可变(var)私有成员变量,并且在回调函数中拿到对应的变量实现它的invoke()函数,传入对应的参数。
  • 4、在类中定义定义一些和回调接口一样的名字,但是参数是对应的函数类型的函数,并且将函数类型赋值给当前类的对应的成员变量。
  • 5、定义一个成员函数,参数是一个带有定义好的那个类的接受者对象并返回Unit的Lambda表达式,在函数里创建相应的对象,并且使用also函数把Lambda表达式传出去。

如何使用?如下:
TextWatcher

1
2
3
4
findViewById<EditText>(R.id.et_dsl_callback_content).addTextChangedListener(
registerTextWatcher {
afterTextChanged { tvDSLCallbackContent.text = it }
})

Tablayout.OnTabSelectedListener

1
2
3
tlOrder.addOnTabSelectedListener(registerOnTabSelectedListener {
onTabSelected { vpOrder.currentItem = it?.position ?: 0 }
})

简化之前代码如下:

1
2
3
4
5
findViewById<EditText>(R.id.et_dsl_callback_content).addTextChangedListener(registerTextWatcher({
this.afterTextChanged({ s: Editable? ->
tvDSLCallbackContent.text = s
})
}))

Kotlin语法规定,如果函数最后一个参数是Lambda表达式的话,可以提到小括号外面,同时小括号也可以省略,然后Kotlin可以自己推导出参数的类型,并且使用默认参数it代替命名参数,然后因为这是个带接收者的Lambda表达式,所以可以用this拿到对象,并且调用它的afterTextChanged()函数,最后就得到简化后的代码。

3.1.3 object对象表达式回调和DSL回调对比

  • 1、DSL写法比object写法会更加符合Kotlin风格。
  • 2、object写法要实现所有方法,DSL写法可以按照需要实现想要的方法。
  • 3、从性能上对比,DSL写法对每个回调函数都会创建Lambda表达式的实例对象,而object写法不管有多少个回调方法,都只生成一个匿名对象实例,所以object写法比DSL写法性能好。

这里拿TextWatcher举例,将它们反编译成Java代码,代码如下:

object对象表达式回调

1
2
3
4
5
6
7
8
9
10
11
12
13
((EditText)this.findViewById(-1000084)).addTextChangedListener((TextWatcher)(new TextWatcher() {
public void beforeTextChanged(@Nullable CharSequence s, int start, int count, int after) {
}

public void onTextChanged(@Nullable CharSequence s, int start, int before, int count) {
}

public void afterTextChanged(@Nullable Editable s) {
TextView var10000 = tvCommonCallbackContent;
Intrinsics.checkExpressionValueIsNotNull(var10000, "tvCommonCallbackContent");
var10000.setText((CharSequence)s);
}
}));

DSL回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
((EditText)this.findViewById(-1000121)).addTextChangedListener((TextWatcher)TextWatcherBuilderKt.registerTextWatcher((Function1)(new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke((TextWatcherBuilder)var1);
return Unit.INSTANCE;
}

public final void invoke(@NotNull TextWatcherBuilder $this$registerTextWatcher) {
Intrinsics.checkParameterIsNotNull($this$registerTextWatcher, "$receiver");
$this$registerTextWatcher.beforeTextChanged((Function4)(new Function4() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1, Object var2, Object var3, Object var4) {
this.invoke((CharSequence)var1, ((Number)var2).intValue(), ((Number)var3).intValue(), ((Number)var4).intValue());
return Unit.INSTANCE;
}

public final void invoke(@Nullable CharSequence s, int start, int count, int after) {
TextView var10000 = tvDSLCallbackContent;
Intrinsics.checkExpressionValueIsNotNull(var10000, "tvDSLCallbackContent");
var10000.setText(s);
}
}));
$this$registerTextWatcher.onTextChanged((Function4)(new Function4() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1, Object var2, Object var3, Object var4) {
this.invoke((CharSequence)var1, ((Number)var2).intValue(), ((Number)var3).intValue(), ((Number)var4).intValue());
return Unit.INSTANCE;
}

public final void invoke(@Nullable CharSequence s, int start, int before, int count) {
TextView var10000 = tvDSLCallbackContent;
Intrinsics.checkExpressionValueIsNotNull(var10000, "tvDSLCallbackContent");
var10000.setText(s);
}
}));
$this$registerTextWatcher.afterTextChanged((Function1)(new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke((Editable)var1);
return Unit.INSTANCE;
}

public final void invoke(@Nullable Editable it) {
TextView var10000 = tvDSLCallbackContent;
Intrinsics.checkExpressionValueIsNotNull(var10000, "tvDSLCallbackContent");
var10000.setText((CharSequence)it);
}
}));
}
})));

可以看到object写法只生成一个匿名的TextWathcer对象实例,而DSL写法对每个回调函数都会创建Lambda表达式的实例对象(Function1Founction4),符合上述预期。

3.2 题外话

Java8引入了default关键字,在接口中可以包含一些默认的方法实现。

1
2
3
4
5
6
7
8
9
interface Handlers{

void onLoginClick(View view);

default void onLogoutClick(View view){

}

}

用Kotlin实现的话,可以加上@JvmDefault注解,如下:

1
2
3
4
5
6
7
8
9
10
interface Handlers{

fun onLoginClick(view: View)

@JvmDefault
fun onLogoutClick(view: View){

}

}

可以反编译成Java代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\bf\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H&J\u0010\u0010\u0006\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0017ø\u0001\u0000\u0082\u0002\u0007\n\u0005\b\u0091(0\u0001¨\u0006\u0007"},
d2 = {"Lcom/tanjiajun/kotlindsldemo/MainActivity$Handlers;", "", "onLoginClick", "", "view", "Landroid/view/View;", "onLogoutClick", "app_debug"}
)
public interface Handlers {
void onLoginClick(@NotNull View var1);

@JvmDefault
default void onLogoutClick(@NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
}
}

在使用@JvmDefault时,需要注意:

因为default关键字是Java8才引入的,所以需要做一些特殊处理,从Kotlin官方文档可以看到:
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-default/

1
2
3
4
5
6
7
8
9
10
11
Specifies that a JVM default method should be generated for non-abstract Kotlin interface member.

Usages of this annotation require an explicit compilation argument to be specified: either -Xjvm-default=enable or -Xjvm-default=compatibility.

· with -Xjvm-default=enable, only default method in interface is generated for each @JvmDefault method. In this mode, annotating an existing method with @JvmDefault can break binary compatibility, because it will effectively remove the method from the DefaultImpls class.
· with -Xjvm-default=compatibility, in addition to the default interface method, a compatibility accessor is generated in the DefaultImpls class, that calls the default interface method via a synthetic accessor. In this mode, annotating an existing method with @JvmDefault is binary compatible, but results in more methods in bytecode.
Removing this annotation from an interface member is a binary incompatible change in both modes.

Generation of default methods is only possible with JVM target bytecode version 1.8 (-jvm-target 1.8) or higher.

@JvmDefault methods are excluded from interface delegation.

翻译:

  • 1、只有使用JVM目标字节码1.8版本或者更高,才可以生成default方法。
  • 2、使用这个注解还要指定一个显示的编译参数:-Xjvm-default=enable-Xjvm-default=compatibility(兼容性),使用-Xjvm-default=enable的话对于每个@JvmDefault方法,仅仅是生成default方法,同时这样做可能会破坏二进制兼容性,因为它从DefaultImpls类中删除该方法;使用-Xjvm-default=compatibility的话,除了生成default方法之外,还将defaultImpls类中生成兼容性访问器,该访问器通过综合访问器调用default方法,在这种模式下,它是二进制兼容的,但是会导致字节码中有更多的方法。

build.gradle文件中加入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
allprojects {
repositories {
google()
jcenter()

}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = '1.8'
freeCompilerArgs += '-Xjvm-default=compatibility'
}
}
}

下面尝试上面的两种方法,看看是否符合预期,代码如下:

加上-Xjvm-default=enable后反编译的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\bf\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H&J\u0010\u0010\u0006\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0017ø\u0001\u0000\u0082\u0002\u0007\n\u0005\b\u0091(0\u0001¨\u0006\u0007"},
d2 = {"Lcom/tanjiajun/kotlindsldemo/MainActivity$Handlers;", "", "onLoginClick", "", "view", "Landroid/view/View;", "onLogoutClick", "app_debug"}
)
public interface Handlers {
void onLoginClick(@NotNull View var1);

@JvmDefault
default void onLogoutClick(@NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
}
}

加上-Xjvm-default=compatibility后反编译的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\bf\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H&J\u0010\u0010\u0006\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0017ø\u0001\u0000\u0082\u0002\u0007\n\u0005\b\u0091(0\u0001¨\u0006\u0007"},
d2 = {"Lcom/tanjiajun/kotlindsldemo/MainActivity$Handlers;", "", "onLoginClick", "", "view", "Landroid/view/View;", "onLogoutClick", "app_debug"}
)
public interface Handlers {
void onLoginClick(@NotNull View var1);

@JvmDefault
default void onLogoutClick(@NotNull View view) {
Intrinsics.checkParameterIsNotNull(view, "view");
}

@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 3
)
public static final class DefaultImpls {
@JvmDefault
public static void onLogoutClick(MainActivity.Handlers $this, @NotNull View view) {
$this.onLogoutClick(view);
}
}
}

可以看到加上了-Xjvm-default=compatibility要比-Xjvm-default=enable多了一个DefaultImpls的静态final类,而且类中也有一个静态方法,其实-Xjvm-default=compatibility是为了兼容Java8之前的版本在接口中也可以实现方法。

Spek

spek是一个为Kotlin打造的测试框架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
describe("Verify Check Email Valid") {
it("Email Is Null") {
presenter.checkEmailValid("")
verify { viewRenderer.showEmailEmptyError() }
}

it("Email Is Not Null and Is Not Valid") {
presenter.checkEmailValid("ktan")
verify { viewRenderer.showEmailInvalidError() }
}

it("Email Is Not Null and Is Valid") {
presenter.checkEmailValid("ktan@xogrp.com")
verify { viewRenderer.hideEmailError() }
}
}

Github:Spek

KxDate

一个日期处理库,可以写出类似于英语句子的代码:

1
2
val twoMonthsLater = 2 months fromNow
val yesterday = 1 days ago

Github:KxDate

Anko

一个专门针对Android开发的Kotlin库,可以这样写:

1
2
3
4
5
6
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { toast("Hello, ${name.text}!") }
}
}

Github:Anko

Demo地址

  • Copyrights © 2019-2020 Tyler Liu

请我喝杯咖啡吧~

支付宝
微信