番外特別篇之 為什麼我不建議你直接使用UIImage傳值?--從一個詭異的相冊九圖連讀崩潰bug談起

来源:http://www.cnblogs.com/ios122/archive/2016/09/22/5895853.html
-Advertisement-
Play Games

所謂"番外特別篇",就是系列文章更新期間內,隨機插入的一篇文章.目前我正在更新的系列文章是 [實現iOS圖片等資源文件的熱更新化](https://github.com/ios122/ios_assets_hot_update).但是,這兩天,被一個自己App中詭異的相冊讀取的Bug困擾,暫時延緩了... ...


關於"番外特別篇"

所謂"番外特別篇",就是系列文章更新期間內,隨機插入的一篇文章.目前我正在更新的系列文章是 實現iOS圖片等資源文件的熱更新化.但是,這兩天,被一個自己App中詭異的相冊讀取的Bug困擾,暫時延緩了文章的更新進度.這個BUG,詭異而又有趣,既然花了10個小時才理清,不妨再投入1個小時,曬出來供大家鑒賞,品玩.

Bug 的詳細描述

詭異的畫風

bug_img

此Bug僅在操作多張高像素圖片時才會觸發,所謂高像素就是圖片本身並不算大,但是圖片寬高非常大的圖片.這次觸發這個問題的是一組 5701 * 3171 的圖片.畫風大家可以點擊鏈接查看原圖自行感受下 --https://github.com/ios122/why_not_uiimage/blob/master/bug_img.jpg?raw=true

當BOSS剛好是一個攝影愛好者

在大多數情況下,是很少有用戶觸發這個問題的,但是BOSS是一個攝影愛好者,手機里有許多高像素圖,一天他想往自己公司的App上傳分享幾張圖片時,他竟然沒法把一次性地從相冊選取九張圖,每次選中後,點擊"確定",都會理解Crash.是的,就是那九張圖,其他圖片是沒問題的,8張圖,也是OK的,他還強調了下是用的最新版本的App.

關於 BUG 的預處理

首先,我的第一反應是肯定是他的手機太燙了吧,重啟下,就好了.恩,肯定是這樣.發佈作品的邏輯,好幾個版本都沒動過.模擬器,手機,我自己試了下,都是OK的.也沒有其他用戶反饋過,fabric也看不到任何log.對,手機太燙了.我稍後,再聯繫他,肯定就OK了.

稍後,再直接聯繫BOSS,竟然還是會Crash,他甚至給我錄屏演示了一下,真的每次都會crash.而且我還無法復現.而且BOSS手機iPhone6 plus,自身記憶體不足的原因非常非常小.

形勢,瞬間變得很緊張,這個問題的優先順序瞬間被提到了最高!再次嘗試了各種可能的情況.圖片大小?它是9張1.5M的圖,我就用9張3M的圖,也是OK的呀!選取時,順序有問題?我試著按照錄屏中演示的順序去選取圖片,也是OK的.一股深深地無力感!竟然連復現都無法復現不了!

最後的最後,說是會拿手機給我測試.不過,最後BOSS的手機,還是沒有拿到,只是拿到了開篇那張畫風詭異的圖片.沒錯,就是它,連續選取9張,就Crash了.

至少,我現在能復現問題了.下麵的,需要的就只是時間,耐心還有大開的腦洞了.

Bug 分析思路的簡要描述

我不覺得,分析Bug真的有什麼思路可言.Bug產生的原因,是有許多可能性的,可能行驗證的順序,方式和深度很大程度上取決於coder本身已有的經驗,天賦,甚至還有些許的運氣!我能描述的,可能僅僅是我處理這個問題的一個相對的完整腦洞過程.部分分析過程間,明顯不是有邏輯性的.越是詭異的問題,越是不能循規蹈矩,要時刻嘗試去問自己最可能地問題是什麼,而不是沿著一條路,一條道走到黑.

1.排除通用邏輯問題

Coder有些許高傲,有時候是有利於自己更冷靜地處理問題的.稍微不自信點的童鞋,可能就會懷疑:我代碼是不是有什麼特殊的臨界判斷沒有加?不行,我得去看看.一行一行,看代碼,從天黑到天亮,從期待到絕望...其實,稍微有一些對比實驗常識的人,都很容易猜到: 兩種情況,唯一的變數是 圖片素材本身,那 最可能 的原因肯定是 圖片本身的問題.一種高大上的說法,這某種程度上,也暗合了所謂的"貪心演算法".每次,都只從最可能的原因入手,管他誰是誰,我的代碼就算有問題,那觸發這個問題的可能性,也是遠小於 圖片素材本身的.---多麼朴素的真理呀!

2.確定是相冊選取圖片記憶體過高

這個問題,在真機上,並不好確定,因為連續讀取9張高像素圖時,記憶體是瞬間飆升的,你幾乎沒有機會去觀察記憶體占用,給人一種因為某種邏輯判斷而導致的Crash的錯覺.如果換做模擬器,會很容易看到,這個記憶體占用,是飆升到G單位的.當然,我也沒那麼睿智,我是單個N個斷點,最終確認了Crash的代碼的準確位置.一個for迴圈,每次step 1,這下很明顯地看到記憶體,幾乎是 100M/張的速度在飆升,而圖片本身的大小隻有 1.5M/張.此處我想說的是,打斷點也是有技巧的,最後沒有辦法的辦法也是講究辦法的.可是試著註釋掉可能的引起的代碼,然後逐步放開註釋,這要觀察,會比直接打斷點快些.--意會!

3.確定是PHImageManager 的問題requestImageForAsset:方法引起的高記憶體占用

當你通過註釋法,配合斷點,很容易就可以引起記憶體高占用的代碼.此處,我的App中,是讀取相冊原圖,用的是 PHImageManagerrequestImageForAsset:targetSize:contentMode:options: resultHandler: 方法.此處接下來的解決思路,有大坑呀!你可能會想,是UIImage載入的問題吧?那就研究下UIImage渲染機制吧.然後1天過去了,等你學成歸來,驀然發現 PHImageManager 是一個系統方法,它載入的圖片機制,你無力干涉!我可能運氣比較好些吧,研究UIImage的渲染機制,想想都頭疼,抱著試一試的態度,我google了下: PHImageManager requestImageForAsset memory high,然後第一條鏈接的第二個回答就是我要到答案: http://stackoverflow.com/questions/33274791/high-memory-usage-looping-through-phassets-and-calling-requestimageforasset

是的,我運氣,似乎總是很好~

4.使用requestImageDataForAsset:替換的問題requestImageForAsset:

答案原文是:

I found that if i switch from

- requestImageForAsset:targetSize:contentMode:options:resultHandler:

to

- requestImageDataForAsset:options:resultHandler:

i will receive the image with the same dimension {5376, 2688} but the size in byte is much smaller. So the memory issue is solved.

hope this help !!

(note : [UIImage imageWithData:imageData] use this to convert NSData to UIImage)

簡單說,就是用 - requestImageDataForAsset:options:resultHandler: 替換 requestImageForAsset:targetSize:contentMode:options:resultHandler: 就可以了,前者是直接返回二進位數據,不渲染.

但是,這裡有一個可能不是問題的問題, 這個方法調用是位於一個名為第三方庫 TZImagePickerController 內,我方便直接改嗎? 我是直接給改了.此處,將來必成大患,以後再用到,肯定還會有相同問題,還不如直接把原來的實現直接替換掉.當然,這也是成本最小的方法.這個庫,本身,已經在App內,深度定製和重寫了,如果一些成熟的第三方庫,這麼做,最好先備份或備註下.

5.使用imageWithData:相容原來的調用

為了和原來的Api介面調用相容,用imageWithData:將NSData轉換為 UIImage 傳出,同時擴展方法,使支持同時傳出 UIImage和原始的 NSData對象.傳出NSData對象的原因是,是因為高像素圖片,會引起一些列的問題,故事到此遠遠沒有結束,詳見衍生問題部分.

6.變更前後的代碼對比

還是來段代碼感受下吧,一碼剩千言:

/*原來的代碼*/
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
            BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
            if (downloadFinined && result) {
                result = [self fixOrientation:result];
                if (completion) completion(result,info);
            }
        }];
/*優化後代碼*/
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:option resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
            UIImage * result = [UIImage imageWithData:imageData];

            BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
            if (downloadFinined && result) {
                result = [self fixOrientation:result];
                if (completion) completion(result,info,imageData);
            }
        }];

此類Bug的可能的通用解決思路

首先,我要說明下,我解決的思路和方式,很大程度上依賴也受限於我已有的經驗,此處的解法,可能不是最優解,最多只能算是個通用解.說不定,將來等我再研究下渲染機制一類的技術,會有一個新的更簡單的方法.歡迎大神補充!

未來遇到UIImage記憶體問題的童鞋,至少能從此處獲取的一個至少驗證可用的解決策略.

回到問題本身,用一句概括就是:永遠不要直接傳遞UIImage對象.在需要傳遞UIImage的場景中,請使用圖片名或者NSData二進位對代替.

衍生問題應用與解決

故事,真的還沒有完結.從相冊順利讀取這張詭異的高像素圖後,我發現我沒有辦法將它上傳,也無法在輪播圖上,連續顯示.簡要概括如下.

無法直接以UIImage格式,連續把九張圖保存到緩存目錄

圖片選取後,並不是立即上傳的,為了能實現"重發"功能,需要在緩存目錄保留副本.原來是將 UIImage 轉換為 NSData寫入.在此過程中,又一次引起了巨額的記憶體開銷.解決方法,就是直接緩存原始獲取的 NSData 的對象,而不要 NSData --> UIImage --> NSData.

無法直接以UIImage格式,連續在輪播圖上顯示九張圖

此處對應的是一個本地大圖預覽功能,實現是在前一個頁面把九張本地圖的UIImage傳遞給輪播預覽組件.此處的坑是: 把一個存放在 數組中的UIImage對象傳遞給 UIImageView的 image屬性,當UIImageView載入到父視圖時,會引起巨額的記憶體占用.原因初步猜測是 UIImage 對象顯示到 UIImageView 會有一個特殊的耗費記憶體的操作,如果原始的 UIImage 對象一直存在,這一塊記憶體那就無法釋放.這一步,困擾了我很久很久,好幾個小時!我真沒想到,一個UIImage對象,竟然會二次引起高記憶體占用.最終的解決方法,就是在前一個頁面傳遞 NSData數組,在賦值處,再使用imageWithData:轉換為 UIImage.這樣,記憶體使用基本沒什麼起伏.

或許,我應該研究下 一個UIImage對象,竟然會二次引起高記憶體占用 的原因.歡迎大神完善!

參考鏈接



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 隨著Android應用開發規模的擴大,客戶端業務邏輯也越來越複雜,已然不是簡單的數據展示了。如同後端開發遇到瓶頸時採用的組件拆分思想,客戶端也需要進行架構設計,拆分視圖和數據,解除模塊之間的耦合,提高模塊內部的聚合度。 開始之前先上一張內部分享時用的PPT圖: 以上是筆者在客戶端開發過程中面臨的問題 ...
  • 1、SpannableString、SpannableStringBuilder與String的關係 首先SpannableString、SpannableStringBuilder基本上與String差不多,也是用來存儲字元串,但它們倆的特殊就在於有一個SetSpan()函數,能給這些存儲的Str... ...
  • Android Weekly Issue #223 中文筆記, 本期內容包括: Offline時間戳處理; Accessibility的安全問題可能並不是個問題; 如何在單元測試和UI測試之間共用代碼; Android中的指紋認證; 編譯時間Kotlin vs Java; MVP結合RxJava,... ...
  • 前言: 單挑Android項目,最近即時通訊用到環信,集成sdk的時候 官方有一句 即:混淆規則。 自己沒寫過關於混淆打包的文章,在此補上。 下麵瞭解Android studio環境下 項目混淆打包的操作。 一、打包: 即 將Android項目生成.apk文件,讓用戶去安裝。 1、工具欄 Build ...
  • 官方地址: http://global.18wifibank.com/ github: https://github.com/yibawifi/wifisdk ...
  • 上一篇記錄了利用系統私有變數和方法實現右滑返回手勢功能:http://www.cnblogs.com/ALongWay/p/5893515.html 這篇繼續記錄另一種方案:利用UINavigationController的delegate方法。 核心代理方法有如下兩個: 第一個代理方法,要求在視圖 ...
  • Fragment是activity的界面中的一部分或一種行為。可以把多個Fragment組合到一個activity中來創建一個多界面並且可以在多個activity中重用一個Fragment。可以把Fragment任務模塊化的一段activity,它具有自己的生命周期,接收它自己的事件,並可以在act ...
  • runtime簡介 RunTime簡稱運行時。OC就是 ,也就是在運行時候的一些機制,其中最主要的是消息機制。 對於C語言, 。 對於OC的函數,屬於 ,在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用。 事實證明: 在編譯階段,OC可以 ,即使這個 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...