大年初一微信閃退?看看如何修復的


歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐干貨哦~

本文由 微信終端開發團隊團隊發布在騰訊雲+社區

前言

相信大家都遇到過一段特殊文本可以讓iOS設備所有app閃退的經歷。前段時間大年初一,又出現某個印度語字符引起iOS11系統奔潰。所幸微信客戶端做了保護並沒有引起太大問題。一般來說,特殊字符閃退是系統漏洞引起,只要更新系統就行。但大部分用戶不願意更新系統,而蘋果也不一定第一時間解決問題。另外后台可以攔截惡意文本傳遞,但對於本地已下發的消息,后台沒有辦法讓它刪除。所以客戶端還是要做些保護預防特殊字符閃退。

方案

由於無法事先知道字符串里包含特殊字符,所以只能先讓它排版/繪制,看看是否出現問題。做法是,在排版/繪制字符串前,先設置標記位,排版/繪制結束后,移除標記位;一旦發現標記位存在,就意味着這字符串可能有問題,下次就不顯示這個字符串:

這里有幾個問題:

  1. 有可能在排版/繪制過程中,其它線程crash,導致標記位不能正常移除。所以crash時要判斷crash線程是否為排版/繪制線程。
  2. 究竟crash多少次才能判斷這字符串是有問題的。最早做法是crash一次就直接屏蔽,但很多用戶反饋,說某些好友昵稱無法顯示。其實iOS繪制字符串時也會極少概率出現閃退,從而誤判。但crash兩次才屏蔽的話,如果用戶連續收到N條惡意消息,那么至少crash 2N次才徹底把所有有問題消息屏蔽。因此,第一次字符串crash先不屏蔽,后續連續字符串crash的話,直接屏蔽。這樣crash N+1次就能處理完了。

整個邏輯代碼大致如下:

// MessageItemView.mm, CP是CrashProtected的簡稱

@implementation MessageItemView

- (void)initContentLabel { m_label = [[MMCPLabel alloc] init]; m_label.cpKey = [MMCPUtil generateKeyWithObject:self.messageModel]; if ([MMCPUtil isUnsafeWithKey:m_label.cpKey]) { // 檢測出messageModel消息內容有問題,屏蔽顯示 m_label.text = @"該內容無法顯示"; } else { m_label.text = self.messageModel.content; } } @end
// MMCPLabel.mm

@implementation MMCPLabel

@synthesize cpKey = m_cpKey; // 對常用的排版/繪制接口做檢查 - (void)layoutSublayersOfLayer:(CALayer *)layer { CScopedCrashCounter crashCounter(m_cpKey); [super layoutSublayersOfLayer:layer]; } - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx { CScopedCrashCounter crashCounter(m_cpKey); [super drawLayer:layer inContext:ctx]; } - (CGSize)sizeThatFits:(CGSize)size { CScopedCrashCounter crashCounter(m_cpKey); return [super sizeThatFits:size]; } @end
// MMCPUtil.mm

// 利用C++特性,在聲明C++類臨時變量時,會自動執行構造函數,離開作用域會執行析構函數 // 因此構造函數做crashCount+1,析構函數做crashCount-1 class CScopedCrashCounter { public: CScopedCrashCounter(NSString *cpKey) { m_cpKey = cpKey; [MMCPUtil increaseCrashCountWithKey:m_cpKey]; } ~CScopedCrashCounter() { [MMCPUtil decreaseCrashCountWithKey:m_cpKey]; } private: NSString *m_cpKey; }; @implementation MMCPUtil @synthesize crashKeyMemoryMappedKV = m_crashKeyMemoryMappedKV; // 被判定為惡意信息對應的key @synthesize crashCountMemoryMappedKV = m_crashCountMemoryMappedKV; // 每個key crash次數 - (BOOL)isUnsafeWithKey:(NSString *)key { return [m_crashKeyMemoryMappedKV getBoolForKey:key] == YES; } - (void)increaseCrashCountWithKey:(NSString *)key { // 這里記錄key所在線程 ... int32_t count = [m_crashCountMemoryMappedKV getInt32ForKey:key]; [m_crashCountMemoryMappedKV setInt32:count + 1 forKey:key] } - (void)decreaseCrashCountWithKey:(NSString *)key { int32_t count = [m_crashCountMemoryMappedKV getInt32ForKey:key]; [m_crashCountMemoryMappedKV setInt32:MAX(0, count - 1) forKey:key]; } // crash回調函數 - (void)onSignalCrash:(siginfo_t *)info { // 先找到跟crash線程相同的key NSString *key = [self lastCPKey:info->si_pid]; if (key == nil) return; if (m_isLastTimeCrashedBySpecialCharacter == NO) { // 設置當前是特殊字符引起的閃退,如果crash次數大於1,則屏蔽這字符串顯示 [self setLastTimeCrashedBySpecialCharacter:YES]; if ([m_crashCountMemoryMappedKV getInt32ForKey:key] > 1) { [m_crashKeyMemoryMappedKV setBool:YES forKey:key]; } } else { // 連續特殊字符閃退,直接屏蔽 [m_crashKeyMemoryMappedKV setBool:YES forKey:key]; } } @end

即使有了上面的N+1優化,當N很大時,客戶端還是要crash很多次才能正常使用。之前有用戶亂掃二維碼被拉進炸群,如果不發紅包,群主不停炸群;用戶頻繁crash,也無法退群。不少用戶會選擇卸載重裝客戶端。因此客戶端要加上安全模式的機制。當客戶端檢測出連續三次crash,下次啟動會出現安全模式的界面,提示用戶如何處理:

對於頻繁閃退的群聊,主界面提供快捷入口方便用戶退群。另外對於可能誤判的字符串,界面也提供入口方便用戶恢復字符串顯示:

為了讓后台第一時間發現新的特殊字符變種,客戶端檢測出特殊字符crash后,會把相關信息上報到后台。通過客戶端上報、后台攔截的閉環,能大大降低特殊字符傳播范圍。這方案不僅用於特殊字符,還能用於其他惡意信息,如炸群消息、GIF、小視頻、鏈接等。

MemoryMappedKV

由於需要埋點的地方太多了,昵稱、消息內容、頭像等等,為了不影響滑動性能,guoling同學開發了一套基於mmap的高性能通用key-value存儲組件,敬請留意WeMobileDev后續文章。

 

問答

短視頻的發展前景以及未來發展方向?

更多與短視頻相關的精華問答,盡在騰訊雲+社區!

相關閱讀

 

談談編程

程序員字典:「牛逼」

碼雲推薦 | Symphony 社區平台的微信小程序

 

此文已由作者授權騰訊雲+社區發布,轉載請注明文章出處

原文鏈接:https://cloud.tencent.com/developer/article/1066209


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM