目錄
一、內存管理優化
二、動態更新
三、hotfix
四、App安全
五、一些感受
最近事情比較多,很遺憾這是一篇遲來的分享。
DiyCode技術沙龍 是由廣州社區的成員合力舉辦的一場Android技術分享會,因為獲得了一張免費的門票,加上分享的主題都比較感興趣,所以去參加學習一下,總的來說干貨滿滿,見識了很多的技術大牛。
一、內存管理優化
Low Memory Killder(LMK)
常見的進程優先級:
- 前臺進程(Foreground Process)
- 可見進程(Visbile Process)
- 服務進程(Service Process)
- 后臺進程(Background Process)
- 空進程(Empty Process)
oom_adj的值越高,優先級越低,越容易被系統回收
查看oom_adj
在adb shell 中,輸入如下兩個命令即可:
- ps | grep 包名
- cat /proc/進程pid/oom_adj
示例:
E:\asWorkSpace>adb shell
shell@hnSCL-Q:/ $ ps | grep com.android.browser
u0_a7 26894 353 1716624 138140 ffffffff 00000000 S com.android.browser
u0_a7 26963 353 1538696 49280 ffffffff 00000000 S com.android.browser:service
shell@hnSCL-Q:/ $ cat /proc/26963/oom_adj
1
shell@hnSCL-Q:/ $
其中的26894和26963分別是瀏覽器兩個進程的pid
StrictMode(嚴苛模式)
什么是StrictMode ?
StrictMode(嚴苛模式)是用來檢測程序中違例情況的一個開發者工具,最常用的場景是檢測主線程中本地磁盤和網絡讀寫等耗時操作。
能檢測哪些?
嚴苛模式主要用來檢測兩個問題,一個是線程策略,即ThreadPolicy,另一個是VM策略,即VmPolicy。
ThreadPolicy
- 自定義的耗時調用,使用detectCustomSlowCalls()開啟
- 磁盤讀取操作,使用detectDiskReads()開啟
- 磁盤寫入操作,使用detectDiskWrites()開啟
- 網絡操作,使用detectNetwork()開啟
VmPolicy
- Activity泄漏,使用detectActivityLeaks()開啟
- 未關閉的Closable對象泄漏,使用detectLeakedClosableObjects()開啟
- 泄漏的Sqlite對象,使用detectLeakedSqliteObjects()開啟
- 檢測實例數量,使用setClassInstanceLimit()開啟
在哪里開啟?
嚴苛模式的的開啟可以放在Application或者Activity以及其他組件的onCreate方法。為了更好地分析應用中的問題,建議放在Application的onCreate方法。
簡單的開啟方式?
第一種,通過代碼的方式:
if (IS_DEBUG && Build.VERSION.SDK_INT >= 9) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
StrictMode.setVmPolicy(new VmPolicy.Builder().detectAll().penaltyLog().build());
}
嚴苛模式需要在Debug模式開啟,不要在Release版本中啟用。同時,嚴苛模式自API19開始引入,某些方法也從API11開始引入,使用時需注意API級別。
第二種,通過在手機開發者選項中開啟嚴苛模式,開啟之后,如果主線程中有執行時間長的操作,屏幕會閃爍。
常見問題及優化方案
使用合適的Context,一般注冊第三方框架或者SDK時采用Application的Context,除非特地要求傳Activity的Context
多進程應用需要避免Appication onCreate多次執行引起的重復初始化
圖片資源放對位置(優先考慮主流設備,優先考慮用戶分布多的設備)
-
圖片加載優化
1.inSampleSize(降低采樣率) 2.BitmapRegionDecoder(加載超級大圖) 3.Matrix(小圖放大) 4.LruCache/LinkedHaspMap(緩存控制,避免oom) 5.選擇合適的圖片加載框架(UIL、Fresco、Glide、Picasso) 6.按需顯示,優先顯示縮略圖,需要時顯示大圖 7.優化加載圖片的時機
-
主動釋放內存
關鍵函數: onTrimMemory(int level) ,這是4.0以后提供的一個API,系統提供的回調有 - Application.onTrimMemory() - Activity.onTrimMemory() - Fragement.OnTrimMemory() - Service.onTrimMemory() - ContentProvider.OnTrimMemory() 參數level代表不同的內存狀態: - TRIM_MEMORY_COMPLETE:內存不足,并且該進程在后臺進程列表最后一個,馬上就要被清理 - TRIM_MEMORY_MODERATE:內存不足,并且該進程在后臺進程列表的中部 - TRIM_MEMORY_BACKGROUND:內存不足,并且該進程是后臺進程 - TRIM_MEMORY_UI_HIDDEN:內存不足,并且該進程的UI已經不可見了 以上4個是4.0新增 - TRIM_MEMORY_RUNNING_CRITICAL:內存不足(后臺進程不足3個),并且該進程優先級比較高,需要清理內存 - TRIM_MEMORY_RUNNING_LOW:內存不足(后臺進程不足5個),并且該進程優先級比較高,需要清理內存 - TRIM_MEMORY_RUNNING_MODERATE:內存不足(后臺進程超過5個),并且該進程優先級比較高,需要清理內存 以上3個是4.1新增
選擇合適的時機,對View和資源解綁,在合適的時機再恢復
釋放緩存
小心未關閉的Dialog,在Activity和Fragment銷毀時記得先dismiss
匿名內部類和非靜態內部類泄漏,用靜態外部類和弱引用的方式取而代之
-
避免創建大量的臨時對象而造成內存抖動,考慮復用
- 高頻執行函數中避免創建大量臨時對象,如View的onDraw和onTouch等 - StingBuilder、StringBuffer代替String
-
WebView造成內存泄漏
Android系統和各家的ROM本身存在的問題造成的,最好的處理方式是將承載WebView的界面獨立到其他進程,在合適的時機選擇殺死WebView進程
-
自身內存監控(來自騰訊開發者提供的一種方案)
- Runtime.getRuntime().getMaxMemory() Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() - 定期檢查上邊的比例值,達到一定峰值時調用此API觸發內存釋放 WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);
-
更多
- 注冊和反注冊(BroadcastReceiver、Observer) - 關閉資源(Cursor、IO流) - 使用優化過的數據結構SparseXXXX - 考慮使用Parcelable取代Serializable - 建立緩存池,如ListView復用View的思路 - 開辟多進程(當然,也不是越多越好) - 借助lint來規避一些常見的編碼問題
二、動態更新
隨著App日益龐大以及越來越復雜的邏輯,不僅需要團隊多人的協作開發還有方法數越界等問題,因此,插件化以此衍生出而來,它的好處有以下幾個方面:
- 插件模塊動態升級
- 改進大型App的架構
- 實現多團隊協作開發,各自發布
因為對這一塊接觸了解的并不是很多,在此不作更詳細的記錄。
三、hotfix
所謂熱補丁(hotfix),它可以讓應用在無須重新安裝下能夠自動更新。對于熱補丁的應用,可能我們會存在幾個等級的需求:
- Bugfix : 簡單的bugfix,解決一些空指針之類的問題
- UI : 改變UI,支持資源的變更
- features : 功能的發布,甚至一定情況下面代替升級
對于現在市面上的熱修復方案,大致可以分為兩個流派,第一個是native派,它肯定是使用native方式實現,第二個是Java派,如kkfix可以hook系統的方法,robust是AOP的方式。
那么問題又來了,熱補丁技術哪家強?其實各個方案都有自己的優缺點,世上沒有最好的方案,只有只適合自己的方案
- AndFix方案
這套方案主要是在Native層替換方法的實現,關鍵挑戰是兼容性問題,底層的代碼每個版本都有改動,可能有些廠商也會去改這塊代碼,所以這套方案的使用場景存在較大的限制
優點:1、立刻生效 缺點:1、兼容性
2、性能影響小 2、Native定位復雜
3、補丁相對較小 3、應用場景首先
4、支持大部分的加固場景
總結: 適合Bugfix場景
- QZone方案
主要是利用了android classloader查找類的順序,當出現重復類的時候,優先使用補丁中修改過的類。這套方案有個問題是dalvik在加載修改過的類的時候會出現一個crash,當時的解決方案是用插樁的方式。這套方案的特點是非常簡單,而且兼容性不錯。
優點:1、兼容性較好 缺點:1、性能損耗
2、應用場景廣 2、補丁可能會過大
3、簡單,成功率高 3、無法立刻生效
4、支持大部分的加固場景
總結: 可實現Features發布
- Tinker方案
在審視大量方案后,發現gradle的instant run 和 facebook buck exopacakage方案都使用了全量合成的做法,于是想到了推送差異Dex的方式。
優點:1、兼容性較好 缺點:1、Dalvik Rom體積較大
2、應用場景廣 2、單獨的合成過程
3、補丁較小 3、無法立刻生效
4、性能損耗小 4、不支持加固(通過回退Qzone方案)
總結: 可實現Features發布
- Robust方案
關鍵挑戰:super方法調用問題;Proguard內聯
優點:1、立即生效 缺點:1、應用場景受限
2、性能影響小 2、修改源碼
3、補丁較小 3、安裝包變大(5%左右)
4、支持大部分加固場景
總結: 適合Bugfix場景
各種方案的對比:
四、App安全
代碼安全
- 完整性校驗:檢查classex.dex、apk的完整性,最好將哈希值存儲到服務器
- 防逆向分析:代碼混淆、加殼保護
- 防進程注入:防止ptrace進行so注入,并hook任意函數
傳輸安全
- HTTPS=HTTP+SSL/TLS
- 蘋果已宣布從2017年起所有IOS應用將強制使用HTTPS
- HTTP/2.0也只支持HTTPS
- 防止中間人攻擊:SSL Pinning
- 防止降級攻擊
關于URL簽名的一般處理方式:
- 將所有參數按參數名進行升序排序;
- 將排序后的參數名和值拼接成字符串stringParams,格式:key1value1key2value2...;
- 在上一步的字符串前面拼接上請求URL的Endpoint,字符串后面拼接上AppSecret,即stringUri+StringParams+AppSecret;
- 使用AppSecret為密鑰,對上一步的結果字符串使用HMAC算法計算MAC值,這個MAC值就是簽名。
URL示例:
http://api.domain.com/users/123?appKey=qwer&token=asfe×tamp=23456789
AppSecret:nmefj
參數排序:appKey、timestamp、token
參數拼接:appKeyqwertimestamp23456789tokenasfe
URL拼接:users/123appKeyqwertimestamp23456789tokenasfenmefj
計算MAC值:reRwV
最終URL:http://api.domain.com/users/123?appKey=qwer&token=asfe×tamp=23456789&sign=reRwV
存儲安全
常用的存儲方式:
- SharedPreferences
- Internal Storage
- External Storage
- SQlite Databases
- keystore/keychain
- 硬編碼
- so文件
存儲的安全標準:
- 敏感數據不能存在外部存儲器上,無論是否加密
- 私有目錄數據正確設置權限
- 銘感數據不能以明文存儲在私有目錄
敏感數據安全
密碼:
- 不要在客戶端本地保存
- 網絡傳輸加密:MD5、加鹽MD5、HMAC、AES、RSA
- 數據庫保存:加鹽MD5,鹽值和MD5分開保存
密鑰:
- 保存到SharedPreferences
- 硬編碼到代碼中
- 加密保存到配置文件中
- NDK開發,放在so文件中,加解密操作都在so文件,并添加簽名認證
TOKEN
- 用戶登錄成功后返回Token,一般有兩個:accessToken和和refreshToken
- Token需要設置有效期,accessToken的有效期不能設置太長,最好不超過一周,refreshToken可以長一點
- accessToken過期后,通過refreshToken更新accessToken
- refreshToken過期后,則需要用戶重新登錄
五、一些感受
額,做技術的人好像蠻容易禿頂的...