fix: double back quitting
连接时导致界面无响应的问题没修好
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
@@ -26,6 +25,9 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- <profileable android:shell="true" /> -->
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,9 +1,7 @@
|
||||
package io.github.miuzarte.scrcpyforandroid.pages
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -61,6 +59,7 @@ import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonCard
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -73,6 +72,7 @@ import java.net.Socket
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val ADB_CONNECT_TIMEOUT_MS = 3_000L
|
||||
@@ -125,7 +125,6 @@ fun DeviceTabScreen(
|
||||
nativeCore: NativeCoreFacade,
|
||||
snack: SnackbarHostState,
|
||||
scrollBehavior: ScrollBehavior,
|
||||
keepScreenOnWhenStreamingEnabled: Boolean,
|
||||
virtualButtonsOutside: List<String>,
|
||||
virtualButtonsInMore: List<String>,
|
||||
previewCardHeightDp: Int,
|
||||
@@ -222,9 +221,19 @@ fun DeviceTabScreen(
|
||||
val virtualButtonLayout = remember(virtualButtonsOutside, virtualButtonsInMore) {
|
||||
VirtualButtonActions.resolveLayout(virtualButtonsOutside, virtualButtonsInMore)
|
||||
}
|
||||
val activity = remember(context) { context as? Activity }
|
||||
val initialSettings = remember(context) { loadDevicePageSettings(context) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val adbWorkerDispatcher = remember {
|
||||
Executors.newSingleThreadExecutor { runnable ->
|
||||
Thread(runnable, "adb-connect-worker").apply { isDaemon = true }
|
||||
}.asCoroutineDispatcher()
|
||||
}
|
||||
|
||||
DisposableEffect(adbWorkerDispatcher) {
|
||||
onDispose {
|
||||
adbWorkerDispatcher.close()
|
||||
}
|
||||
}
|
||||
|
||||
var busy by rememberSaveable { mutableStateOf(false) }
|
||||
var statusLine by rememberSaveable { mutableStateOf("未连接") }
|
||||
@@ -315,6 +324,46 @@ fun DeviceTabScreen(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun disconnectAdbConnection(
|
||||
clearQuickOnlineForTarget: ConnectionTarget? = currentTarget,
|
||||
logMessage: String? = null,
|
||||
showSnackMessage: String? = null,
|
||||
) {
|
||||
withContext(adbWorkerDispatcher) {
|
||||
runCatching { nativeCore.scrcpyStop() }
|
||||
runCatching { nativeCore.adbDisconnect() }
|
||||
}
|
||||
adbConnected = false
|
||||
currentTargetHost = ""
|
||||
currentTargetPort = AppDefaults.ADB_PORT
|
||||
audioForwardingSupported = true
|
||||
cameraMirroringSupported = true
|
||||
sessionInfo = null
|
||||
statusLine = "未连接"
|
||||
connectedDeviceLabel = "未连接"
|
||||
clearQuickOnlineForTarget?.let { target ->
|
||||
if (target.host.isNotBlank()) {
|
||||
upsertQuickDevice(context, quickDevices, target.host, target.port, false)
|
||||
}
|
||||
}
|
||||
logMessage?.let { logEvent(it) }
|
||||
if (!showSnackMessage.isNullOrBlank()) {
|
||||
scope.launch {
|
||||
snack.showSnackbar(showSnackMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun disconnectCurrentTargetBeforeConnecting(newHost: String, newPort: Int) {
|
||||
val current = currentTarget
|
||||
if (!adbConnected || current == null) return
|
||||
if (current.host == newHost && current.port == newPort) return
|
||||
|
||||
sessionReconnectBlacklistHosts += current.host
|
||||
logEvent("切换连接目标,先断开当前设备: ${current.host}:${current.port}")
|
||||
disconnectAdbConnection(clearQuickOnlineForTarget = current)
|
||||
}
|
||||
|
||||
fun applyConnectedDeviceCapabilities(sdkInt: Int, release: String) {
|
||||
val audioSupported = sdkInt !in 0..<30
|
||||
audioForwardingSupported = audioSupported
|
||||
@@ -337,7 +386,7 @@ fun DeviceTabScreen(
|
||||
}
|
||||
|
||||
suspend fun connectWithTimeout(host: String, port: Int): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
return withContext(adbWorkerDispatcher) {
|
||||
withTimeout(ADB_CONNECT_TIMEOUT_MS) {
|
||||
nativeCore.adbConnect(host, port)
|
||||
}
|
||||
@@ -345,7 +394,7 @@ fun DeviceTabScreen(
|
||||
}
|
||||
|
||||
suspend fun keepAliveCheck(host: String, port: Int): Boolean {
|
||||
return withContext(Dispatchers.IO) {
|
||||
return withContext(adbWorkerDispatcher) {
|
||||
withTimeout(ADB_KEEPALIVE_TIMEOUT_MS) {
|
||||
val connected = nativeCore.adbIsConnected()
|
||||
if (!connected) {
|
||||
@@ -381,7 +430,9 @@ fun DeviceTabScreen(
|
||||
} catch (e: IllegalArgumentException) {
|
||||
val detail = e.message?.takeIf { it.isNotBlank() } ?: e.javaClass.simpleName
|
||||
logEvent("$label 参数错误: $detail", Log.WARN, e)
|
||||
snack.showSnackbar("$label 参数错误: $detail")
|
||||
scope.launch {
|
||||
snack.showSnackbar("$label 参数错误: $detail")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val detail = e.message?.takeIf { it.isNotBlank() } ?: e.javaClass.simpleName
|
||||
logEvent("$label 失败: $detail", Log.ERROR, e)
|
||||
@@ -403,7 +454,9 @@ fun DeviceTabScreen(
|
||||
} catch (e: IllegalArgumentException) {
|
||||
val detail = e.message?.takeIf { it.isNotBlank() } ?: e.javaClass.simpleName
|
||||
logEvent("$label 参数错误: $detail", Log.WARN, e)
|
||||
snack.showSnackbar("$label 参数错误: $detail")
|
||||
scope.launch {
|
||||
snack.showSnackbar("$label 参数错误: $detail")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val detail = e.message?.takeIf { it.isNotBlank() } ?: e.javaClass.simpleName
|
||||
logEvent("$label 失败: $detail", Log.ERROR, e)
|
||||
@@ -414,6 +467,16 @@ fun DeviceTabScreen(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun runAutoAdbConnect(host: String, port: Int): Boolean {
|
||||
return runCatching {
|
||||
connectWithTimeout(host, port)
|
||||
}.getOrElse { error ->
|
||||
val detail = error.message?.takeIf { it.isNotBlank() } ?: error.javaClass.simpleName
|
||||
logEvent("自动重连失败: $host:$port ($detail)", Log.WARN)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshEncoderLists() {
|
||||
if (!adbConnected) return
|
||||
val remotePath = serverRemotePath.trim().ifBlank { AppDefaults.SERVER_REMOTE_PATH }
|
||||
@@ -627,10 +690,8 @@ fun DeviceTabScreen(
|
||||
snack.showSnackbar("ADB 自动重连成功")
|
||||
}
|
||||
} else {
|
||||
runCatching { nativeCore.adbDisconnect() }
|
||||
disconnectAdbConnection()
|
||||
statusLine = "ADB 连接断开"
|
||||
connectedDeviceLabel = "未连接"
|
||||
sessionInfo = null
|
||||
logEvent("ADB 自动重连失败: $host:$port", Log.ERROR)
|
||||
scope.launch {
|
||||
snack.showSnackbar("ADB 自动重连失败")
|
||||
@@ -662,20 +723,19 @@ fun DeviceTabScreen(
|
||||
if (!portReachable) continue
|
||||
|
||||
quickConnectTriedOnce += targetKey
|
||||
runAdbConnect("快速设备端口可达,尝试连接一次") {
|
||||
val ok = connectWithTimeout(target.host, target.port)
|
||||
adbConnected = ok
|
||||
upsertQuickDevice(
|
||||
context,
|
||||
quickDevices,
|
||||
target.host,
|
||||
target.port,
|
||||
ok
|
||||
)
|
||||
if (ok) {
|
||||
handleAdbConnected(target.host, target.port)
|
||||
logEvent("ADB 快速探测连接成功: ${target.host}:${target.port}")
|
||||
}
|
||||
val ok = runAutoAdbConnect(target.host, target.port)
|
||||
adbConnected = ok
|
||||
upsertQuickDevice(
|
||||
context,
|
||||
quickDevices,
|
||||
target.host,
|
||||
target.port,
|
||||
ok
|
||||
)
|
||||
if (ok) {
|
||||
handleAdbConnected(target.host, target.port)
|
||||
logEvent("ADB 快速探测连接成功: ${target.host}:${target.port}")
|
||||
break
|
||||
}
|
||||
}
|
||||
if (adbConnected) break
|
||||
@@ -727,22 +787,20 @@ fun DeviceTabScreen(
|
||||
continue
|
||||
}
|
||||
|
||||
runAdbConnect("自动重连 ADB") {
|
||||
val ok = connectWithTimeout(discoveredHost, discoveredPort)
|
||||
adbConnected = ok
|
||||
upsertQuickDevice(
|
||||
context,
|
||||
quickDevices,
|
||||
discoveredHost,
|
||||
discoveredPort,
|
||||
ok
|
||||
)
|
||||
if (ok) {
|
||||
handleAdbConnected(discoveredHost, discoveredPort)
|
||||
logEvent("ADB 自动重连成功: $discoveredHost:$discoveredPort")
|
||||
} else {
|
||||
logEvent("ADB 自动重连失败: $discoveredHost:$discoveredPort", Log.WARN)
|
||||
}
|
||||
val ok = runAutoAdbConnect(discoveredHost, discoveredPort)
|
||||
adbConnected = ok
|
||||
upsertQuickDevice(
|
||||
context,
|
||||
quickDevices,
|
||||
discoveredHost,
|
||||
discoveredPort,
|
||||
ok
|
||||
)
|
||||
if (ok) {
|
||||
handleAdbConnected(discoveredHost, discoveredPort)
|
||||
logEvent("ADB 自动重连成功: $discoveredHost:$discoveredPort")
|
||||
} else {
|
||||
logEvent("ADB 自动重连失败: $discoveredHost:$discoveredPort", Log.WARN)
|
||||
}
|
||||
|
||||
delay(ADB_AUTO_RECONNECT_RETRY_INTERVAL_MS)
|
||||
@@ -759,19 +817,6 @@ fun DeviceTabScreen(
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(activity, keepScreenOnWhenStreamingEnabled, sessionInfo != null) {
|
||||
val window = activity?.window
|
||||
val shouldKeepScreenOn = keepScreenOnWhenStreamingEnabled && sessionInfo != null
|
||||
if (window != null && shouldKeepScreenOn) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
onDispose {
|
||||
if (window != null && shouldKeepScreenOn) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(sessionInfo) {
|
||||
if (sessionInfo != null) {
|
||||
sessionInfoWidth = sessionInfo?.width ?: 0
|
||||
@@ -808,7 +853,6 @@ fun DeviceTabScreen(
|
||||
onClearLogsActionChange(null)
|
||||
onCanClearLogsChange(false)
|
||||
onOpenReorderDevicesActionChange(null)
|
||||
onSessionStartedChange(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -915,26 +959,18 @@ fun DeviceTabScreen(
|
||||
haptics.press()
|
||||
if (isConnectedTarget) {
|
||||
activeDeviceActionId = device.id
|
||||
runBusy("断开 ADB", onFinished = { activeDeviceActionId = null }) {
|
||||
nativeCore.adbDisconnect()
|
||||
runAdbConnect("断开 ADB", onFinished = { activeDeviceActionId = null }) {
|
||||
sessionReconnectBlacklistHosts += host
|
||||
adbConnected = false
|
||||
currentTargetHost = ""
|
||||
currentTargetPort = AppDefaults.ADB_PORT
|
||||
audioForwardingSupported = true
|
||||
cameraMirroringSupported = true
|
||||
sessionInfo = null
|
||||
statusLine = "未连接"
|
||||
connectedDeviceLabel = "未连接"
|
||||
upsertQuickDevice(context, quickDevices, host, port, false)
|
||||
logEvent("ADB 已断开: ${device.name}")
|
||||
scope.launch {
|
||||
snack.showSnackbar("ADB 已断开")
|
||||
}
|
||||
disconnectAdbConnection(
|
||||
clearQuickOnlineForTarget = ConnectionTarget(host, port),
|
||||
logMessage = "ADB 已断开: ${device.name}",
|
||||
showSnackMessage = "ADB 已断开",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
activeDeviceActionId = device.id
|
||||
runAdbConnect("连接 ADB", onFinished = { activeDeviceActionId = null }) {
|
||||
disconnectCurrentTargetBeforeConnecting(host, port)
|
||||
val ok = connectWithTimeout(host, port)
|
||||
adbConnected = ok
|
||||
upsertQuickDevice(context, quickDevices, host, port, ok)
|
||||
@@ -975,6 +1011,7 @@ fun DeviceTabScreen(
|
||||
onConnect = {
|
||||
val target = parseQuickTarget(quickConnectInput) ?: return@QuickConnectCard
|
||||
runAdbConnect("连接 ADB") {
|
||||
disconnectCurrentTargetBeforeConnecting(target.host, target.port)
|
||||
val ok = connectWithTimeout(target.host, target.port)
|
||||
adbConnected = ok
|
||||
upsertQuickDevice(context, quickDevices, target.host, target.port, ok)
|
||||
|
||||
@@ -4,6 +4,10 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.PredictiveBackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -23,6 +27,7 @@ import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -51,7 +56,9 @@ import io.github.miuzarte.scrcpyforandroid.services.loadMainSettings
|
||||
import io.github.miuzarte.scrcpyforandroid.services.saveMainSettings
|
||||
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.yukonga.miuix.kmp.basic.DropdownImpl
|
||||
import top.yukonga.miuix.kmp.basic.Icon
|
||||
import top.yukonga.miuix.kmp.basic.IconButton
|
||||
@@ -189,6 +196,7 @@ fun MainPage() {
|
||||
var openReorderDevicesAction by remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||
var canClearLogs by remember { mutableStateOf(false) }
|
||||
var showDeviceMenu by rememberSaveable { mutableStateOf(false) }
|
||||
var lastExitBackPressAtMs by rememberSaveable { mutableLongStateOf(0L) }
|
||||
var fullscreenOrientation by rememberSaveable {
|
||||
mutableIntStateOf(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
|
||||
}
|
||||
@@ -201,6 +209,19 @@ fun MainPage() {
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(activity, keepScreenOnWhenStreamingEnabled, sessionStarted) {
|
||||
val window = activity?.window
|
||||
val shouldKeepScreenOn = keepScreenOnWhenStreamingEnabled && sessionStarted
|
||||
if (window != null && shouldKeepScreenOn) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
onDispose {
|
||||
if (window != null && shouldKeepScreenOn) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(activity, currentRootScreen, fullscreenOrientation) {
|
||||
val targetOrientation = when (currentRootScreen) {
|
||||
is RootScreen.Fullscreen -> fullscreenOrientation
|
||||
@@ -275,13 +296,34 @@ fun MainPage() {
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
if (now - lastExitBackPressAtMs > 2_000L) {
|
||||
lastExitBackPressAtMs = now
|
||||
Toast.makeText(context, "再按一次返回键退出", Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
lastExitBackPressAtMs = 0L
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching { nativeCore.scrcpyStop() }
|
||||
runCatching { nativeCore.adbDisconnect() }
|
||||
}
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val canNavigateBack = rootBackStack.size > 1 ||
|
||||
pagerState.currentPage != MainTabDestination.Device.ordinal
|
||||
|
||||
PredictiveBackHandler(enabled = canNavigateBack) { progress ->
|
||||
BackHandler(enabled = currentRootScreen !is RootScreen.Fullscreen) {
|
||||
handleBackNavigation()
|
||||
}
|
||||
|
||||
PredictiveBackHandler(
|
||||
enabled = canNavigateBack && currentRootScreen !is RootScreen.Fullscreen
|
||||
) { progress ->
|
||||
try {
|
||||
progress.collect { }
|
||||
handleBackNavigation()
|
||||
@@ -380,7 +422,6 @@ fun MainPage() {
|
||||
nativeCore = nativeCore,
|
||||
snack = snackHostState,
|
||||
scrollBehavior = deviceScrollBehavior,
|
||||
keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
|
||||
virtualButtonsOutside = virtualButtonsOutside,
|
||||
virtualButtonsInMore = virtualButtonsInMore,
|
||||
customServerUri = customServerUri,
|
||||
|
||||
Reference in New Issue
Block a user