Android应用接收Firestore错误时崩溃,请求技术排查
Firestore错误处理失效导致应用崩溃的问题排查与修复
问题描述
我设置了Firestore的失败监听器,但应用还是会崩溃,搞不懂为什么没法处理Firestore返回的错误,现在连测试都没法进行。相关代码如下:
class LoadingActivity : AppCompatActivity() { private var prefStateFireBase: SharedPreferences? = null private var prefStateBd: SharedPreferences? = null private lateinit var binding: ActivityLoadingBinding private val db = Firebase.firestore private lateinit var webView: WebView @RequiresApi(Build.VERSION_CODES.M) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityLoadingBinding.inflate(layoutInflater) setContentView(binding.root) if (!isOnline(this)) { val intent = Intent(this@LoadingActivity, MainActivity::class.java) startActivity(intent) } else { prefStateFireBase = getSharedPreferences(Constants.PREF_STATE_FIRE_BASE, Context.MODE_PRIVATE) prefStateBd = getSharedPreferences(Constants.PREF_STATE_BD, Context.MODE_PRIVATE) webView = findViewById(R.id.webView) webView.settings.javaScriptEnabled = true val stateFireBase = prefStateFireBase?.getString(Constants.SAVE_STATE_FIRE_BASE, "") val stateBd = prefStateBd?.getString(Constants.SAVE_STATE_BD, "") if (stateFireBase == "" && stateBd == "") { db.collection("database").document("check") .get() .addOnSuccessListener { document -> if (document.data != null) { if (document.data.toString().contains("h")) { saveStateFireBase(document.data.toString().dropWhile { it != 'h' } .dropLast(1)) webView.loadUrl( setLink( document.data.toString().dropWhile { it != 'h' } .dropLast(1))) CoroutineScope(Dispatchers.Default).launch { val getLink = getUrlSource( setLink( document.data.toString().dropWhile { it != 'h' } .dropLast(1))) if (getLink.contains("http", true)) { val intent = Intent(this@LoadingActivity, WebActivity::class.java) intent.putExtra(Constants.SEND_LINK_IN_WEB_VIEW, getLink) startActivity(intent) } else { val intent = Intent(this@LoadingActivity, MainActivity::class.java) startActivity(intent) } saveStateBd(getLink) } } else { saveStateFireBase("Nothing") val intent = Intent(this@LoadingActivity, MainActivity::class.java) startActivity(intent) } } else { saveStateFireBase("Error") val intent = Intent(this@LoadingActivity, MainActivity::class.java) startActivity(intent) } } .addOnFailureListener { saveStateFireBase("Error") val intent = Intent(this@LoadingActivity, MainActivity::class.java) startActivity(intent) } } else { if (stateFireBase?.contains("h") == true && stateBd?.contains("h") == true) { val intent = Intent(this@LoadingActivity, WebActivity::class.java) intent.putExtra(Constants.SEND_LINK_IN_WEB_VIEW, stateBd) startActivity(intent) } else { val intent = Intent(this@LoadingActivity, MainActivity::class.java) startActivity(intent) } } } } @Throws(IOException::class) private fun getUrlSource(site: String): String { val url = URL(site) val urlConnection = url.openConnection() val bufferedReader = BufferedReader( InputStreamReader( urlConnection.getInputStream(), "UTF-8" ) ) var inputLine: String? val stringBuilder = StringBuilder() while (bufferedReader.readLine().also { inputLine = it } != null) stringBuilder.append( inputLine ) bufferedReader.close() return stringBuilder.toString() } private fun setLink(link: String): String { val pack = this.packageName val uuid = UUID.randomUUID().toString() val timeZone = TimeZone.getDefault().toString().substringAfter('"') return "$link/?packageid=$pack&usserid=$uuid&getz=${timeZone.substringBeforeLast('"')}&getr=utm_source=google-play&utm_medium=organic" } private fun saveStateFireBase(string: String) { val editor = prefStateFireBase?.edit() editor?.putString(Constants.SAVE_STATE_FIRE_BASE, string) editor?.apply() } private fun saveStateBd(string: String) { val editor = prefStateBd?.edit() editor?.putString(Constants.SAVE_STATE_BD, string) editor?.apply() } @RequiresApi(Build.VERSION_CODES.M) fun isOnline(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) if (capabilities != null) { if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { Log.i("Internet", "NetworkCapabilities.TRANSPORT_CELLULAR") return true } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { Log.i("Internet", "NetworkCapabilities.TRANSPORT_WIFI") return true } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { Log.i("Internet", "NetworkCapabilities.TRANSPORT_ETHERNET") return true } } return false } }
崩溃原因分析
- 字符串处理的不安全操作:
document.data.toString().dropWhile { it != 'h' }.dropLast(1)如果字符串中不存在'h',dropWhile会返回空字符串,再执行dropLast(1)会触发索引越界异常。 - 协程未捕获异常:
CoroutineScope(Dispatchers.Default).launch内没有异常处理逻辑,getUrlSource抛出的IOException会直接导致应用崩溃(协程默认未捕获异常会终止进程)。 - 网络请求异常未处理:
getUrlSource中的url.openConnection()、getInputStream()均可能抛出IOException,没有捕获会直接引发崩溃。 - SharedPreferences空安全隐患:
prefStateFireBase和prefStateBd是可空类型,若初始化失败,saveStateFireBase和saveStateBd中的编辑器会为null,无法保存数据,后续逻辑依赖这些数据时可能引发问题。 - Firestore回调外的逻辑未覆盖:你只处理了Firestore的成功/失败回调,但后续的字符串处理、网络请求、协程操作都可能抛出异常,这些不在Firestore的回调异常处理范围内。
修复方案
1. 安全处理字符串截取
先校验字符串是否包含目标字符,再执行截取,避免空字符串操作:
val dataStr = document.data.toString() if (dataStr.contains("h")) { val trimmedLink = dataStr.substringAfter('h').dropLast(1) // 确保截取后的链接不为空 if (trimmedLink.isNotEmpty()) { saveStateFireBase(trimmedLink) webView.loadUrl(setLink(trimmedLink)) // 后续协程逻辑... } else { saveStateFireBase("Nothing") startActivity(Intent(this@LoadingActivity, MainActivity::class.java)) } } else { saveStateFireBase("Nothing") startActivity(Intent(this@LoadingActivity, MainActivity::class.java)) }
2. 给协程添加异常处理
使用CoroutineExceptionHandler或try-catch捕获协程内的异常:
val exceptionHandler = CoroutineExceptionHandler { _, throwable -> Log.e("CoroutineError", "协程异常: ${throwable.message}") runOnUiThread { startActivity(Intent(this@LoadingActivity, MainActivity::class.java)) } } CoroutineScope(Dispatchers.Default + exceptionHandler).launch { try { val getLink = getUrlSource(setLink(trimmedLink)) runOnUiThread { if (getLink.contains("http", true)) { startActivity(Intent(this@LoadingActivity, WebActivity::class.java).apply { putExtra(Constants.SEND_LINK_IN_WEB_VIEW, getLink) }) } else { startActivity(Intent(this@LoadingActivity, MainActivity::class.java)) } } saveStateBd(getLink) } catch (e: IOException) { Log.e("UrlError", "获取URL内容失败: ${e.message}") runOnUiThread { startActivity(Intent(this@LoadingActivity, MainActivity::class.java)) } } }
3. 安全处理网络请求
在getUrlSource中添加异常捕获,或在调用处处理:
@Throws(IOException::class) private fun getUrlSource(site: String): String { return try { val url = URL(site) val urlConnection = url.openConnection() BufferedReader(InputStreamReader(urlConnection.getInputStream(), "UTF-8")).use { reader -> val stringBuilder = StringBuilder() var inputLine: String? while (reader.readLine().also { inputLine = it } != null) { stringBuilder.append(inputLine) } stringBuilder.toString() } } catch (e: IOException) { Log.e("NetworkError", "读取URL失败: ${e.message}") throw e // 或返回默认空字符串,根据业务需求调整 } }
4. 加固SharedPreferences操作
确保保存数据时的空安全:
private fun saveStateFireBase(string: String) { prefStateFireBase?.edit()?.putString(Constants.SAVE_STATE_FIRE_BASE, string)?.apply() ?: Log.w("PrefWarning", "prefStateFireBase为空,无法保存状态") } private fun saveStateBd(string: String) { prefStateBd?.edit()?.putString(Constants.SAVE_STATE_BD, string)?.apply() ?: Log.w("PrefWarning", "prefStateBd为空,无法保存状态") }
5. 确保UI操作在主线程
协程中启动Activity必须切换到主线程,用runOnUiThread包裹所有startActivity调用。
内容的提问来源于stack exchange,提问作者Meridiane
相关产品推荐
相关产品推荐

