不论是登出还是中断连线,在App中我最後都会希望回到登入画面,因此在nav_main.xml中增加了Action:
<action
android:id="@+id/action_to_loginFragment"
app:destination="@id/loginFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@id/searchArticleFragment"
app:popUpToInclusive="true" />
此Action为Global Action,是放在navigation tag下而非fragment tag。
登出发生的时间点比较单纯,预期是在SearchArticleFragment页面点击返回按钮的时候,所以先用onBackPressedDispatcher拦截返回事件并在里面跳出Dialog询问是否登出:
// ...
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
AlertDialog.Builder(requireActivity())
.setTitle("登出")
.setMessage("是否登出Ptt?")
.setPositiveButton(android.R.string.ok) { _, _ ->
//TODO: Logout Process
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
})
// ...
// ...
val activity = (requireActivity() as MainActivity)
activity.showLoading("Logout...")
PttClient.getInstance().end()
activity.startPttClient {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
activity.dismissLoading()
if (it == 0) {
NavHostFragment.findNavController(this@SearchArticleFragment)
.navigate(R.id.action_to_loginFragment)
} else {
activity.showConnectionErrorDialog()
}
}
}
// ...
可以看到登出的部分我不打算走Ptt的指令流程。直接将WebSocket连线切断後重连就好了。MainActivity.startPttClient是稍微改写WelcomeFragment页的程序码,并将PttClient.expect的值做为callback回传。
public val startPattern = arrayOf(
"请输入代号,或以 guest 参观,或以 new 注册:"
)
public fun startPttClient(callback: ((Int) -> Unit)) {
lifecycleScope.launch(Dispatchers.IO) {
delay(500)
PttClient.getInstance().start()
callback(PttClient.getInstance().expect(startPattern))
}
}
这边做完後WelcomeFragment使用到的地方也一起做了修改:
private fun startPttClient() {
if (checkOverlayDisplayPermission()) {
(requireActivity() as MainActivity).startPttClient {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
delay(500)
withContext(Dispatchers.Main) {
if (it == 0) {
val extras = FragmentNavigator.Extras.Builder()
.addSharedElement(binding.logo, "logo")
.build()
NavHostFragment.findNavController(this@WelcomeFragment)
.navigate(
R.id.action_welcomeFragment_to_loginFragment,
null,
null,
extras
)
} else {
(requireActivity() as MainActivity).showConnectionErrorDialog()
}
}
}
}
} else {
requestOverlayDisplayPermission()
}
}
连线中断的模拟我是使用另外登入同帐号的方法。
companion object {
// ...
private var isClosedByUser = false
public var unexpectedClosedCallback: (() -> Unit)? = null
// ...
}
isClosedByUser flag是用来标注这次的onClose事件是否是发生在App主动断线(如:登出)时:
public fun start() {
// ...
isClosedByUser = false
// ...
}
public fun end() {
isClosedByUser = true
// ...
}
并且在WebSocketClient的onClose中呼叫unexpectedClosedCallback
override fun onClose(code: Int, reason: String?, remote: Boolean) {
if (!isClosedByUser) {
unexpectedClosedCallback?.invoke()
}
}
PttClient.unexpectedClosedCallback = {
lifecycleScope.launch(Dispatchers.Main) {
AlertDialog.Builder(this@MainActivity)
.setTitle("Disconnected")
.setMessage("Reconnect to Ptt?")
.setPositiveButton(android.R.string.ok) { _, _ ->
showLoading("Reconnecting...")
PttClient.getInstance().end()
startPttClient {
lifecycleScope.launch(Dispatchers.Main) {
dismissLoading()
if (it == 0) {
findNavController(R.id.nav_host_fragment)
.navigate(R.id.action_to_loginFragment)
} else {
showConnectionErrorDialog()
}
}
}
}
.setNegativeButton(android.R.string.cancel) { _, _ -> finish() }
.setCancelable(false)
.show()
}
}
基本上就是走关闭→重启→回到登入页的流程,若不重连线或是再连线失败的话就关闭App。
如果是在悬浮视窗中断线的话,我选择的做法是关闭Service并发送Notification告知使用者连线中断。
以下内容写在ObserveService的onCreate方法中:
PttClient.unexpectedClosedCallback = {
val manager = NotificationManagerCompat.from(this)
if (manager.getNotificationChannel(notificationChannelIdImportant) == null) {
val channel = NotificationChannelCompat.Builder(
notificationChannelIdImportant,
NotificationManager.IMPORTANCE_HIGH
).setName("CA_HIGH").build()
manager.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(this, notificationChannelIdImportant)
.setSmallIcon(R.drawable.ic_outline_track)
.setContentTitle("Ptt connection has closed")
manager.notify(notificationIdImportant, builder.build())
stopSelf()
}
今天测试的时间比较长,所以使用了三倍速播放,看不清楚又很有兴趣的话,就麻烦多看几次罗...XD
<<: [Day28] Flutter with GetX Socket.io
>>: Day 27 LeetCode 212. Word Search II
Mindmap (思维导图、心智图) 是一个被广泛用在任何领域上的一种作图法,可以帮我们归类思绪、分...
再熟悉不过的字串,也算是资料结构? 我们常常使用的字串,也算是一种有顺序关系的「序列容器」,因此...
前言: 经过多次的测试和训练 val_accuracy 在最後几乎都是处於0.6500左右的状态 所...
Twinkle Tray是一款支持多显示器的屏幕亮度调节工具,让你可以在一块屏幕上调节所有的显示器亮...
在11/19 Tableau有一场线上的大中华区直播分享会,不知道大家有没有追里面的内容? 以下分享...