欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android性能優(yōu)化全局異常處理詳情

 更新時(shí)間:2022年08月28日 09:03:04   作者:Ghelper???????  
這篇文章主要介紹了Android性能優(yōu)化全局異常處理詳情,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容協(xié)商,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下

前言

異常崩潰,是Android項(xiàng)目中一項(xiàng)比較棘手的問(wèn)題,即便做了很多的try - catch處理,也不能保證上線不會(huì)崩,而且一旦出現(xiàn)崩潰,就會(huì)出現(xiàn)下圖的彈窗,xx應(yīng)用停止運(yùn)行了,這種體驗(yàn)對(duì)用戶來(lái)說(shuō)是非常差的,因此已經(jīng)很明顯地提示,我們做的app崩潰了。

像現(xiàn)在企業(yè)應(yīng)用,有的在發(fā)生崩潰的時(shí)候,直接啟動(dòng)一個(gè)統(tǒng)計(jì)異常的Activity,然后用戶可以填寫(xiě)異常信息描述上報(bào);還有就是直接閃退,不會(huì)出現(xiàn)上圖的彈窗,用戶其實(shí)感知力上會(huì)差一些,并不知道是因?yàn)槭裁撮W退了。

那異??赡茈S時(shí)發(fā)生,不能在每個(gè)代碼塊中去處理,肯定需要統(tǒng)一處理異常問(wèn)題,這個(gè)就需要Java中的一個(gè)工具UncaughtExceptionHandler

1 UncaughtExceptionHandler

class AppCrashHandler : Thread.UncaughtExceptionHandler {

    override fun uncaughtException(t: Thread, e: Throwable) {

    }
}

UncaughtExceptionHandler是Java線程中的一個(gè)接口,它能夠捕獲到某個(gè)線程發(fā)生的異常。像try-catch是只能捕獲主線程中的異常,子線程發(fā)送異常不會(huì)catch住,但是UncaughtExceptionHandler是可以捕獲子線程中出現(xiàn)的異常的,當(dāng)異常發(fā)生時(shí),會(huì)回調(diào)uncaughtException方法,在這里可以做異常的上報(bào)。

1.1 替代Android異常機(jī)制

在文章的開(kāi)頭,我們看到Android中異常處理的機(jī)制就是閃退 + 彈窗,那么我們想自己處理異常并替換掉Android的處理方式,這個(gè)訴求其實(shí)Java中已經(jīng)實(shí)現(xiàn)了,就是調(diào)用Thread的setDefaultUncaughtExceptionHandler

class AppCrashHandler : Thread.UncaughtExceptionHandler {

    private var context: Context? = null

    fun init(context: Context) {
        this.context = context
        Thread.setDefaultUncaughtExceptionHandler(this)
    }
    override fun uncaughtException(t: Thread, e: Throwable) {
        Log.e(TAG, "thread name ${t.name} throw error ${e.message}")

    }
    companion object {

        private const val TAG = "AppCrashHandler"

        val instance: AppCrashHandler by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            AppCrashHandler()
        }
    }
} 

這樣我們?cè)赼pp中初始化這個(gè)AppCrashHandler,看異常信息能不能捕獲到。

class MainActivity : AppCompatActivity() {

    private lateinit var bigView: BigView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//        bigView = findViewById(R.id.big_view)
        bigView.setImageUrl(assets.open("mybg.png"))


    }
}

這里我們沒(méi)有初始化BigView,而是直接調(diào)用了它的一個(gè)方法,這里肯定是會(huì)報(bào)錯(cuò)的!運(yùn)行之后,我們看到了一份日志信息

E/AppCrashHandler: thread name main throw error Unable to start activity ComponentInfo{com.lay.image_process/com.lay.image_process.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property bigView has not been initialized

主線程拋出異常,原因就是bigView沒(méi)有被初始化,這就說(shuō)明異常是被捕獲到了,而且我們會(huì)發(fā)現(xiàn),app并沒(méi)有閃退,這就是說(shuō)明,我們已經(jīng)替代了Android的異常處理方式。

1.2 可選擇的異常處理

在第一小節(jié)中,我們是捕獲到了異常而且應(yīng)用沒(méi)有閃退,這種方式真的好嗎?其實(shí)我們可以試一下,返回和點(diǎn)擊事件其實(shí)都不響應(yīng)了,因?yàn)檫M(jìn)程都被干掉了。

所以捕獲只是一部分,捕獲之后的處理也很重要,因?yàn)閷?duì)于一些異常,我們不想自己去處理,而是直接走系統(tǒng)的異常處理,其實(shí)這種風(fēng)險(xiǎn)就會(huì)降低,因?yàn)槲覀冏约禾幚砣慨惓R膊滑F(xiàn)實(shí),也可能沒(méi)有系統(tǒng)處理的好。

defaultSystemExpHandler = Thread.getDefaultUncaughtExceptionHandler()

通過(guò)getDefaultUncaughtExceptionHandler()方法獲取到的就是系統(tǒng)默認(rèn)的異常處理對(duì)象,那么什么樣的異常可以放給系統(tǒng)處理呢?在第一小節(jié)中,我們打印出的日志信息中發(fā)現(xiàn)uncaughtException捕獲到的異常不是空的,那么有可能就是捕獲到的異常是空的,那么就需要交給系統(tǒng)處理。

override fun uncaughtException(t: Thread, e: Throwable?) {
    Log.e(TAG, "thread name ${t.name} throw error ${e?.message}")
    if (e == null) {
        defaultSystemExpHandler?.uncaughtException(t, e)
    } else {

    }
}

如果捕獲到的異常不為空,那么就需要我們自己處理異常,其實(shí)當(dāng)異常發(fā)生的時(shí)候,app的進(jìn)程已經(jīng)到了要掛掉的邊緣,已經(jīng)是未響應(yīng)的狀態(tài),為什么點(diǎn)擊沒(méi)有響應(yīng),是因?yàn)槭录鬟f已經(jīng)不起作用了,而且我們?nèi)绻私釧ndroid的事件處理機(jī)制,應(yīng)該明白,在ActivityThread的main方法中,初始化了Looper并開(kāi)啟了死循環(huán)處理系統(tǒng)事件,那么這個(gè)時(shí)候,Looper肯定是不運(yùn)轉(zhuǎn)了,如果我們想要處理異常,需要再激活一個(gè)Looper

override fun uncaughtException(t: Thread, e: Throwable?) {
    Log.e(TAG, "thread name ${t.name} throw error ${e?.message}")
    if (e == null) {
        defaultSystemExpHandler?.uncaughtException(t, e)
    } else {
        executors.execute {
            Looper.prepare()
            //處理異常
            Toast.makeText(context, "系統(tǒng)崩潰了~", Toast.LENGTH_SHORT).show()

            Looper.loop()
        }
    }
}

從上圖中我們能夠看到,Toast已經(jīng)提示系統(tǒng)崩潰的異常。

2 日志上傳

其實(shí)日志上傳,我們現(xiàn)在有很多種方式,像Bugly、阿里云等直接上傳在云端;也有保存在本地文件中,通過(guò)用戶觸發(fā)回?fù)瓢l(fā)送到日志群中,各種各樣的方式都存在。

那么我們?cè)谏蟼魅罩镜臅r(shí)候,信息要全,才能夠直接定位到異常的位置做快速反應(yīng),因此當(dāng)捕獲到異常之后,我們就需要收集日志信息,并上傳。

2.1 日志收集

日志收集通常需要獲取當(dāng)前應(yīng)用的包信息以及硬件設(shè)備信息,包信息獲取很簡(jiǎn)單,Android已經(jīng)有很成熟的API

private fun collectBaseInfo() {
    //獲取包信息
    val packageManager = context?.packageManager
    packageManager?.let {
        try {
            val packageInfo =
                it.getPackageInfo(context?.packageName ?: "", PackageManager.GET_ACTIVITIES)
            val versionName = packageInfo.versionName
            val versionCode = packageInfo.versionCode
            infoMap["versionName"] = versionName
            infoMap["versionCode"] = versionCode.toString()
        } catch (e: Exception) {

        }
    }
}

那么對(duì)于硬件設(shè)備信息,其實(shí)在Build中有對(duì)應(yīng)的字段,但是沒(méi)有取值的方法,因此需要通過(guò)反射來(lái)獲取對(duì)應(yīng)的值

//通過(guò)反射獲取Build的全部參數(shù)

val fields = Build::class.java.fields
if (fields != null && fields.isNotEmpty()) {
    fields.forEach { field ->
        field.isAccessible = true
        infoMap[field.name] = field.get(null).toString()
    }
}

那么我們通過(guò)打印日志,可以看到基本的信息都已經(jīng)有了

E/AppCrashHandler: info -- {versionName=1.0, versionCode=1, BOARD=goldfish_x86, 
BOOTLOADER=unknown, BRAND=google, CPU_ABI=x86, CPU_ABI2=armeabi-v7a, DEVICE=generic_x86_arm, DISPLAY=sdk_gphone_x86_arm-userdebug 9 PSR1.180720.122 6736742 dev-keys, FINGERPRINT=google/sdk_gphone_x86_arm/generic_x86_arm:9/PSR1.180720.122/6736742:userdebug/dev-keys, HARDWARE=ranchu, HOST=abfarm200, ID=PSR1.180720.122, IS_DEBUGGABLE=true, IS_EMULATOR=true, MANUFACTURER=Google, MODEL=AOSP on IA Emulator, PERMISSIONS_REVIEW_REQUIRED=false, PRODUCT=sdk_gphone_x86_arm, RADIO=unknown, SERIAL=unknown, SUPPORTED_32_BIT_ABIS=[Ljava.lang.String;@1139408, \SUPPORTED_64_BIT_ABIS=[Ljava.lang.String;@2a0a7a1, SUPPORTED_ABIS=[Ljava.lang.String;@9009dc6, TAGS=dev-keys, TIME=1596587219000, TYPE=userdebug, UNKNOWN=unknown, USER=android-build}

這樣我們已經(jīng)采集到了一些基礎(chǔ)信息,接下來(lái)就需要上傳日志

2.2 日志存儲(chǔ)

當(dāng)我們的應(yīng)用程序發(fā)生異常的時(shí)候,這時(shí)候觸發(fā)了全局異常捕獲,收集到了日志信息,這個(gè)時(shí)候,可以選擇將日志上傳到數(shù)據(jù)庫(kù),或者存儲(chǔ)在內(nèi)存中。

其實(shí)這兩者都有缺點(diǎn),上傳到數(shù)據(jù)庫(kù)會(huì)有性能問(wèn)題,存儲(chǔ)在內(nèi)存中有可能會(huì)丟失部分?jǐn)?shù)據(jù),所以建議大家使用一種穩(wěn)妥的方式:先將日志存儲(chǔ)文件在某個(gè)文件夾下,等下次app啟動(dòng)的時(shí)候,選擇將該日志上傳,然后清空文件夾。

首先uncaughtException捕獲到的異常是Throwable,我們?cè)贚ogcat中看到的出現(xiàn)異常之后的堆棧信息,其實(shí)就是保存在Throwable中的,所以在上傳的日志中,需要將這些堆棧信息保存在文件中。

private fun saveErrorInfo(e: Throwable) {
    val stringBuffer = StringBuffer()
    infoMap.forEach { (key, value) ->
        stringBuffer.append("$key == $value")
    }

    val stringWriter = StringWriter()
    val printWriter = PrintWriter(stringWriter)
    //獲取到堆棧信息
    e.printStackTrace(printWriter)
    printWriter.close()
    //轉(zhuǎn)換異常信息
    val errorStackInfo = stringWriter.toString()
    stringBuffer.append(errorStackInfo)
    Log.e(TAG, "error -- ${stringBuffer.toString()}")
    }

從我們看到的堆棧信息中,我們可以看到有很多行,每行都對(duì)應(yīng)一個(gè)行號(hào)告訴我們異常在哪里,因此我們通過(guò)StringWriter承接所有的堆棧信息,等到所有堆棧信息遍歷完成,都保存在了StringWriter中。

    versionName == 1.0 
    versionCode == 1 
    BOARD == goldfish_x86 
    BOOTLOADER == unknown 
    BRAND == google 
    CPU_ABI == x86 
    CPU_ABI2 == armeabi-v7a 
    DEVICE == generic_x86_arm 
    DISPLAY == sdk_gphone_x86_arm-userdebug 9 PSR1.180720.122 6736742 dev-keys 
    FINGERPRINT == google/sdk_gphone_x86_arm/generic_x86_arm:9/PSR1.180720.122/6736742:userdebug/dev-keys 
    HARDWARE == ranchu 
    HOST == abfarm200 
    ID == PSR1.180720.122 
    IS_DEBUGGABLE == true 
    IS_EMULATOR == true 
    MANUFACTURER == Google 
    MODEL == AOSP on IA Emulator 
    PERMISSIONS_REVIEW_REQUIRED == false 
    PRODUCT == sdk_gphone_x86_arm 
    RADIO == unknown 
    SERIAL == unknown 
    SUPPORTED_32_BIT_ABIS == [Ljava.lang.String;@9544e25 
    SUPPORTED_64_BIT_ABIS == [Ljava.lang.String;@e52bbfa 
    SUPPORTED_ABIS == [Ljava.lang.String;@bdc65ab 
    TAGS == dev-keys 
    TIME == 1596587219000 
    TYPE == userdebug 
    UNKNOWN == unknown 
    USER == android-build 
    ----------------異常信息捕獲-------------
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.lay.image_process/com.lay.image_process.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property bigView has not been initialized
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: kotlin.UninitializedPropertyAccessException: lateinit property bigView has not been initialized
        at com.lay.image_process.MainActivity.onCreate(MainActivity.kt:16)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)?
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)?
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)?
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)?
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)?
        at android.os.Handler.dispatchMessage(Handler.java:106)?
        at android.os.Looper.loop(Looper.java:193)?
        at android.app.ActivityThread.main(ActivityThread.java:6669)?
        at java.lang.reflect.Method.invoke(Native Method)?
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)?
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)?

然后將該文件保存到sd卡,具體的存儲(chǔ)邏輯就不寫(xiě)了,很簡(jiǎn)單。

然后,我們?cè)诖鎯?chǔ)完日志信息之后呢,就需要將進(jìn)程干掉,可選擇將進(jìn)程重啟

//這里就是將進(jìn)程干掉
android.os.Process.killProcess(android.os.Process.myPid())
//這里等價(jià) System.exit(1) 進(jìn)程被干掉后,然后重啟
exitProcess(1)

關(guān)于是否需要重啟,這個(gè)需要謹(jǐn)慎使用,如果app首頁(yè)就發(fā)生崩潰,那么會(huì)進(jìn)入死循環(huán),一直殺掉進(jìn)程然后重啟!

3 策略設(shè)計(jì)模式實(shí)現(xiàn)上傳功能

其實(shí)本地文件存儲(chǔ),其實(shí)只是一種方式,其實(shí)還有其他的方式,像上傳到云端、發(fā)送短信等等,那么業(yè)務(wù)方在調(diào)用的時(shí)候,可以選擇要實(shí)現(xiàn)的方式,所以這種多形態(tài)的處理方式可以采用策略設(shè)計(jì)模式

interface LogHelper {
    fun upload(context: Context,listener: LogUploadListener)
}

策略設(shè)計(jì)模式,核心在于易擴(kuò)展,因此接口不可缺少,任何實(shí)現(xiàn)的方式都需要實(shí)現(xiàn)這個(gè)接口

interface LogUploadListener {
    fun loadSuccess()
    fun loadFail(reason:String)
}

同時(shí)還需要一個(gè)上傳日志的狀態(tài)監(jiān)聽(tīng)接口,回調(diào)給業(yè)務(wù)方日志是否上傳成功。

class NetUploadHelper : LogHelper {
    override fun upload(context: Context, listener: LogUploadListener) {
        //模擬網(wǎng)絡(luò)上傳
        Thread.sleep(1000)
        listener.loadSuccess()
    }
}
class SmsLoadHelper : LogHelper {
    override fun upload(context: Context, listener: LogUploadListener) {
        Thread.sleep(2000)
        listener.loadFail("網(wǎng)絡(luò)連接失敗")
    }
}

接著有兩個(gè)實(shí)現(xiàn)類(lèi),用來(lái)做具體的上傳邏輯處理,那么用戶選擇的方式就是在AppCrashHandler中開(kāi)放入口

fun setUploadFunc(helper: LogHelper) {
    this.helper = helper
}
context?.let {
    helper?.upload(it,object : LogUploadListener{
        override fun loadSuccess() {
            Log.e(TAG,"loadSuccess")
        }

        override fun loadFail(reason: String) {
            Log.e(TAG,"loadFail $reason")
        }
    })
}

在日志上傳的時(shí)候,調(diào)用upload方法上傳日志,具體的實(shí)現(xiàn)類(lèi)是業(yè)務(wù)方自行選擇的,假設(shè)我選擇了發(fā)短信

AppCrashHandler.instance.setUploadFunc(SmsLoadHelper())

打印的日志如下:

E/AppCrashHandler: loadFail 網(wǎng)絡(luò)連接失敗

到此這篇關(guān)于Android性能優(yōu)化全局異常處理詳情的文章就介紹到這了,更多相關(guān)Android全局異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android四種常見(jiàn)布局方式示例教程

    Android四種常見(jiàn)布局方式示例教程

    Android四種布局有線性布局LinearLayout、相對(duì)布局RelativeLayout、網(wǎng)格布局GridLayout、和滾動(dòng)視圖ScrollView,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-09-09
  • Android應(yīng)用禁止屏幕休眠的3種方法

    Android應(yīng)用禁止屏幕休眠的3種方法

    這篇文章主要為大家詳細(xì)介紹了Android應(yīng)用禁止屏幕休眠的3種方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • Android獲取應(yīng)用程序大小和緩存的實(shí)例代碼

    Android獲取應(yīng)用程序大小和緩存的實(shí)例代碼

    這篇文章主要介紹了Android獲取應(yīng)用程序大小和緩存的實(shí)例代碼的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-10-10
  • Android快遞物流信息布局開(kāi)發(fā)

    Android快遞物流信息布局開(kāi)發(fā)

    這篇文章主要為大家詳細(xì)介紹了Android快遞物流信息布局開(kāi)發(fā),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • 超實(shí)用的Android手勢(shì)鎖制作實(shí)例教程

    超實(shí)用的Android手勢(shì)鎖制作實(shí)例教程

    這篇文章主要介紹了一個(gè)超實(shí)用的Android手勢(shì)鎖制作實(shí)例教程,普通的圓環(huán)形圖標(biāo)變換,在App和系統(tǒng)的鎖屏界面中都可以調(diào)用,需要的朋友可以參考下
    2016-04-04
  • Android開(kāi)發(fā)實(shí)現(xiàn)ListView異步加載數(shù)據(jù)的方法詳解

    Android開(kāi)發(fā)實(shí)現(xiàn)ListView異步加載數(shù)據(jù)的方法詳解

    這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)ListView異步加載數(shù)據(jù)的方法,結(jié)合具體實(shí)例形式分析了Android操作ListView實(shí)現(xiàn)異步加載數(shù)據(jù)的具體步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2017-11-11
  • Flutter實(shí)現(xiàn)滑動(dòng)塊驗(yàn)證碼功能

    Flutter實(shí)現(xiàn)滑動(dòng)塊驗(yàn)證碼功能

    這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)滑動(dòng)塊驗(yàn)證碼功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 制作獨(dú)立的Android模擬器實(shí)現(xiàn)方法

    制作獨(dú)立的Android模擬器實(shí)現(xiàn)方法

    本文主要介紹如何制作獨(dú)立的Android模擬器,這里給大家提供詳細(xì)的制作流程,有需要的小伙伴可以參考下
    2016-08-08
  • Android仿QQ可拉伸頭部控件

    Android仿QQ可拉伸頭部控件

    這篇文章主要為大家詳細(xì)介紹了Android仿QQ可拉伸頭部控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • Android初學(xué)者必須知道的10個(gè)技術(shù)

    Android初學(xué)者必須知道的10個(gè)技術(shù)

    本篇內(nèi)容給大家整理10個(gè)作為Android初學(xué)者必須要了解和會(huì)用的技術(shù)以及詳細(xì)代碼分析,需要的朋友收藏下慢慢學(xué)習(xí)吧。
    2017-12-12

最新評(píng)論