You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
    }
}

崩溃原因分析

  1. 字符串处理的不安全操作:document.data.toString().dropWhile { it != 'h' }.dropLast(1)如果字符串中不存在'h',dropWhile会返回空字符串,再执行dropLast(1)会触发索引越界异常。
  2. 协程未捕获异常:CoroutineScope(Dispatchers.Default).launch内没有异常处理逻辑,getUrlSource抛出的IOException会直接导致应用崩溃(协程默认未捕获异常会终止进程)。
  3. 网络请求异常未处理:getUrlSource中的url.openConnection()、getInputStream()均可能抛出IOException,没有捕获会直接引发崩溃。
  4. SharedPreferences空安全隐患:prefStateFireBase和prefStateBd是可空类型,若初始化失败,saveStateFireBase和saveStateBd中的编辑器会为null,无法保存数据,后续逻辑依赖这些数据时可能引发问题。
  5. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 19:24:59