再談 iOS App Crash 防護

2022-01-31 老司機技術周報

作者:Parsifal, iOS 開發者,目前就職於微醫

本文表於 2018/05/20, 《SwiftOldDriver 精選》

在移動開發中,App 的閃退率是工程師十分關注且又頭疼的事情。去年,網易杭州研究院曾經針對 crash 的防護有提出『大白健康系統--iOS APP 運行時 Crash 自動修復系統[1]』方案,使得 crash 防護這個想法真正被落實,但至今該方案的具體實現並沒有被開源。圈子裡也有一些開發朋友,基於這套方案設計並開源了自己的 「Baymax」,比如『老司機 iOS 周報第七期[2]』中曾提到的 BayMaxProtector[3]。本文將會針對網易 Baymax 這套方案,結合團隊內的實踐結果,總結其在生產環境中可能遇到的問題及其解決方案,並提出一些自己對這套方案的思考。友情提示,閱讀本文前需對網易『大白健康系統--iOS APP 運行時 Crash 自動修復系統』一文有所了解,該文中已有的實現方案,本文不會再花更多筆墨進行贅述。

Crash 防護可選的方案Crash 是什麼?

在探討 Crash 防護的方案之前,我們有必要對計算機領域 Crash 這個概念進行重新認識。對於 [Crash 的概念](https://en.wikipedia.org/wiki/Crash_(computing "Crash 的概念")),維基百科中是這麼定義的:

In computing, a crash (or system crash) occurs when a computer program, such as a software application or an operating system, stops functioning properly and exits.

An application typically crashes when it performs an operation that is not allowed by the operating system. The operating system then triggers an exception or signal in the application. Unix applications traditionally responded to the signal by dumping core. Most Windows and Unix GUI applications respond by displaying a dialogue box (such as the one shown to the right) with the option to attach a debugger if one is installed. Some applications attempt to recover from the error and continue running instead of exiting.

對於我們 iOS 應用層的 App,可簡單總結為應用執行了某些不被允許的操作觸發了系統拋出異常信號但又沒有處理這些異常信號從而被殺掉的現象,比如常見的閃退(crash to desktop)。在我們開發領域從拋出異常的對象上來看,一共可以分為三類內核導致的異常應用自身的異常其他進程導致的異常

由作業系統內核捕獲硬體產生的異常信號,比如 EXC_BAD_ACCESS,這類異常如果沒有被處理掉的話,會被轉發到 SIGBUS 或 SIGSEGV 等類型的 BSD 信號;

由 SDK 開發者或上層應用開發者主動拋出的異常信號,比如各種常見的 NSException,這類異常蘋果為了統一處理,最終會被轉發為 SIGABRT 類的 BSD 信號;

這裡我們主要談最常見的前兩種異常。

可選的 Crash 防護方案

上面已經提到了 Crash 實際上我們觸發了異常,但又沒有去處理這些異常而導致的結果。那麼很自然的第一個防護方案便可以想到是去處理這些異常

通過 NSUncaughtExceptionHandler 來捕獲並處理異常

蘋果的確提供有異常捕獲的 API 以供開發者使用——NSSetUncaughtExceptionHandler[4],開發者只需要傳入處理函數的指針,便可以處理掉應用中拋出的 NSException 類的異常。代碼寫起來就是:

NSSetUncaughtExceptionHandler(&HandleException);

通過 BSD 的 signal 來捕獲並處理異常

由於蘋果將所有異常最終都轉換成了 BSD 信號的發出,那麼我們就可以去捕獲這個信號來處理這些異常,從而達到 Crash 防護的目的。系統也有提供相關 API 實現:

void (*signal(int, void (*)(int)))(int);

前一個參數為異常類型,可以是 SIGSEGV 等這類,後一個參數為回調的函數,代碼寫起來就可以是:

signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);

注意:由於 Xcode 默認會開啟 debug executable,它會在我們捕獲這些異常信號之前攔截掉,因此做這個測試需要手動將 debug executable 功能關閉,或者不在 Xcode 連接調試下進行測試。

至此,似乎一切看起來都很順利,然而實踐過程中你會發現程序並沒有在你處理完這些異常後就能繼續進行。這與 iOS 的 Runloop 機制有關,在觸發異常後,Main Runloop 將不會繼續運行,這也就意味著 App 跑不起來了。當然,你可能會很自然地聯想到,我自己再把 Main Runloop 繼續掛起來跑不就行了嗎?如以下類似代碼:

//這裡取到的是 Main Runloop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

while (YES)
{
    for (NSString *mode in (NSArray *)allModes)
    {
        CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
    }
}

CFRelease(allModes);

這樣一試,確實程序在捕獲異常之後又能夠繼續運行了。但『通過 NSUncaughtExceptionHandler 來捕獲並處理異常』和『通過 BSD 的 signal 來捕獲並處理異常』這兩種方式去做 Crash 防護並不是一種靠譜的方式,原因有以下幾點:

iOS/OSX 在被拋出異常後,被認為是不可恢復的,如果我們強行恢復 Runloop,整個 App 的不確定性將會更大,crash 的部分可能會再次發生;

內核拋出的異常一般都是較嚴重的底層硬體問題,如果這類問題不及時停止程序運行,可能會進一步影響整個系統的運行,乃至損壞硬體;

以上兩種做法,通常是用於 Crash 日誌收集上,如果我們防護層也通過這個方案去做的話,衝突的可能性會很大;

這裡附帶下『App Architecture book』作者 Matt Gallagher[5] 早年對於這部分研究後的一個 demo[6],由於是 MRC 時代的代碼了,修改了部分配置使得能夠正常編譯且測試。

通過 try-catch 的組合拳來捕獲異常

和其他程式語言一樣,Objective-C 中也有萬能的 try-catch 組合來捕獲異常,這樣處理不就可以了?這種方案確實是可行的,我也確實有見過一些人使用 try-catch 來做一些常見的 Crash 防護。但 Objective-C 的 try-catch 實際上有先天缺陷的,首先是效率並不高,甚至某些情況下會導致內存洩漏,不可控。

效率不高是由於 try-catch 是基於 block 的處理方案,會多出額外的開銷(不過蘋果已經重寫了 64 bit 機器上的 try-catch,而且聲明是 zero-cost[7]);

可能會內存洩漏是由於 Xcode 默認並不會對 try-catch 中的代碼進行 ARC 管理。try 在捕捉到 Exception 之後,會立即轉到 catch 中執行,這樣就導致了如果 release 代碼是寫在 try 中 throw 異常的代碼之後的話,就會不被執行而導致內存洩漏。如果為了防止這個洩漏而去配置 -fobjc-arc-exceptions 選項,更會因為生成低效代碼而得不償失,這也是蘋果並不推薦的方式。

但這不能完全否定 try-catch 組合在我們日常編程中的作用,在一些容易出現異常的操作上,比如文件讀寫或者需要配合使用 throw 的情況等。這裡指的不適合,只是針對在大範圍防護並不適合。

Baymax 的方案

在綜合分析了以上幾個防護方案後,我們再來看看 Baymax 中採用的方案。如果說上面三種方案都是在已經拋出了異常之後再去捕獲處理,也就是 「喝後悔藥」 的機制,那麼 Baymax 的方案便是不讓這些異常產生。不讓錯誤異常產生可以通過多種做法,往項目管理上說提高代碼質量,增加 Code Review 等,從編碼角度來說,我們可以通過各種保護性代碼進行。Baymax 中的大部分防護方案都可以理解為一種為你自動增加保護性代碼的措施。比如,各種 Collection 類型,String 類型等。

實踐 Baymax 方案中可能遇到的問題高頻調用方法的性能問題

Baymax 是基於 AOP 思想而設計的,方案中會充斥著各種 Hook 系統方法,這對於高頻調用的方法,性能上的損耗是不可忽略的。為了將損耗儘量降低,我們可以通過只防護特定類來進行,比如只針對我們的自定義類和部分在防護名單內的類,而對於系統的類,我們不進行防護,這樣就能在一定限度上降低性能損耗。對於判斷自定義類可以通過以下方法進行:

如果只是判斷 main bundle 的話可以通過以下代碼進行:

+ (BOOL)isMainBundleClass:(Class)cls {
    return cls && [[NSBundle bundleForClass:cls] isEqual:[NSBundle mainBundle]] ;
}

但在組件化開發中,我們的代碼會通過各種私有 pod 的形式導入,這樣只判斷 main bundle 的方式就不夠用了,我們可以通過以下代碼進行:

+ (BOOL)isCustomClass:(Class)cls {
    ///var/containers/Bundle/Application/CB0D354B-DD08-4845-A084-A22FF01097FE/Example.app
    NSString *mainBundlePath = [NSBundle mainBundle].bundlePath;
    ///var/containers/Bundle/Application/CB0D354B-DD08-4845-A084-A22FF01097FE/Example.app/Frameworks/Baymax.framework
    NSString *clsBundlePath = [NSBundle bundleForClass:cls].bundlePath;
    
    return cls && mainBundlePath && clsBundlePath && [clsBundlePath hasPrefix:mainBundlePath];
}

另外,由於判斷是否防護的條件會相對比較多,這裡可以引入名單緩存來做進一步的效率優化,將本次判斷結果存儲到 NSCache 中,下回優先從 Cache 裡讀取防護狀態,性能提升將會十分顯著。大致代碼如下:

//先從緩存中讀取狀態
NSNumber *status = [baymax needBaymaxStatusInProtectionCache:clsStr];
//如果有在緩存中 則直接返回緩存中的狀態 若不在緩存中 則繼續走判斷邏輯
if (status != nil) return [status boolValue];

UnrecognizedSelector 防護的坑

蘋果在 KVO 的實現中,為每種類型都封裝了一個特定的 set 方法,原因未知(或許又是 Historical Reasons 吧),這裡涵蓋了 CoreFoundation 裡的所有基礎類型。

_NSSetBoolValueAndNotify、_NSSetCharValueAndNotify、_NSSetDoubleValueAndNotify、_NSSetFloatValueAndNotify、_NSSetIntValueAndNotify、_NSSetLongLongValueAndNotify、_NSSetLongValueAndNotify、_NSSetObjectValueAndNotify、_NSSetPointValueAndNotify、_NSSetRangeValueAndNotify、_NSSetRectValueAndNotify、_NSSetShortValueAndNotify、_NSSetSizeValueAndNotify、_NSSetUnsignedCharValueAndNotify、_NSSetUnsignedIntValueAndNotify、_NSSetUnsignedLongLongValueAndNotify、_NSSetUnsignedLongValueAndNotify、_NSSetUnsignedShortValueAndNotify

除這些類型外的其他類型(比如 UIKit 中的 struct 或者其他自定義的 struct)被作為 property 觀察時,都會走以下的轉發邏輯。這樣的處理邏輯在特定的情況下就會影響防護,比如 UIEdgeInsets 類型的 property 被加入 KVO 檢測,那麼之後再 set 這個 property 的時候,set 方法就會進入轉發邏輯,這樣就會被誤識別為一次UnrecognizedSelector 的 Crash,且導致原有的 KVO 邏輯失效。

<_NSCallStackArray 0x100700630>(
       0   ???                                 0x00000001001f3ecd 0x0 + 4297014989,
       1   KVOAnalysisDemo                     0x0000000100001850 main + 0,
       2   Foundation                          0x00007fff981fd67d NSKeyValueNotifyObserver + 350,
       3   Foundation                          0x00007fff981fcf14 NSKeyValueDidChange + 486,
       4   Foundation                          0x00007fff981cbdf6 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 118,
       5   Foundation                          0x00007fff9829cc11 NSKVOForwardInvocation + 325,
       6   CoreFoundation                      0x00007fff967c65fa ___forwarding___ + 538,
       7   CoreFoundation                      0x00007fff967c6358 _CF_forwarding_prep_0 + 120,
       8   KVOAnalysisDemo                     0x000000010000198b main + 315,
       9   libdyld.dylib                       0x00007fffabf2d235 start + 1
       )

解決方案是通過判斷是否重寫相關轉發方法決定是否需要防護,主要代碼如下:

BOOL isMethodOverride = ([self isMethodOverride:cls selector:@selector(forwardInvocation:)] || [self isMethodOverride:cls
selector:@selector(forwardingTargetForSelector:)]);
       
if (!isMethodOverride) {
           return YES;
}

+ (BOOL)isMethodOverride:(Class)cls selector:(SEL)sel {
    IMP selfIMP = class_getMethodImplementation(cls, sel);
    IMP superIMP = class_getMethodImplementation(class_getSuperclass(cls), sel);
    
    return selfIMP != superIMP;
}

iOS SDK 在不斷調整

由於 iOS 系統的封閉性,系統 API 的實現我們是無法直接看到的。而蘋果有可能在更新系統版本的時候,出於各種原因對一些 API 進行調整。在測試中已發現有以下幾個系統類在 iOS8-iOS10 中被調整過:

po [@[] class] before iOS8:__NSArrayI later:__NSArray0
po [@[@1] class] before iOS9:__NSArrayI iOS10:__NSSingleObjectArrayI
po [objc_getClass(&quot;NSTaggedPointerString&quot;) superclass] before iOS8:NSObject after iOS8:NSString

以上這些實現的調整,造成的影響均是 method-swizzling 的失敗。但從實際測試情況來看,雖然以上類有做了調整,但其實並不影響防護。比如,__NSArray0 在 iOS8 中是__NSArrayI 代替,而 __NSArrayI 這個類在 iOS8 或者之後的系統都是會被防護的。

BadAccess 防護中原 dealloc 方法的延遲調用

BadAccess 防護的核心原理是延遲內存釋放,這裡就需要在之後的某個合適的時機,手動去調用原有的釋放方法來執行真正的內存釋放。但在實際開發中,發現直接去調用保存的原 dealloc,並不能做到正確釋放內存。排查搜索之後,發現這可能是在 ARC 環境下,蘋果對 dealloc 方法的特殊處理導致的,在 method-swizzling 後,原 dealloc 的 selector 實際上已經變成了轉發後的 selector 了,而猜測目前 ARC 的對 dealloc 的處理只認 dealloc 這個 selector,所以唯一的方法處理便是還是通過 imp(obj, NSSelectorFromString(@"dealloc")) 來調用。

目前的解決方法:直接用 c 函數傳 imp 和 dealloc 調用,主要代碼如下:

// Get Original Dealloc IMP.
// See more in JSPatch:https://github.com/bang590/JSPatch/blob/master/JSPatch/JPEngine.m
Class objCls = object_getClass(obj);
Method deallocMethod = class_getInstanceMethod(objCls, NSSelectorFromString(@&quot;wycd_dealloc&quot;));
void (*originalDealloc)(__unsafe_unretained id, SEL) = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);
originalDealloc(obj, NSSelectorFromString(@&quot;dealloc&quot;));

NSArray 防護後出現的奇葩問題

Hook 掉 objectAtIndex:方法後,在這樣一個場景下會出現意外的 crash:調出系統鍵盤再把 App 切到後臺,就出現 [uikeyboardlayoutstar release] message sent to deallocated instance crash。這其實是 iOS 系統在 ARC 下的一個坑,ARC 導致了 over-released 的 crash,暫時沒有其他更好的解決方案,只能把這部分防護改為 MRC 編寫。

如何保證 SDK 更新的穩定性

Baymax 方案涉及到很多的系統方法,那麼怎麼保證每一次更新迭代不會造成嚴重的線上問題呢?這最終還是要落實到單元測試上,我們可以給 Baymax 編寫足夠完善的單元測試用例,然後配置一個觸髮腳本,來自動地在我們每次 push 到開發分支時跑這些測試用例。當然,必須值得注意的是,測試必須覆蓋到你當前支持的所有 iOS 版本,如果是使用 GitLab Runner 可以按如下配置做:

test_job:
  only:
    - UnitTest
  stage: test
  script:
    - export LC_ALL='en_US.UTF-8'
    - xcodebuild clean -workspace Example/Baymax.xcworkspace -scheme Baymax-Example | xcpretty
    - pod install --project-directory=Example
    - xcodebuild test -workspace Example/Baymax.xcworkspace -scheme Baymax-Example -destination 'platform=iOS Simulator,name=iPhone 5s,OS=11.2' -destination 'platform=iOS Simulator,name=iPhone 5s,OS=9.3' -destination 'platform=iOS Simulator,name=iPhone 5s,OS=8.4' | xcpretty -s

大致的單元測試代碼可以如下:

- (void)testCrashProtection {
    //given when
    Baymax *baymax = [Baymax sharedInstance];
    [baymax configBaymaxType:BaymaxAll];
    [baymax start];
    
    //then
    for (int i = 0 ; i < kBaymaxType; i++) {
        NSUInteger type = 1 << i;
        Tester *tester = [Tester tester:type];
        
        NSUInteger caseCount = [[tester testCaseSelectors] count];
        
        for (int j = 0; j < caseCount; j++) {
            XCTAssertNoThrow([tester executeTestCase:j]);
        }
    }
}

防護的代價是什麼

任何事物我們都從正反兩方面考慮,既然 Baymax 提供了防護功能,那其必然也存在著弊端。

首先,第一點就是上面提到的性能問題,在方案調研階段,筆者曾經使用 XCTest 對 Collection 類型的防護做了部分的性能測試,結果大致如下:

不做 HookTest Case '-[PerformanceTests testPerformance_Collection]' measured [Time, seconds] average: 0.000, relative standard deviation: 151.327%, values: [0.000011, 0.000002, 0.000001, 0.000001, 0.000001, 0.000001, 0.000001, 0.000001, 0.000001, 0.000001]

做了 Hook 但是不觸發防護邏輯Test Case '-[PerformanceTests testPerformance_Collection]' measured [Time, seconds] average: 0.000, relative standard deviation: 83.636%, values: [0.000021, 0.000005, 0.000005, 0.000009, 0.000003, 0.000003, 0.000003, 0.000003, 0.000009, 0.000003]

做了 Hook 且觸發了防護邏輯Test Case '-[PerformanceTests testPerformance_Collection]' measured [Time, seconds] average: 0.000, relative standard deviation: 47.857%, values: [0.000026, 0.000010, 0.000009, 0.000009, 0.000008, 0.000009, 0.000009, 0.000008, 0.000009, 0.000009]

從上面數據可以很直觀地看到,在不做任何優化的前提下性能下降十分明顯,效率損失甚至高達 3 倍以上,所以如果要做防護,必須充分考慮到性能優化這些點。

其次,需要合理權衡開啟的防護類型,目前我們僅默認開啟線上反饋的常見類型,而不是開啟所有類型,其他類型可以配置為動態開啟,根據用戶設備的閃退日誌開啟防護。其中,Baymax 中提到的野指針防護,在實踐中發現用處很有限,因為只是做了延遲釋放,而不是真正意義上對野指針這種 crash 進行防護,且由於對系統的釋放時機進行了處理,與 Xcode 原來的 Zombie 機制有一定衝突,也會產生一些很奇葩的問題,不確定性很高。

再次,各種Hook帶來的未知性,Crash 本身是非正常情況下才產生的,如果一味地規避這種異常,可能會產生更多的異常情況,特別是業務邏輯上會出現不可控制的流程。

最後,這套防護方案的作用究竟有多大呢?根據筆者個人經驗來說,對于越成熟的團隊,防護方案帶來的效果會越小。因為成熟團隊的代碼質量相對更高,一些低級錯誤出現的概率極小。但對於小團隊,或者歷史比較久的項目而言,這套方案帶來的幫助會比較大,畢竟坑總是防不勝防的。

推薦閱讀

✨ iOS Crash 分析攻略

✨ 初探 Objective-C/C++ 異常處理實現機制

iOS 穩定性:App 被終止的原因

關注我們

我們是「老司機技術周報」,每周會發布一份關於 iOS 的周報,也會定期分享一些和 iOS 相關的技術。歡迎關注。

這篇文章的內容來自於《SwiftOldDriver 精選》。關注【老司機技術周報】,回復「2020」, 即可領取。

參考資料[1]

大白健康系統--iOS APP 運行時 Crash 自動修復系統: https://neyoufan.github.io/2017/01/13/ios/BayMax_HTSafetyGuard/

[2]

老司機 iOS 周報第七期: https://github.com/SwiftOldDriver/iOS-Weekly/blob/master/Reports/%237.md#ios-kvo-crash-自修復技術實現與原理解析

[3]

BayMaxProtector: https://github.com/sunday1990/BayMaxProtector

[4]

NSSetUncaughtExceptionHandler: https://developer.apple.com/documentation/foundation/1409609-nssetuncaughtexceptionhandler?language=objc

[5]

Matt Gallagher: https://www.cocoawithlove.com/about/

[6]

demo: https://github.com/ParsifalC/UncaughtExceptions

[7]

64 bit 機器上的 try-catch,而且聲明是 zero-cost: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Exceptions/Articles/Exceptions64Bit.html#//apple_ref/doc/uid/TP40009044-SW1

相關焦點

  • iOS Crash 殺手排名
    殺手 NO.1:NSInvalidArgumentException 異常出現這個crash的原因有很多,選取了崩潰次數較多的crash。這是一個讓程序終止的標識,會在斷言、app內部、作業系統用終止方法拋出。通常發生在異步執行系統方法的時候。如CoreData、NSUserDefaults等,還有一些其他的系統多線程操作。
  • [譯]《iOS Crash Dump Analysis》- 符號化
    = 0.0);DSYM 結構DSYM 文件嚴格來說是一個目錄層次結構:icdab_planets.app.dSYMicdab_planets.app.dSYM/Contentsicdab_planets.app.dSYM/Contents/Resourcesicdab_planets.app.dSYM/Contents/Resources/
  • 淺談 iOS Crash(一)
    從中找到.dSYM和.app文件xcarchive所在的路徑一般在: /Users//Library/Developer/Xcode/Archives 目錄下3)獲取crash日誌文件4)將symbolicatecrash、.dSYM、.app、crash.crash拷貝到桌面下同一個文件夾下5)檢查 xx.app 和 xx.app.dSYM
  • iOS Crash 分析攻略
    為了維護一個統一的機制,作業系統和用戶產生的信號首先被轉換為 "Mach異常",然後再轉換為信號。EXC_RESOURCE無資源受限線程調度太頻繁,子線程每秒被喚醒次數超過150: https://stackoverflow.com/questions/25848441/app-shutdown-with-exc-resource-wakeups-exception-on-ios
  • iOS實錄14:淺談iOS Crash
    4)將symbolicatecrash、.dSYM、.app、crash.crash拷貝到桌面下同一個文件夾下5)檢查 xx.app 和 xx.app.dSYM 文件以及crash 文件這三種的 UUID是否一致。
  • iOS App 連續閃退時如何上報 crash 日誌
    crash 日誌能夠準確上報。App 無限循環 crash 時上報crash 日誌上報時,會發送網絡請求,如果請求成功之前 App 又發生 crash 該如何處理?用戶甚至會陷入無限循環的 crash 中。這篇文章介紹下出現第二種情況時,如何準確上報 crash 日誌。首先我們需要一種比較可靠的方式,可以在 app 啟動時判斷上次是否發生了啟動 crash。
  • App Crash原因及美團外賣Crash治理之路
    ,找到一個data_app_anr@xxx.txt文件4,在data_app_anr@xxx.txt文件中找到"main" prio=5 tid=1 Nativ這一行,往下看會有一些異常log,這些log描述的就是問題原因ANR如果是由於主線程阻塞,在data_app_anr@xxx.txt中的"main" prio=5 tid=1 中會顯示 block那麼我們看下美團外賣
  • iOS Crash防護你看這個就夠了(上)
    0x2 為什麼要寫這篇文章起因也是因為自己的項目踩了FB的SDK的坑:2020.7.10,FB後臺下發數據錯誤,導致大量使用FB SDK的App發生啟動Crash,影響用戶之多,範圍之大,再加上當時包括我們的大部分App也缺乏相關的防護或者是容錯處理,Crash率瞬間飆升,重新發版又要走發布流程,只能依賴FB後臺的修復,當時束手無策十分被動,
  • 如何調試支付寶(iOS)
    53、按提示操作越獄砸殼1、我使用的是frida-ios-dump,詳細使用步驟可以看作者的githubfrida-ios-dump, 遇到的問題如下• 更新fria一直卡住 ○ 終端先設置代理再執行命令 export https_proxy=你的代理如http://xxx.xxx.com:
  • 深入理解iOS Crash Log
    Symbolication剛剛我們拿到的crash log的函數棧:...Crash Log的工具cd /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources.
  • iOS App 後臺 Crash 調查
    如果 30 秒內連續收到兩個 PushKit 通知,那麼 App 總共的後臺運行時間加起來還是 30 秒,如果 30 秒之後被 Suspend,再收到第二個 Push,那麼又可以獲得額外的 30 秒。Background Crash 調查以上是 iOS App 後臺運行的現有機制的簡單介紹,最近在調查一個 background crash,需要用到上述的後臺運行機制。用戶抱怨 App 的 cold start 非常頻繁,導致耗電嚴重。
  • 簡單分析 App 進程 Crash 機制
    App進程的創建要分析一個app進程是怎麼沒的,先看看app進程是怎麼來的。, uid, uid, gids, debugFlags, mountExternal,                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,                        app.info.dataDir, invokeWith,
  • Google App Crash 參考解決方案
    ConcurrentModificationException Crash四、setupwizard ActivityNotFoundException Crash五、setupwizard On-body ActivityNotFoundException六、Google play Service NullPointerException Crash七、恢復出廠設置或者第一次開機,先閃壁紙再顯示開機嚮導
  • 聊聊蘋果的Bug - iOS 10 nano_free Crash
    它的crash堆棧大致為:這兩種跡象表明,這很可能是蘋果的bug。按流程,我們向蘋果提了bug report,並得到回覆:「iOS 10.2 Beta有穩定性提升」。終於等到iOS 10.2 Beta發布,我們重新統計了此類crash的系統版本分布。發現不僅在10.2 Beta正常,而且iOS 9也沒有crash。
  • App Crash 詳解
    如果app使用Java語言編寫,那麼,當Throwable拋出未處理的異常時,此時就會引起 app Crash。如果app 使用native-code編寫,那麼,當執行時遇到未處理的signal(例如SIGSEGV)時,app就會 Crash並退出。當應用程式崩潰時,Android終止應用程式的進程並顯示一個對話框,讓用戶知道應用程式已經停止。
  • App為什麼會crash?事情沒有你想得那麼簡單
    看到這個問題,馬上就可以回答出來:因為拋出異常就會 crash。那麼為什麼拋出異常就會 crash 呢?有沒有辦法不讓 App crash 呢?先探討一下第一個問題吧:為什麼拋出異常就會 crash。我們先試一下主動拋出異常的效果吧,先是在 MainActivity 裡面放置一個 Button,讓它點擊可以主動拋出異常:package com.netease.demo;import android.os.Bundle;import android.view.View;import androidx.appcompat.app.AppCompatActivity
  • 深入理解Android Crash 流程
    crash在內),對於Crash相信很多app開發者都會遇到,那麼上層什麼時候會出現Crash呢,系統又是如何處理Crash的呢。再回到mMap,這是以進程name為key,再以(uid為key,以ProcessRecord為Value的)結構體作為value。
  • 關於面試總結13-app測試面試題
    面試app測試崗位會被問到哪些問題,怎樣讓面試管覺得你對APP測試很精通的樣子?本篇總結了app測試面試時候經常被問的10個相關問題1.什麼是activity?2.Activity生命周期?3.Android四大組件?4.app測試和web測試有什麼區別?5.android和ios測試區別?6.app出現ANR,是什麼原因導致的?
  • 你知道App為什麼會Crash嗎?
    比如收到msg=H.LAUNCH_ACTIVITY,則調用ActivityThread.handleLaunchActivity()方法,最終會通過反射機制,創建Activity實例,然後再執行Activity.onCreate()等方法;再比如收到msg=H.PAUSE_ACTIVITY,則調用ActivityThread.handlePauseActivity()方法
  • iOS 中常見 Crash 總結
    3、EXC_BAD_ACCESS4、KVO引起的崩潰5、集合類相關崩潰6、多線程中的崩潰7、Socket長連接,進入後臺沒有關閉8、Watch Dog超時造成的crash9、後臺返回NSNull導致的崩潰,多見於Java做後臺伺服器開發語言1、找不到方法的實現