【问题标题】:Kotlin Flows Java Interop CallbackKotlin Flows Java 互操作回调
【发布时间】:2020-06-21 14:46:45
【问题描述】:

当我想将 Kotlin Flows 与普通回调一起使用时,我一直在寻找合适的解决方案或最佳实践。我的用例是我编写了一个在内部使用 Kotlin Flow 的 kotlin 库,并且我必须假设用户将使用 Java。所以我认为最好的解决方案是为我的流方法重载一个基本的回调接口,并在collect 中调用它,如下所示:

class KotlinClass {

    interface Callback {
        fun onResult(result: Int)
    }

    private fun foo() = flow {
        for (i in 1..3) {
            emit(i)
        }
    }

    fun bar(callback: Callback) {
        runBlocking {
            foo().collect { callback.onResult(it) }
        }
    }

  private fun main() {
    bar(object : Callback {
        override fun onResult(result: Int) {
            TODO("Not yet implemented")
        }
    })
}

在我的 Java 应用程序中,我可以像这样简单地使用它:

public class JavaClass {

    public void main() {
        KotlinClass libraryClass = new KotlinClass();
        libraryClass.bar(new KotlinClass.Callback() {
            @Override
            public void onResult(int result) {
                // TODO("Not yet implemented")
            }
        });
    } 
}

我不确定要走什么路,因为我希望我的 Kotlin 库使用 Flows 以良好的方式用于 Java 和 Kotlin。

我遇到了callbackFlow,但这似乎只是当我想让它称之为 flow-ify 一个基于回调的 API 时?因为我对 Kotlin 和 Flows 很陌生,如果我的问题因缺少 kotlin 的一些基本概念而存在缺陷,请道歉。

【问题讨论】:

    标签: java kotlin kotlin-coroutines kotlin-flow


    【解决方案1】:

    您无需在 Kotlin 代码中创建接口。你可以这样定义 bar:

     fun bar(callback: (Int) -> Unit) {
         runBlocking {
             foo().collect { callback(it) }
         }
     }
    

    从 Java 代码中,您可以这样调用函数:

    public class JavaClass {
    
        public static void main(String[] args) {
            KotlinClass libraryClass = new KotlinClass();
            libraryClass.bar(v -> { System.out.println(v); return Unit.INSTANCE; });
        } 
    }
    

    【讨论】:

    • 这是一个有效的建议,但我认为在我的 java 代码中使用 return Unit.INSTANCE 会是不好的做法,如下所述:developer.android.com/kotlin/interop?要在我的 java 代码中使用 Unit 类,我是否需要添加一些 kotlin 依赖项,因为我无法立即使用它?
    • 这个文本说“目前没有办法从 Java 和 Kotlin 中定义一个参数类型来用作 lambda,这样它在两种语言中都是惯用的。目前的建议是更喜欢这个函数type 尽管返回类型为 Unit 时 Java 的体验会下降。”。如果您在 Java 项目中使用 kotlin,它应该已经包含 Kotlin stdlib 作为依赖项,不是吗?如果您的 lib 将专门从 Java 中使用,您还可以尝试最后一个建议“在 Java 中定义命名的 SAM 接口”。
    【解决方案2】:

    我会给 Java 客户端更多的流控制权。我会在你的回调接口中添加一个onStartonCompletion 方法。除此之外,我会使用自己的CoroutineScope - 可以从 Java 客户端定制。而且我不会阻止 Kotlin 函数中的调用线程 - 没有 runBlocking

    @InternalCoroutinesApi
    class KotlinClass {
        val coroutineScope = CoroutineScope(Dispatchers.Default)
    
        interface FlowCallback {
            @JvmDefault
            fun onStart() = Unit
    
            @JvmDefault
            fun onCompletion(thr: Throwable?) = Unit
            fun onResult(result: Int)
        }
    
        private fun foo() = flow {
            for (i in 1..3) {
                emit(i)
            }
        }
    
        fun bar(flowCallback: FlowCallback) {
            coroutineScope.launch {
                foo().onStart { flowCallback.onStart() }
                    .onCompletion { flowCallback.onCompletion(it) }
                    .collect { flowCallback.onResult(it) }
            }
        }
    
        fun close() {
            coroutineScope.cancel()
        }    
    }
    

    现在 Java 客户端可以完全控制如何启动、收集和取消流程。例如,您可以使用闩锁等待完成、设置超时并取消协同程序范围。这首先看起来像很多代码,但通常您需要这种灵活性。

    public class JavaClass {
        public static void main(String[] args) throws InterruptedException {
            CountDownLatch latch = new CountDownLatch(1);
            KotlinClass libraryClass = new KotlinClass();
            libraryClass.bar(new KotlinClass.FlowCallback() {
                @Override
                public void onCompletion(@Nullable Throwable thr) {
                    latch.countDown();
                }
    
    
                @Override
                public void onResult(int result) {
                    System.out.println(result);
                }
            });
    
            try {
                latch.await(5, TimeUnit.SECONDS);
            } finally {
                libraryClass.close();
            }
        }
    }
    

    【讨论】:

    • 感谢您的回答,我不完全理解 countdownLatch 的部分,我真的需要从我的 Java 库中关闭 coroutineScope 还是我可以在它关闭时终止它在我的 Kotlin 库中完成?
    • 您也可以从 Kotlin 关闭 coroutineScope,阻塞调用线程也是如此。但你的问题是,什么是好的 API。如果你在你的图书馆里这样做,你就会非常固执己见,客户如何使用你。从我的角度来看,最好让客户端决定阻塞线程是否是一个好主意以及超时时间。
    • 事情是我真的想隐藏 kotlin 库中的所有线程,而 java 库应该只为每个收集的流结果在主线程上获得回调
    • 感谢您为 Kotlin Flow 的 Java 互操作提供这些想法 :heart:
    【解决方案3】:

    以防万一有人想知道通用解决方案。这是来自@rene answer here 的增强版本。

    1. 接受泛型类型
    2. 一个可配置的coroutineScope
    // JavaFlow.kt
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.InternalCoroutinesApi
    import kotlinx.coroutines.cancel
    import kotlinx.coroutines.flow.Flow
    import kotlinx.coroutines.flow.collect
    import kotlinx.coroutines.flow.onCompletion
    import kotlinx.coroutines.flow.onStart
    import kotlinx.coroutines.launch
    
    @InternalCoroutinesApi
    class JavaFlow<T>(
        private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
    ) {
    
        interface OperatorCallback <T> {
            @JvmDefault
            fun onStart() = Unit
    
            @JvmDefault
            fun onCompletion(thr: Throwable?) = Unit
            fun onResult(result: T)
        }
    
        fun collect(
            flow: Flow<T>,
            operatorCallback: OperatorCallback<T>,
        ) {
            coroutineScope.launch {
                flow
                    .onStart { operatorCallback.onStart() }
                    .onCompletion { operatorCallback.onCompletion(it) }
                    .collect { operatorCallback.onResult(it) }
            }
        }
    
        fun close() {
            coroutineScope.cancel()
        }
    }
    

    Java 调用方:

    // code omitted...
    new JavaFlow<File>().collect(
            // compressImageAsFlow is our actual kotlin flow extension
            FileUtils.compressImageAsFlow(file, activity),
            new JavaFlow.OperatorCallback<File>() {
                @Override
                public void onResult(File result) {
                    // do something with the result here
                    SafeSingleton.setFile(result);
                }
            }
    );
    
    
    // or using lambda with method references
    // new JavaFlow<File>().collect(
    //        FileUtils.compressImageAsFlow(file, activity),
    //        SafeSingleton::setFile
    // );
    
    // Change coroutineScope to Main
    // new JavaFlow<File>(CoroutineScopeKt.MainScope()).collect(
    //        FileUtils.compressImageAsFlow(file, activity),
    //        SafeSingleton::setFile
    // );
    

    OperatorCallback.onStartOperatorCallback.onCompletion 是可选的,根据需要覆盖它。

    【讨论】:

      猜你喜欢
      • 2018-06-18
      • 2022-08-18
      • 2015-03-27
      • 1970-01-01
      • 2015-12-12
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多