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.util.Log
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.services.CallForegroundService
import ru.f7cloud.talk.utils.bundle.BundleKeys
@@ -22,6 +26,9 @@ object TalkNativeCallLauncher {
private const val TAG = "TalkNativeCallLauncher"
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(
context: Context,
session: AuthSession,
@@ -83,26 +90,30 @@ object TalkNativeCallLauncher {
}
return
}
if (!TalkVendorBootstrap.ensureSessionReady(context, session)) {
showError(context, "Не удалось подготовить Talk для звонка")
return
}
val token = callContext.roomToken.trim()
if (token.isBlank()) {
showError(context, "Некорректная комната звонка")
return
}
runCatching {
startCallActivity(
context,
buildCallIntent(context, session, callContext, incomingFromNotification, suppressIncomingRingtone),
)
}.onFailure { error ->
Log.e(TAG, "Native CallActivity launch failed", error)
if (TalkNativeConfig.allowWebViewCallFallback) {
launchWebViewFallback(context, session, token, incomingFromNotification)
} else {
showError(context, "Не удалось начать звонок")
// Подготовка vendor-юзера — suspend (IO): раньше runBlocking держал main
// на пути звонка (ANR-риск на входящем звонке, аудит C4). Запуск activity — на main.
launchScope.launch {
if (!TalkVendorBootstrap.ensureSessionReady(context, session)) {
showError(context, "Не удалось подготовить Talk для звонка")
return@launch
}
runCatching {
startCallActivity(
context,
buildCallIntent(context, session, callContext, incomingFromNotification, suppressIncomingRingtone),
)
}.onFailure { error ->
Log.e(TAG, "Native CallActivity launch failed", error)
if (TalkNativeConfig.allowWebViewCallFallback) {
launchWebViewFallback(context, session, token, incomingFromNotification)
} else {
showError(context, "Не удалось начать звонок")
}
}
}
}
@@ -2,6 +2,10 @@ package ru.forbion.f7cloud.feature.talknative
import android.content.Context
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.AuthStore
@@ -10,15 +14,20 @@ import ru.forbion.f7cloud.core.auth.AuthStore
*/
object TalkVendorBootstrap {
private const val TAG = "TalkVendorBootstrap"
private val warmupScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
/** Прогрев vendor-юзера на старте — в фоне (раньше блокировал main в Application.onCreate). */
fun onApplicationCreate(context: Context) {
if (!TalkNativeConfig.bootstrapVendorRuntime) return
val session = AuthStore(context).load() ?: return
runCatching { TalkVendorUserSync.ensureUser(context, session) }
.onFailure { Log.w(TAG, "Talk user sync failed: ${it.message}") }
val appContext = context.applicationContext
warmupScope.launch {
val session = AuthStore(appContext).load() ?: return@launch
runCatching { TalkVendorUserSync.ensureUser(appContext, session) }
.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
return runCatching { TalkVendorUserSync.ensureUser(context, session) }
.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.f7cloud.F7TalkUserSync
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import ru.forbion.f7cloud.core.auth.AuthSession
/**
* Ensures talk-android [ru.f7cloud.talk.data.user.model.User] exists for F7cloud sessions.
* Required before native [ru.f7cloud.talk.activities.CallActivity] can run.
*
* suspend (IO) — раньше был runBlocking на main (старт приложения и путь звонка),
* что давало ANR-риск из аудита (C4/п.7).
*/
object 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) {
Log.w(TAG, "Application is not F7cloudTalkApplication, skip sync")
return false
}
return runBlocking(Dispatchers.IO) {
return withContext(Dispatchers.IO) {
runCatching {
F7TalkUserSync.get().ensureUser(
serverUrl = session.serverUrl,