0

如何从在非活动类中运行的 ktor 路由 API 调用(例如 /POST)中获取另一个活动(registerForActivity)的结果?

背景:对于 Android 应用程序,我在非活动类 HttpServer.kt 中运行 ktor 服务器引擎“netty”。我需要在 ktor 的 Routing POST 处理程序中调用另一个应用程序的活动,所以我从 MainActivity.kt 传递了 'appCompatActivity'。这样做是因为,我认为 registerForActivityResult() 依赖于 UI/生命周期类。

如下运行时会出现问题,因为 registerForActivityResult() 需要更早运行(如 onCreate() ?),而我在这个非活动类中没有这样的类。而且,返回ActivityResult时要运行的回调需要调用ktor ApplicationCall的response,这也是一个suspend函数。

class HttpServer(
    private val applicationContext: AppCompatActivity
) {
    private val logger = LoggerFactory.getLogger(HttpServer::class.java.simpleName)
    private val server = createServer()

    private fun ApplicationCall.startSaleActivityForResult() {    //    <======   *
        val activityLauncherCustom =
            applicationContext.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
                if (result.resultCode == Activity.RESULT_OK || result.resultCode == Activity.RESULT_CANCELED) {
                    val transactionResultReturned = result.data
                    // Handle the returned result properly using transactionResultReturned
                    GlobalScope.launch {
                        respond(status = HttpStatusCode.OK, TransactionResponse())
                    }
                }
            }
        val intent = Intent()    
        // Ignoring statements to create proper action/data intent
        activityLauncherCustom.launch(intent)    //    <======   *
    }

    fun start() = server.start()

    fun stop() = server.stop(0, 0)

    private fun createServer(): NettyApplicationEngine {
        return GlobalScope.embeddedServer(Netty) {
            install(CallLogging)
            install(ContentNegotiation) {
                gson {
                    setPrettyPrinting()
                }
            }
            routing {
                route("/") {
                    post {
                        call.startSaleActivityForResult()    //    <======   *
                    }
                }
            }
        }
    }

    private fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration>
            CoroutineScope.embeddedServer(
        factory: ApplicationEngineFactory<TEngine, TConfiguration>,
        module: Application.() -> Unit
    ): TEngine {
        val environment = applicationEngineEnvironment {
            this.parentCoroutineContext = coroutineContext + parentCoroutineContext
            this.log = logger
            this.module(module)

            connector {
                this.port = 8081
            }
        }
        return embeddedServer(factory, environment)
    }
}

以上是我尝试过的,但给出了以下错误。而且我在这个非活动类上没有 onCreate 。

java.lang.IllegalStateException:LifecycleOwner com.youtap.upti.MainActivity@38dcf06 正在尝试注册,而当前状态为 RESUMED。LifecycleOwners 必须在开始之前调用 register。

任何解决此问题的建议将不胜感激。

下面与上面相同的片段作为屏幕截图显示来自 Android Studio 的声明/参数类型的帮助文本: 在此处输入图像描述

我从 MainActivity 的 onCreate() 调用这个服务器类: 在此处输入图像描述

4

1 回答 1

1

为了解决您的问题并隐藏复杂性,您可以创建一个中间类来启动活动并等待结果出现:

import kotlinx.coroutines.channels.Channel

class Repository(private val activity: MainActivity) {
    private val channel = Channel<Int>(1)

    suspend fun get(input: String): Int {
        activity.activityLauncher.launch(input)
        return channel.receive()
    }

    suspend fun callback(result: Int) {
        channel.send(result)
    }
}

您可以在类中存储对存储库和活动启动器的引用MainActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        CoroutineScope(Dispatchers.IO).launch {
            HttpServer(this@MainActivity).also { it.start() }
        }
    }

    val activityLauncher = registerForActivityResult(MySecondActivityContract()) { result ->
        GlobalScope.launch {
            repository.callback(result!!)
        }
    }

    val repository = Repository(this)
}

我的第二个活动和合同如下所示:

class ChildActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_child)

        val result = Intent()
        result.putExtra("name", 6666)
        result.data = Uri.parse("http://mydata")
        setResult(Activity.RESULT_OK, result)
        finish()
    }
}

class MySecondActivityContract : ActivityResultContract<String, Int?>() {

    override fun createIntent(context: Context, input: String?): Intent {
        return Intent(context, ChildActivity::class.java)
            .putExtra("my_input_key", input)
    }

    override fun parseResult(resultCode: Int, intent: Intent?): Int? = when {
        resultCode != Activity.RESULT_OK -> null
        else -> intent?.getIntExtra("name", 42)
    }

    override fun getSynchronousResult(context: Context, input: String?): SynchronousResult<Int?>? {
        return if (input.isNullOrEmpty()) SynchronousResult(42) else null
    }
}

最简单的部分是路由处理程序:

routing {
    route("/") {
        post {
            val result = (applicationContext as MainActivity).repository.get("input")
            call.respondText { result.toString() }
        }
    }
}

此解决方案有效,但同时仅处理一个请求,并且不健壮,因为Activity可能在 HTTP 服务器或存储库对象之前被破坏。

于 2021-10-18T13:55:48.993 回答