调用 Java 类中的 Kotlin 挂起函数

假设我们具有以下暂停函数:

suspend fun doSomething(): List<MyClass> { ... }

如果我想在我现有的 Java 类中调用这个函数(我现在还不能把它转换成 Kotlin)并得到它的返回值,我必须提供一个 Continuation<? super List<MyClass>>作为它的参数(显然)。

我的问题是,我如何实现一个。特别是它的 getContext接收器。

38821 次浏览

First, add org.jetbrains.kotlinx:kotlinx-coroutines-jdk8 module to your dependencies. In your Kotlin file define the following async function that corresponds to Java style of writing async APIs:

fun doSomethingAsync(): CompletableFuture<List<MyClass>> =
GlobalScope.future { doSomething() }

Now use doSomethingAsync from Java in the same way as you are using other asynchronous APIs in the Java world.

For coroutines 1.3.0 use this:

BuildersKt.launch(GlobalScope.INSTANCE,
Dispatchers.getMain(),//context to be ran on
CoroutineStart.DEFAULT,
(coroutineScope, continuation) -> suspendFunction(arguments)
);

For java < 8:

BuildersKt.launch(
GlobalScope.INSTANCE,
Dispatchers.getMain(),//context to be ran on
CoroutineStart.DEFAULT,
new Function2<CoroutineScope, Continuation<? super Unit>, Unit/*or your return type here*/>() {
@Override
public Unit/*or your return type here*/ invoke(CoroutineScope coroutineScope, Continuation<? super Unit> continuation) {
//do what you want
return Unit.INSTANCE; //or something with the defined type
}
}
);

My gradle file:

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"

Kotlin uses static classes for extension functions, launch is an extension function, so it is defined in BuildersKt. The first parameter is the target of the extension function, the rest are the parameters from the extension functions.

If you dont want to use org.jetbrains.kotlinx:kotlinx-coroutines-jdk8, I have a new idea.

Write below code in your kotlin project.

    @JvmOverloads
fun <R> getContinuation(onFinished: BiConsumer<R?, Throwable?>, dispatcher: CoroutineDispatcher = Dispatchers.Default): Continuation<R> {
return object : Continuation<R> {
override val context: CoroutineContext
get() = dispatcher


override fun resumeWith(result: Result<R>) {
onFinished.accept(result.getOrNull(), result.exceptionOrNull())
}
}
}

I write it in my Coroutines class

Then you can call your suspend function like:

            Coroutines coroutines = new Coroutines();
UserUtils.INSTANCE.login("user", "pass", coroutines.getContinuation(
(tokenResult, throwable) -> {
System.out.println("Coroutines finished");
System.out.println("Result: " + tokenResult);
System.out.println("Exception: " + throwable);
}
));

login() function is a suspend function.
suspend fun login(username: String, password: String): TokenResult

For your code, you can:

doSomething(getContinuation((result, throwable) -> {
//TODO
}));

Besides, you may want to run your callback code in different thread (e.g. Main thread), just use launch(Dispathers.Main) to wrap resumeWith()

I created interface class based on @Kenvix answer to make it compatible with old Android SDK (lower than API 24)

interface CoroutineCallback<RESULT> {


companion object {


@JvmOverloads
fun <R> call(
callback: CoroutineCallback<R>,
dispatcher: CoroutineDispatcher = Dispatchers.Default
): Continuation<R> {
return object : Continuation<R> {
override val context: CoroutineContext
get() = dispatcher


override fun resumeWith(result: Result<R>) {
callback.onComplete(result.getOrNull(), result.exceptionOrNull())
}
}
}
}


fun onComplete(result: RESULT?, error: Throwable?)
}

usage

class kotlinClass {
suspend doSomething(foo, bar) : FooBar {}
}




class javaClass {
void doSomething(){
kotlinClassObject.doSomething("foo", "bar", CoroutineCallback.Companion.call((fooBar, error) -> {
//do something with result or error
}));
}
}

now call suspend function from any java class by passing CoroutineCallback