得物 Android Crash 治理實踐

一、前言

通過修復歷史遺留的Crash漏報問題(包括端側SDK采集的兼容性優化及Crash平臺的數據消費機制完善),得物Android端的Crash監控體系得到顯著增強,使得歷史Crash數據的完整捕獲能力得到系統性改善,相應Crash指標也有所上升,經過架構以及各團隊的共同努力下,崩潰率已從最高的萬2降至目前的萬1.1到萬1.5,其中疑難問題占比約90%、因系統bug導致的Crash占比約40%,在本文中將簡要介紹一些較典型的系統Crash的治理過程。

二、DNS解析崩潰

背景

Android11及以下版本在DNS解析過程中的有幾率產生野指針問題導致的Native Crash,其中Android9占比最高。

堆棧與上報趨勢

at libcore.io.Linux.android_getaddrinfo(Linux.java)
at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:172)
at java.net.InetAddress.parseNumericAddressNoThrow(InetAddress.java:1631)
at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:96)
at java.net.InetAddress.getAllByName(InetAddress.java:1154)#00 pc 000000000003b938  /system/lib64/libc.so (android_detectaddrtype+1164)
#01 pc 000000000003b454  /system/lib64/libc.so (android_getaddrinfofornet+72)
#02 pc 000000000002b5f4  /system/lib64/libjavacore.so (_ZL25Linux_android_getaddrinfoP7_JNIEnvP8_jobjectP8_jstringS2_i+336)

上報趨勢.jpeg

問題分析

崩潰入口方法InetAddress.getAllByName用于根據指定的主機名返回與之關聯的所有 IP 地址,它會根據系統配置的名稱服務進行解析,沿著調用鏈查看源碼發現在parseNumericAddressNoThrow方法內部調用Libcore.os.android_getaddrinfo時中有try catch的容錯邏輯,繼續查看后續調用的c++的源碼,在調用android_getaddrinfofornet函數返回值不為0時拋出GaiException異常。

https://cs.android.com/android/platform/superproject/+/android-9.0.0_r49:libcore/ojluni/src/main/java/java/net/InetAddress.javastatic InetAddress parseNumericAddressNoThrow(String address) {// Accept IPv6 addresses (only) in square brackets for compatibility.if (address.startsWith("[") && address.endsWith("]") && address.indexOf(':') != -1) {address = address.substring(1, address.length() - 1);}StructAddrinfo hints = new StructAddrinfo();hints.ai_flags = AI_NUMERICHOST;InetAddress[] addresses = null;try {addresses = Libcore.os.android_getaddrinfo(address, hints, NETID_UNSET);} catch (GaiException ignored) {}return (addresses != null) ? addresses[0] : null;}
https://cs.android.com/android/platform/superproject/+/master:libcore/luni/src/main/native/libcore_io_Linux.cpp?q=Linux_android_getaddrinfo&ss=android%2Fplatform%2Fsuperprojectstatic jobjectArray Linux_android_getaddrinfo(JNIEnv* env, jobject, jstring javaNode,jobject javaHints, jint netId) {......int rc = android_getaddrinfofornet(node.c_str(), NULL, &hints, netId, 0, &addressList);std::unique_ptr<addrinfo, addrinfo_deleter> addressListDeleter(addressList);if (rc != 0) {throwGaiException(env, "android_getaddrinfo", rc);return NULL;}......return result;
}

解決過程

解決思路是代理android_getaddrinfofornet函數,捕捉調用原函數過程中出現的段錯誤信號,接著吃掉這個信號并返回-1,使之轉換為JAVA異常進而走進parseNumericAddressNoThrow方法的容錯邏輯,和負責網絡的同學提前做了溝通,確定此流程對業務沒有影響后開始解決。

首先使用inline-hook代理了android_getaddrinfofornet函數,接著使用字節封裝好的native try catch工具做吃掉段錯誤信號并返回-1的,字節工具內部原理是在try塊的開始使用sigsetjmp打個錨點并快照當前寄存器的值,然后設置信號量處理器并關聯當前線程,在catch塊中解綁線程與信號的關聯并執行業務兜底代碼,在捕捉到信號時通過siglongjmp函數長跳轉到catch塊中,感興趣的同學可以用下面精簡后的demo試試,以下代碼保存為mem_err.c,執行gcc ./mem_err.c;./a.out

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>struct sigaction old;
static sigjmp_buf buf;void SIGSEGV_handler(int sig, siginfo_t *info, void *ucontext) {printf("信號處理 sig: %d, code: %d\n", sig, info->si_code);siglongjmp(buf, -1);
}int main() {if (!sigsetjmp(buf, 0)) {struct sigaction sa;sa.sa_sigaction = SIGSEGV_handler;sigaction(SIGSEGV, &sa, &old);printf("try exec\n");//產生段錯誤int *ptr = NULL;*ptr = 1;printf("try-block end\n");//走不到} else {printf("catch exec\n");sigaction(SIGSEGV, &old, NULL);}printf("main func end\n");return 0;
}//輸出以下日志
//try exec
//信號處理 sig: 11, code: 2
//catch exec
//main func end

inline-hook庫: https://github.com/bytedance/android-inline-hook

字節native try catch工具: https://github.com/bytedance/android-inline-hook/blob/main/shadowhook/src/main/cpp/common/bytesig.c

三、MediaCodec 狀態異常崩潰

背景

在Android 11系統庫的音視頻播放過程中,偶爾會出現因狀態異常導致的SIGABRT崩潰。音視頻團隊反饋指出,這是Android 11的一個系統bug。隨后,我們協助音視頻團隊通過hook解決了這一問題。

堆棧與上報趨勢

#00 pc 0000000000089b1c  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164)
#01 pc 000000000055ed78  /apex/com.android.art/lib64/libart.so (_ZN3art7Runtime5AbortEPKc+2308)
#02 pc 0000000000013978  /system/lib64/libbase.so (_ZZN7android4base10SetAborterEONSt3__18functionIFvPKcEEEEN3$_38__invokeES4_+76)
#03 pc 0000000000006e30  /system/lib64/liblog.so (__android_log_assert+336)
#04 pc 0000000000122074  /system/lib64/libstagefright.so (_ZN7android10MediaCodec37postPendingRepliesAndDeferredMessagesENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEERKNS_2spINS_8AMessageEEE+720)
#05 pc 00000000001215cc  /system/lib64/libstagefright.so (_ZN7android10MediaCodec37postPendingRepliesAndDeferredMessagesENSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEi+244)
#06 pc 000000000011c308  /system/lib64/libstagefright.so (_ZN7android10MediaCodec17onMessageReceivedERKNS_2spINS_8AMessageEEE+8752)
#07 pc 0000000000017814  /system/lib64/libstagefright_foundation.so (_ZN7android8AHandler14deliverMessageERKNS_2spINS_8AMessageEEE+84)
#08 pc 000000000001d9cc  /system/lib64/libstagefright_foundation.so (_ZN7android8AMessage7deliverEv+188)
#09 pc 0000000000018b48  /system/lib64/libstagefright_foundation.so (_ZN7android7ALooper4loopEv+572)
#10 pc 0000000000015598  /system/lib64/libutils.so (_ZN7android6Thread11_threadLoopEPv+460)
#11 pc 00000000000a1d6c  /system/lib64/libandroid_runtime.so (_ZN7android14AndroidRuntime15javaThreadShellEPv+144)
#12 pc 0000000000014d94  /system/lib64/libutils.so (_ZN13thread_data_t10trampolineEPKS_+412)
#13 pc 00000000000eba94  /apex/com.android.runtime/lib64/bionic/libc.so (_ZL15__pthread_startPv+64)
#14 pc 000000000008bd80  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)

狀態異常崩潰上報趨勢.jpeg

問題分析

根據堆棧內容分析Android11的源碼以及結合SIGABRT信號采集到的信息(postPendingRepliesAndDeferredMessages: mReplyID == null, from kWhatRelease:STOPPING following kWhatError:STOPPING),找到崩潰發生在onMessageReceived函數處理kWhatRelease類型消息的過程中,onMessageReceived函數連續收到兩條消息,第一條是kWhatError:STOPPING,第二條是kWhatRelease:STOPPING此時因mReplyID已經被置為空,因此走到判空拋異常的邏輯。

https://cs.android.com/android/_/android/platform/frameworks/av/+/refs/tags/android-11.0.0_r48:media/libstagefright/MediaCodec.cpp;l=2280;drc=789055bbcb4560b42faf19103b1cda5534e8f9cb;bpv=0;bpt=0

問題分析1.jpeg
問題分析2.jpeg
問題分析3.jpeg
問題分析4.jpeg
對比Android12的源碼,在處理kWhatRelease事件且狀態為STOPPING拋異常前,增加了對mReplyID不為空的判斷來規避這個問題。

https://cs.android.com/android/_/android/platform/frameworks/av/+/ca0c3286a4790a4de2d90cb275ae89a9601b805b:media/libstagefright/MediaCodec.cpp;dlc=7327aab894f6c456ea16c95b64134841da8d5737

規避這個問題.jpeg

解決過程

Android12的修復方式意味著上述三個條件結合下吃掉異常是符合預期的,接下來就是想辦法通過hook Android11使邏輯對齊Android12。

【初探】最先想到的辦法是代理相關函數通過判斷走到這個場景時提前return出去來規避,音視頻的同學嘗試后發現不可行,原因如下:

  • void MediaCodec::postPendingRepliesAndDeferredMessages(std::string origin, status_t err): 匹配origin是否為特征字符串(postPendingRepliesAndDeferredMessages: mReplyID == null, from kWhatRelease:STOPPING following kWhatError:STOPPING);很多設備找不到這個符號不可行;
  • void MediaCodec::onMessageReceived(const sp&msg): 已知MediaCodec實例的內存首地址,需要通過hardcode偏移量來獲取mReplay、mState兩個字段,這里又缺少可供校驗正確性的特征,風險略大擔心有不同機型的兼容性問題(不同機型新增、刪除字段導致偏移量不準)。

【踩坑】接著嘗試使用與修復DNS崩潰類似思路的保護方案,使用inline-hook代理onMessageReceived函數調用原函數時使用setjmp打錨點,然后使用plt hook代理_android_log_assert函數并在內部檢測錯誤信息為特征字符串時通過longjmp跳轉到onMessageReceived函數的錨點并作return操作,精簡后的demo如下:

Plt-hook 庫: https://github.com/iqiyi/xHook

#include <iostream>
#include <setjmp.h>
#include <csignal>static thread_local jmp_buf _buf;
void *origin_onMessageReceived = nullptr;
void *origin__android_log_assert = nullptr;void _android_log_assert_proxy(const char* cond, const char *tag, const char* fmt, ...) {//模擬liblog.so的__android_log_assert函數std::cout << "__android_log_assert start" << std::endl;if (!strncmp(fmt, "postPendingRepliesAndDeferredMessages: mReplyID == null", 55)) {longjmp(_buf, -1);}//模擬調用origin__android_log_assert,產生崩潰 raise(SIGABRT);
}void onMessageReceived_proxy(void *thiz, void *msg) {std::cout << "onMessageReceived_proxy start" << std::endl;if (!setjmp(_buf)) {//模擬調用onMessageReceived原函數(origin_onMessageReceived)進入崩潰流程std::cout << "onMessageReceived_proxy 1" << std::endl;_android_log_assert_proxy(nullptr, nullptr, "postPendingRepliesAndDeferredMessages: mReplyID == null, from kWhatRelease:STOPPING following kWhatError:STOPPING");std::cout << "onMessageReceived_proxy 2" << std::endl;//走不到} else {//保護后從此處返回std::cout << "onMessageReceived_proxy 3" << std::endl;}std::cout << "onMessageReceived_proxy end" << std::endl;
}int main() {std::cout << "main func start" << std::endl;/**inline-hook: shadowhook_hook_sym_name("libstagefright.so","_ZN7android10MediaCodec17onMessageReceivedERKNS_2spINS_8AMessageEEE",(void *) onMessageReceived_proxy, (void **) &origin_onMessageReceived);plhook: xh_core_register("libstagefright.so", "__android_log_assert", (void *) (_android_log_assert_proxy), (void **) (&origin__android_log_assert));*///模擬調用libstagefright.so的_ZN7android10MediaCodec17onMessageReceivedERKNS_2spINS_8AMessageEEE函數onMessageReceived_proxy(nullptr, nullptr);std::cout << "main func end" << std::endl;return 0;
}/**
日志輸出main func start
onMessageReceived_proxy start
onMessageReceived_proxy 1
__android_log_assert start
onMessageReceived_proxy 3
onMessageReceived_proxy end
main func end
*/

線下一陣操作猛如虎經測試保護邏輯符合預期,但是在灰度期間踩到棧溢出保護導致錯誤轉移的坑,堆棧如下:

#00 pc 000000000004e40c  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164)
#01 pc 0000000000062730  /apex/com.android.runtime/lib64/bionic/libc.so (__stack_chk_fail+20)
#02 pc 000000000000a768 /data/app/~~JaQm4SU8wxP7T2GaSWxYkQ==/com.shizhuang.duapp-N5RFIB8WurdccMgAVsBang==/lib/arm64/libduhook.so (_ZN25CrashMediaCodecProtection5proxyEPvS0_)
#03 pc 0000000001091c0c  [anon:scudo:primary]

*關于棧溢出保護機制感興趣的同學可以參考這篇文章https://bbs.kanxue.com/thread-221762-1.htm

(CSPP 第3版 “3.10.3 內存越界引用和緩沖區溢出”章節講的更詳細)*

longjmp函數只是恢復寄存器的值后從錨點處再次返回,過程中也唯一可能會操作棧禎只有inline-hook,當時懷疑是與setjmp/longjmp機制不兼容,由于inline-hook內部邏輯大量使用匯編來實現排查起來比較困難,因此這個問題困擾比較久,網上的資料提到可以使用代理出錯函數(__stack_chk_fail)或者編譯so時增加參數不讓編譯器生成保護代碼來繞過,這兩種方式影響面都比較大所以未采用。有了前面的懷疑點想到使用c++的try catch機制來做跨函數域的跳轉,大致的思路同上只是把setjmp替換為c++的try catch,把longjmp替換為throw exception,精簡后的demo如下:

c++異常機制介紹: https://baiy.cn/doc/cpp/inside_exception.htm

#include <iostream>
#include <csignal>void *origin_onMessageReceived = nullptr;
void *origin__android_log_assert = nullptr;class MyCustomException : public std::exception {
public:explicit MyCustomException(const std::string& message): msg_(message) {}virtual const char* what() const noexcept override {return msg_.c_str();}private:std::string msg_;
};void _android_log_assert_proxy(const char* cond, const char *tag, const char* fmt, ...) {//模擬liblog.so的__android_log_assert函數std::cout << "__android_log_assert start" << std::endl;if (!strncmp(fmt, "postPendingRepliesAndDeferredMessages: mReplyID == null", 55)) {throw MyCustomException("postPendingRepliesAndDeferredMessages: mReplyID == null");}//模擬調用origin__android_log_assert,產生崩潰raise(SIGABRT);
}void onMessageReceived_proxy(void *thiz, void *msg) {std::cout << "onMessageReceived_proxy start" << std::endl;try {//模擬調用onMessageReceived原函數(origin_onMessageReceived)進入崩潰流程std::cout << "onMessageReceived_proxy 1" << std::endl;_android_log_assert_proxy(nullptr, nullptr, "postPendingRepliesAndDeferredMessages: mReplyID == null, from kWhatRelease:STOPPING following kWhatError:STOPPING");std::cout << "onMessageReceived_proxy 2" << std::endl;//走不到} catch (const MyCustomException& e) {//保護后從此處返回std::cout << "onMessageReceived_proxy 3" << std::endl;}std::cout << "onMessageReceived_proxy end" << std::endl;
}int main() {std::cout << "main func start" << std::endl;/**inline-hook: shadowhook_hook_sym_name("libstagefright.so","_ZN7android10MediaCodec17onMessageReceivedERKNS_2spINS_8AMessageEEE",(void *) onMessageReceived_proxy, (void **) &origin_onMessageReceived);plhook: xh_core_register("libstagefright.so", "__android_log_assert", (void *) (_android_log_assert_proxy), (void **) (&origin__android_log_assert));*///模擬調用libstagefright.so的_ZN7android10MediaCodec17onMessageReceivedERKNS_2spINS_8AMessageEEE函數onMessageReceived_proxy(nullptr, nullptr);std::cout << "main func end" << std::endl;return 0;
}/**
日志輸出main func start
onMessageReceived_proxy start
onMessageReceived_proxy 1
__android_log_assert start
onMessageReceived_proxy 3
onMessageReceived_proxy end
main func end
*/

灰度上線后發現有設備走到了_android_log_assert代理函數中的throw邏輯,但是未按預期走到catch塊而是把錯誤又轉移為" terminating with uncaught exception of type" ,有點搞心態啊。

【柳暗花明】C++的異常處理機制在throw執行時,會開始在調用棧中向上查找匹配的catch塊,檢查每一個函數直到找到一個具有合適類型的catch塊,上述的錯誤信息代表未找到匹配的catch塊。從轉移的堆棧中注意到沒有onMessageReceived代理函數的堆棧,此時基于inline-hook的原理(修改原函數前面的匯編代碼跳轉到代理函數)又懷疑到它身上,再次排查代碼時發現代理函數開頭漏寫了一個宏,在inline-hook中SHADOWHOOK_STACK_SCOPE就是來管理棧禎的,因此出現找不到catch塊以及前面longjmp的問題就不奇怪了。加上這個宏以后柳暗花明,重新放量后保護邏輯按預期執行并且保護生效后視頻播放正常。和音視頻的小伙伴一努力下,經歷了幾個版本終于解決了這個系統bug,目前僅剩老版本App有零星的上報。

四、bio多線程環境崩潰

背景

Android 11 Socket close過程中在多線程場景下有幾率產生野指針問題導致Native Crash,現象是多個線程同時close連接時,一個線程已銷毀了bio的上下文,另外一個線程仍執行close并在此過程中嘗試獲取這個bio有多少未寫出去的字節數時出現野指針導致的段錯誤。此問題從21年首次上報以來在得物的Crash列表中一直處于較前的位置。

堆棧與上報趨勢

at com.android.org.conscrypt.NativeCrypto.SSL_pending_written_bytes_in_BIO(Native method)
at com.android.org.conscrypt.NativeSsl$BioWrapper.getPendingWrittenBytes(NativeSsl.java:660)
at com.android.org.conscrypt.ConscryptEngine.pendingOutboundEncryptedBytes(ConscryptEngine.java:566)
at com.android.org.conscrypt.ConscryptEngineSocket.drainOutgoingQueue(ConscryptEngineSocket.java:584)
at com.android.org.conscrypt.ConscryptEngineSocket.close(ConscryptEngineSocket.java:480)
at okhttp3.internal.Util.closeQuietly_aroundBody0(Util.java:1)
at okhttp3.internal.Util$AjcClosure1.run(Util.java:1)
at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:3)
at com.shizhuang.duapp.common.aspect.ThirdSdkAspect.t(ThirdSdkAspect.java:1)
at okhttp3.internal.Util.closeQuietly(Util.java:3)
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.java:42)
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.java:1)
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.java:6)
at okhttp3.internal.connection.Transmitter.newExchange(Transmitter.java:5)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:5)#00 pc 0000000000064060  /system/lib64/libcrypto.so (bio_ctrl+144)
#01 pc 00000000000615d8  /system/lib64/libcrypto.so (BIO_ctrl_pending+40)
#02 pc 00000000000387dc  /apex/com.android.conscrypt/lib64/libjavacrypto.so (_ZL45NativeCrypto_SSL_pending_written_bytes_in_BIOP7_JNIEnvP7_jclassl+20)

bio多線程.jpeg

問題分析

從設備分布上看,出問題都全是Android 11且各個國內廠商的設備都有,懷疑是Android 11引入的bug,對比了Android 11 和 Android 12的源碼,發現在Android12 崩潰堆棧中的相關類 com.android.org.conscrypt.NativeSsl$BioWrapper有四個方法增加了讀寫鎖,此時懷疑是多線程問題,通過搜索Android源碼的相關issue以及差異代碼的MR描述信息,進一步確認此結論。通過源碼進一步分析發現NativeSsl的所有加鎖的方法,會分發到NativeCrypto.java中的native方法,最終調用到native_crypto.cc中的JNI函數,如果能hook到相關的native函數并在Native層實現與Android12相同的讀寫鎖邏輯,這個問題就可以解決了。

https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:external/conscrypt/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSsl.java
https://cs.android.com/android/platform/superproject/+/android-11.0.0_r48:external/conscrypt/repackaged/common/src/main/java/com/android/org/conscrypt/NativeCrypto.java
https://cs.android.com/android/platform/superproject/+/android-11.0.0_r48:external/conscrypt/common/src/jni/main/cpp/conscrypt/native_crypto.cc

解決過程

通過JNI hook代理Android12中增加鎖的相關函數,當走到代理函數中時,先分發到JAVA層通過反射獲取ReadWriteLock實例并上鎖再通過跳板函數調用原來的JNI函數,此時就完成了對Android12 增量鎖邏輯的復刻。經歷了兩個版本的灰度hook方案已穩定在線上運行,期間無因hook導致的網絡不可用和其它崩潰問題,目前開關放全量的版本崩潰設備數已降為0。

解決過程.jpeg
JNI hook原理,以及詳細修復過程: https://blog.dewu-inc.com/article/MTMwNDU?fromType=personal_blog

五、小米Android15 焦點處理空指針崩潰

背景

隨著Android15開放公測,焦點處理過程中發生的空指針問題逐步增多,并在1月份上升到Top。

堆棧與上報趨勢

java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.ViewGroup$LayoutParams android.view.View.getLayoutParams()' on a null object reference
at android.view.ViewRootImpl.handleWindowFocusChanged(ViewRootImpl.java:5307)
at android.view.ViewRootImpl.-$$Nest$mhandleWindowFocusChanged(Unknown Source:0)
at android.view.ViewRootImpl$ViewRootHandler.handleMessageImpl(ViewRootImpl.java:7715)
at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:7611)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loopOnce(Looper.java:249)
at android.os.Looper.loop(Looper.java:337)
at android.app.ActivityThread.main(ActivityThread.java:9568)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)

問題分析

通過分析ASOP的源碼,崩潰的觸發點是mView字段為空。

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/ViewRootImpl.java;drc=98e96368cc73432efbacd6fbcf61fe789dcec0ee;l=7243?q=ViewRootImpl

問題分析5.jpeg
源碼中mView為空的情況有兩種:

  • 未調用setView方法前觸發窗口焦點變化事件(只有setView方法才會給mView賦不為空的值)。
  • 先正常調用setView使mView不為空,其它地方置為空。

結合前置判斷了mAdded為true才會走到崩潰點,在源碼中尋找到只有先正常調用setView以后在調用dispatchDetachedFromWindow時才滿足mAdded=true、mView=null的條件,從采集的logcat日志中可以證明這一點,此時基本可以定位根因是窗口銷毀與焦點事件處理的時序問題。

時序問題.jpeg
時序問題2.jpeg

解決過程

在問題初期,嘗試通過 Hook 攔截 handleWindowFocusChanged 方法增加防御:當檢測到 mView 為空時直接中斷后續邏輯執行。本地驗證階段,通過在 Android 15 設備上高頻觸發商詳頁 Dialog 彈窗的焦點獲取與關閉操作,未復現線上崩潰問題。考慮到 Hook 方案的侵入性風險 ,且無法本地測試,最終放棄此方案上線。

通過崩潰日志分析發現,問題設備100% 集中在小米/紅米機型,而該品牌在 Android 15 DAU中僅占 36% ,因此懷疑是MIUI對Android15某些定制功能有bug。經與小米技術團隊數周的溝通與聯合排查,最終小米在v2.0.28版本修復了此問題,需要用戶升級ROM解決,目前>=2.0.28的MIUI設備無此問題的上報。

六、總結

通過上述問題的治理,系統bug類的崩潰顯著減少,希望這些經驗對大家有所幫助。

文 / 亞鵬

關注得物技術,每周更新技術干貨

要是覺得文章對你有幫助的話,歡迎評論轉發點贊~

未經得物技術許可嚴禁轉載,否則依法追究法律責任。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/72090.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/72090.shtml
英文地址,請注明出處:http://en.pswp.cn/web/72090.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SpringBoot3+Lombok如何配置logback輸出日志到文件

Background/Requirement SpringBoot3Lombok如何配置logback輸出日志到文件&#xff0c;因為我需要對這些日志進行輸出&#xff0c;控制臺輸出和文件輸出&#xff0c;文件輸出是為了更好的作為AuditLog且支持滾動式備份&#xff0c;每天一個文件。 Technical Solution 1.確保你…

主流向量數據庫對比

在 AI 的 RAG&#xff08;檢索增強生成&#xff09;研發領域&#xff0c;向量數據庫是存儲和查詢向量嵌入的核心工具&#xff0c;用于支持高效的語義搜索和信息檢索。向量嵌入是文本或其他非結構化數據的數值表示&#xff0c;RAG 系統通過這些嵌入從知識庫中檢索相關信息&#…

搞定python之四----函數、lambda和模塊

本文是《搞定python》系列專欄的第四篇&#xff0c;通過代碼演示列python自定義函數、lambda和模塊的用法。本文學習完成后&#xff0c;python的基礎知識就完了。后面會學習面向對象的內容。 1、自定義函數 # 測試python自定義函數# 有參數&#xff0c;沒有返回值 def say_he…

[操作系統] 學校課程關于“靜態優先級搶占式調度“作業

今天我們來分享兩道題目哈, 學校弄得題目. T1: 靜態優先級, 搶占式(1為高優先級) 圖解: 以下是靜態優先級搶占式調度的解題過程和結果&#xff1a; 解題思路&#xff1a; 優先級規則&#xff1a; 數值越小優先級越高。新進程到達時&#xff0c;若其優先級高于當前運行進程&…

洛谷P1320 壓縮技術(續集版)

P1320 壓縮技術&#xff08;續集版&#xff09; 題目描述 設某漢字由 N N N \times N NN 的 0 \texttt 0 0 和 1 \texttt 1 1 的點陣圖案組成。 我們依照以下規則生成壓縮碼。連續一組數值&#xff1a;從漢字點陣圖案的第一行第一個符號開始計算&#xff0c;按書寫順序從…

使用DeepSeek完成一個簡單嵌入式開發

開啟DeepSeek對話 請幫我使用Altium Designer設計原理圖、PCB&#xff0c;使用keil完成代碼編寫&#xff1b;要求&#xff1a;使用stm32F103RCT6為主控芯片&#xff0c;控制3個流水燈的原理圖 這里需要注意&#xff0c;每次DeepSeek的回答都不太一樣。 DeepSeek回答 以下是使…

volatile、synchronized和Lock

名詞解釋&#xff1a; 指令重排是計算機為了優化執行效率&#xff0c;在不改變單線程程序結果的前提下&#xff0c;對代碼的執行順序進行重新排列的操作。它可能發生在編譯階段&#xff08;編譯器優化&#xff09;或CPU運行階段&#xff08;處理器優化&#xff09;。 舉個栗子…

嵌入式八股C語言---面向對象篇

面向對象與面向過程 面向過程 就是把整個業務邏輯分成多個步驟,每步或每一個功能都可以使用一個函數來實現面向對象 對象是類的實例化,此時一個類就內部有屬性和相應的方法 封裝 在C語言里實現封裝就是實現一個結構體,里面包括的成員變量和函數指針,然后在構造函數中,為結構體…

Distilling the Knowledge in a Neural Network知識蒸餾

一.知識蒸餾的定義 1. 量化VS蒸餾 量化&#xff1a;減小精度 例如參數float32—>float16蒸餾&#xff1a;Student model模仿Teacher model,在保持較高性能的同時&#xff0c;減少模型大小和計算復雜度的技術。 二.知識蒸餾步驟 1.教師模型訓練: 訓練一個大型且復雜的神…

靜態程序分析

參考&#xff1a;https://github.com/RangerNJU/Static-Program-Analysis-Book/blob/master/SUMMARY.md 課件&#xff1a;https://pascal-group.bitbucket.io/teaching.html 視頻&#xff1a;南京大學《軟件分析》課程01&#xff08;Introduction&#xff09;_嗶哩嗶哩_bilib…

Flutter_學習記錄_device_info_plus 插件獲取設備信息

引入三方庫device_info_plus導入頭文件 import package:device_info_plus/device_info_plus.dart;獲取設備信息的主要代碼 DeviceInfoPlugin deviceInfoPlugin DeviceInfoPlugin(); BaseDeviceInfo deviceInfo await deviceInfoPlugin.deviceInfo;完整案例 import package…

日有所得-google 瀏覽器離線安裝

一、目標&#xff1a; 基于UOS系統進行瀏覽器插件開發&#xff0c;目標展現形式為側欄 二、背景&#xff1a; UOS操作系統需支持1032及以上版本 瀏覽器插件基于google瀏覽器&#xff0c;自帶360等瀏覽器能兼容基于google瀏覽器開發的插件 JS庫借用Vue庫以提高效率 三、問…

高效自動化測試:打造Python+Requests+Pytest+Allure+YAML的接口測試框架

一、背景 在快節奏的開發周期中&#xff0c;如何確保接口質量&#xff1f;自動化測試是關鍵。通過構建標準化、可復用的測試框架&#xff0c;能顯著提升測試效率與準確性&#xff0c;為項目質量保駕護航[1][7]。 二、目標 ? 核心目標&#xff1a; ● 實現快速、高效的接口測試…

談談List,Set,Map的區別

List、Set 和 Map 是 Java 集合框架&#xff08;Java Collections Framework&#xff09;中的三種主要接口&#xff0c;它們各自有不同的特點和用途。以下是它們的區別和使用場景的詳細解釋&#xff1a; 1. List&#xff08;列表&#xff09; 1.1 特點 有序集合&#xff1a;Li…

智能運維管理系統的主要優勢

智能運維管理系統通過整合大數據、人工智能、機器學習等技術&#xff0c;顯著提升了IT運維的效率和質量。以下是智能運維管理系統的主要優勢&#xff1a; 一、提升運維效率 1.自動化運維 自動執行重復性任務&#xff08;如日志分析、故障排查、系統備份&#xff09;&#xf…

分享一個用來解決運維問題的 AI 提示詞

模板如下&#xff08;每次我都是自己寫的&#xff0c;但是感覺可以更加調優一些&#xff09; 我遇到了如下問題<問題的清晰描述>你是一位資深運維工程師&#xff0c;任務是指導我一步步排查并解決上面的問題排查過程中&#xff0c;你給我操作指示&#xff0c;我將操作的…

【python運行Janus-Pro-1B文生圖功能】

前言 體驗了一把本地部署Janus-Pro-1B實現文生圖功能。 1、開源項目下載 官方開源項目代碼直接從Github上下載。 2、模型下載 模型官方下載需要魔法 Janus-Pro-1B模型文件&#xff1a;Janus-Pro-1B模型文件 百度網盤&#xff1a; https://pan.baidu.com/s/16t4H4z-QZe2UDAg4…

跨越時空的對話:圖靈與GPT-4聊AI的前世今生

&#xff08;背景&#xff1a;虛擬咖啡廳&#xff0c;圖靈身著1950年代西裝&#xff0c;端著一杯熱茶&#xff0c;GPT-4以全息投影形態坐在對面&#xff09; 圖靈&#xff08;喝了口茶&#xff09;&#xff1a;“聽說你能寫詩&#xff1f;我當年在布萊切利園破解Enigma時&…

L2-4 吉利矩陣

輸入樣例&#xff1a; 7 3輸出樣例&#xff1a; 666 這道題是暴力純搜&#xff0c;但是很難想&#xff0c;我這個是看的別人的代碼 #include "bits/stdc.h" using namespace std; int x[20][20]; int l, n; int cnt 0; int sumx[5], sumy[5]; void dfs(int x, in…

Quickwit+Jaeger+Prometheus+Grafana搭建Java日志管理平臺

介紹 生產服務應用可觀測性在當下比較流行的方案&#xff0c;其中出現了大量高性能、開箱即用、易上手的的開源產品&#xff0c;大大豐富了在可觀測性領域產品的多樣性&#xff0c;本文講述基于OTLP協議推送Java項目遙測數據&#xff08;日志、指標、鏈路&#xff09;到后端存儲…