fix: 绑定消息移到MainActivity处理,彻底解决配对失败

与解绑一样,BindFragment的collect可能在MQTT消息到达时还未注册,
导致消息丢失。改为Activity级别统一处理type=2绑定消息。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dongliang
2026-05-11 13:58:45 +09:30
parent 939d402d8c
commit f116b4e188
3 changed files with 70 additions and 56 deletions

View File

@@ -7,8 +7,13 @@ import android.view.View
import androidx.navigation.fragment.NavHostFragment
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.Gson
import com.xiaoqu.watch.R
import com.xiaoqu.watch.data.device.WatchBindInfo
import com.xiaoqu.watch.data.prefs.DevicePrefs
import com.xiaoqu.watch.databinding.ActivityMainBinding
import com.xiaoqu.watch.network.api.CommonApi
import com.xiaoqu.watch.network.safeApiCall
import com.xiaoqu.watch.event.AppEvent
import com.xiaoqu.watch.event.EventBus
import com.xiaoqu.watch.data.prefs.UserPrefs
@@ -52,6 +57,9 @@ class MainActivity : AppCompatActivity() {
/** NFC 任务打卡管理器 */
@Inject lateinit var nfcTaskManager: NfcTaskManager
@Inject lateinit var userPrefs: UserPrefs
@Inject lateinit var devicePrefs: DevicePrefs
@Inject lateinit var commonApi: CommonApi
@Inject lateinit var gson: Gson
/** OTA 更新弹窗 */
lateinit var updateDialog: UpdateDialogView
lateinit var notificationBanner: NotificationBannerView
@@ -121,6 +129,11 @@ class MainActivity : AppCompatActivity() {
notificationBanner.show(count)
}
}
2 -> {
// 绑定成功 → 存用户信息 → 跳首页
Timber.d("MainActivity: 收到绑定消息")
handleBindMessage(event.rawJson)
}
3 -> {
// 解绑 → 清除数据 → 跳绑定页(从任何页面都能跳)
Timber.d("MainActivity: 收到解绑消息")
@@ -229,6 +242,53 @@ class MainActivity : AppCompatActivity() {
}
}
// ===== 绑定处理 =====
/** 防止重复处理绑定消息 */
private var bindHandled = false
/** 处理 MQTT type=2 绑定消息Activity 级别,不受 Fragment 生命周期影响) */
private fun handleBindMessage(rawJson: String) {
if (bindHandled) return
bindHandled = true
try {
val bindInfo = gson.fromJson(rawJson, WatchBindInfo::class.java)
// 存入 UserPrefs
userPrefs.saveUser(
userId = bindInfo.userId,
mobile = bindInfo.mobile,
userName = bindInfo.userName,
headUrl = bindInfo.headUrl
)
// 异步调 API 确认(不阻塞导航)
activityScope.launch {
try {
val params = hashMapOf<String, Any>(
"imei" to devicePrefs.imei,
"userId" to bindInfo.userId
)
safeApiCall { commonApi.bindWatchConfirm(params) }
Timber.d("MainActivity: 绑定确认 API 已调用")
} catch (e: Exception) {
Timber.w(e, "MainActivity: 绑定确认 API 异常")
}
}
// 导航到首页
val navHost = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navHost.navController.navigate(R.id.action_global_to_home)
Timber.d("MainActivity: 绑定成功,已导航到首页")
} catch (e: Exception) {
Timber.e(e, "MainActivity: 绑定处理异常")
bindHandled = false
}
}
// ===== OTA 更新 =====
/** 设置更新弹窗按钮回调 */

View File

@@ -6,20 +6,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.gson.Gson
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.google.zxing.common.BitMatrix
import com.xiaoqu.watch.R
import com.xiaoqu.watch.data.device.WatchBindInfo
import com.xiaoqu.watch.data.prefs.DevicePrefs
import com.xiaoqu.watch.data.prefs.UserPrefs
import com.xiaoqu.watch.databinding.FragmentBindBinding
import com.xiaoqu.watch.event.AppEvent
import com.xiaoqu.watch.event.EventBus
import com.xiaoqu.watch.network.api.CommonApi
import com.xiaoqu.watch.network.safeApiCall
import com.xiaoqu.watch.ui.common.BaseFragment
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@@ -36,9 +30,7 @@ import javax.inject.Inject
class BindFragment : BaseFragment<FragmentBindBinding>() {
@Inject lateinit var devicePrefs: DevicePrefs
@Inject lateinit var userPrefs: UserPrefs
@Inject lateinit var eventBus: EventBus
@Inject lateinit var commonApi: CommonApi
@Inject lateinit var gson: Gson
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentBindBinding {
@@ -51,7 +43,8 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
// 生成并显示二维码
generateQrCode()
// 监听 MQTT 绑定消息
// 绑定消息由 MainActivity 统一处理Activity 级别,不受 Fragment 生命周期影响)
// 这里只监听用于显示 Loading 状态
observeBindEvent()
}
@@ -148,54 +141,10 @@ class BindFragment : BaseFragment<FragmentBindBinding>() {
* 3. 调用 bindWatchConfirm API 确认
* 4. 导航到首页
*/
/** 防重复处理标记 */
private var bindHandled = false
/** 收到绑定消息 → 仅切换 Loading 状态(导航由 MainActivity 统一处理) */
private fun handleBindSuccess(rawJson: String) {
// 防止重复处理MQTT 可能重发)
if (bindHandled) return
bindHandled = true
try {
Timber.d("绑定: 收到绑定消息 $rawJson")
// 切换到配对中状态
showLoading()
// 解析用户信息
val bindInfo = gson.fromJson(rawJson, WatchBindInfo::class.java)
// 存入 UserPrefs
userPrefs.saveUser(
userId = bindInfo.userId,
mobile = bindInfo.mobile,
userName = bindInfo.userName,
headUrl = bindInfo.headUrl
)
// 调用 API 确认绑定(异步,不阻塞导航)
viewLifecycleOwner.lifecycleScope.launch {
try {
val params = hashMapOf<String, Any>(
"imei" to devicePrefs.imei,
"userId" to bindInfo.userId
)
safeApiCall { commonApi.bindWatchConfirm(params) }
Timber.d("绑定: 确认 API 已调用")
} catch (e: Exception) {
Timber.w(e, "绑定: 确认 API 异常,不影响导航")
}
}
// 导航到首页(不等 API 返回,用户信息已存本地)
findNavController().navigate(R.id.action_bind_to_home)
} catch (e: Exception) {
Timber.e(e, "绑定: 处理绑定消息异常")
bindHandled = false // 异常时允许重试
// 异常时恢复二维码显示
showQrCode()
}
Timber.d("绑定: 收到绑定消息,显示 Loading")
showLoading()
}
/** 显示二维码状态 */

View File

@@ -9,6 +9,11 @@
app:destination="@id/bindFragment"
app:popUpTo="@id/nav_main" app:popUpToInclusive="true" />
<!-- 全局 action绑定成功后从任何页面跳转到首页清空回退栈 -->
<action android:id="@+id/action_global_to_home"
app:destination="@id/homeFragment"
app:popUpTo="@id/nav_main" app:popUpToInclusive="true" />
<!-- 启动分发页 -->
<fragment
android:id="@+id/splashFragment"