talk-native: убран runBlocking со старта и пути звонка (аудит C4/п.7)

TalkVendorUserSync.ensureUser → suspend(IO). Прогрев на старте приложения —
в фоновом scope (раньше блокировал main в Application.onCreate, вкл. чтение
AuthStore/Keystore). Путь звонка: подготовка vendor-юзера в корутине (IO),
запуск CallActivity на main — убран ANR-риск на входящем звонке.
Поведение то же: при неготовности — тот же тост, фолбэк WebView сохранён.
This commit is contained in:
b-dev-mobile
2026-07-09 12:58:55 +00:00
parent d1d61d0cf8
commit 83e071673c
3 changed files with 45 additions and 22 deletions
@@ -5,6 +5,10 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import ru.f7cloud.talk.activities.CallActivity import ru.f7cloud.talk.activities.CallActivity
import ru.f7cloud.talk.services.CallForegroundService import ru.f7cloud.talk.services.CallForegroundService
import ru.f7cloud.talk.utils.bundle.BundleKeys import ru.f7cloud.talk.utils.bundle.BundleKeys
@@ -22,6 +26,9 @@ object TalkNativeCallLauncher {
private const val TAG = "TalkNativeCallLauncher" private const val TAG = "TalkNativeCallLauncher"
private const val WEBVIEW_CALL_ACTIVITY = "ru.forbion.f7cloud.feature.talk.TalkCallActivity" private const val WEBVIEW_CALL_ACTIVITY = "ru.forbion.f7cloud.feature.talk.TalkCallActivity"
// Main-scope для запуска звонка после suspend-подготовки vendor-юзера (IO)
private val launchScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
fun launchIncomingCall( fun launchIncomingCall(
context: Context, context: Context,
session: AuthSession, session: AuthSession,
@@ -83,15 +90,18 @@ object TalkNativeCallLauncher {
} }
return return
} }
if (!TalkVendorBootstrap.ensureSessionReady(context, session)) {
showError(context, "Не удалось подготовить Talk для звонка")
return
}
val token = callContext.roomToken.trim() val token = callContext.roomToken.trim()
if (token.isBlank()) { if (token.isBlank()) {
showError(context, "Некорректная комната звонка") showError(context, "Некорректная комната звонка")
return return
} }
// Подготовка vendor-юзера — suspend (IO): раньше runBlocking держал main
// на пути звонка (ANR-риск на входящем звонке, аудит C4). Запуск activity — на main.
launchScope.launch {
if (!TalkVendorBootstrap.ensureSessionReady(context, session)) {
showError(context, "Не удалось подготовить Talk для звонка")
return@launch
}
runCatching { runCatching {
startCallActivity( startCallActivity(
context, context,
@@ -106,6 +116,7 @@ object TalkNativeCallLauncher {
} }
} }
} }
}
private fun buildCallIntent( private fun buildCallIntent(
context: Context, context: Context,
@@ -2,6 +2,10 @@ package ru.forbion.f7cloud.feature.talknative
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import ru.forbion.f7cloud.core.auth.AuthSession import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.auth.AuthStore import ru.forbion.f7cloud.core.auth.AuthStore
@@ -10,15 +14,20 @@ import ru.forbion.f7cloud.core.auth.AuthStore
*/ */
object TalkVendorBootstrap { object TalkVendorBootstrap {
private const val TAG = "TalkVendorBootstrap" private const val TAG = "TalkVendorBootstrap"
private val warmupScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
/** Прогрев vendor-юзера на старте — в фоне (раньше блокировал main в Application.onCreate). */
fun onApplicationCreate(context: Context) { fun onApplicationCreate(context: Context) {
if (!TalkNativeConfig.bootstrapVendorRuntime) return if (!TalkNativeConfig.bootstrapVendorRuntime) return
val session = AuthStore(context).load() ?: return val appContext = context.applicationContext
runCatching { TalkVendorUserSync.ensureUser(context, session) } warmupScope.launch {
val session = AuthStore(appContext).load() ?: return@launch
runCatching { TalkVendorUserSync.ensureUser(appContext, session) }
.onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") } .onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") }
} }
}
fun ensureSessionReady(context: Context, session: AuthSession): Boolean { suspend fun ensureSessionReady(context: Context, session: AuthSession): Boolean {
if (!TalkNativeConfig.bootstrapVendorRuntime) return true if (!TalkNativeConfig.bootstrapVendorRuntime) return true
return runCatching { TalkVendorUserSync.ensureUser(context, session) } return runCatching { TalkVendorUserSync.ensureUser(context, session) }
.onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") } .onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") }
@@ -5,22 +5,25 @@ import android.util.Log
import ru.f7cloud.talk.application.F7cloudTalkApplication import ru.f7cloud.talk.application.F7cloudTalkApplication
import ru.f7cloud.talk.f7cloud.F7TalkUserSync import ru.f7cloud.talk.f7cloud.F7TalkUserSync
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext
import ru.forbion.f7cloud.core.auth.AuthSession import ru.forbion.f7cloud.core.auth.AuthSession
/** /**
* Ensures talk-android [ru.f7cloud.talk.data.user.model.User] exists for F7cloud sessions. * Ensures talk-android [ru.f7cloud.talk.data.user.model.User] exists for F7cloud sessions.
* Required before native [ru.f7cloud.talk.activities.CallActivity] can run. * Required before native [ru.f7cloud.talk.activities.CallActivity] can run.
*
* suspend (IO) — раньше был runBlocking на main (старт приложения и путь звонка),
* что давало ANR-риск из аудита (C4/п.7).
*/ */
object TalkVendorUserSync { object TalkVendorUserSync {
private const val TAG = "TalkVendorUserSync" private const val TAG = "TalkVendorUserSync"
fun ensureUser(context: Context, session: AuthSession): Boolean { suspend fun ensureUser(context: Context, session: AuthSession): Boolean {
if (context.applicationContext !is F7cloudTalkApplication) { if (context.applicationContext !is F7cloudTalkApplication) {
Log.w(TAG, "Application is not F7cloudTalkApplication, skip sync") Log.w(TAG, "Application is not F7cloudTalkApplication, skip sync")
return false return false
} }
return runBlocking(Dispatchers.IO) { return withContext(Dispatchers.IO) {
runCatching { runCatching {
F7TalkUserSync.get().ensureUser( F7TalkUserSync.get().ensureUser(
serverUrl = session.serverUrl, serverUrl = session.serverUrl,