GITHUB
https://blog.51cto.com/6342127/2307514
說明
這篇文章是將好久以來看過的文章,包括本身寫的一些測試代碼的總結.屬於筆記的性質,沒有面面俱到,一些本身相對熟悉的點可能會略過.<br>
最開始看到的性能優化的文章,就是胡凱的優化典範系列,後來又陸續看過一些人寫的,我的以爲anly_jun和胡凱的質量最好.<br>
文章大的框架也是先把優化典範過一遍,記錄我的認爲重要的點,而後是anly_jun的系列,將以前未覆蓋的補充進去,也包括HenCoder的一些課程相關內容.<br>
固然除了上面幾位,還有不少其餘大神的文章,時間久了也記不太清,在此一併謝過.javascript
筆記內容引用來源
1.Android性能優化之渲染篇
1.VSYNC
- 幀率:GPU在1秒內繪製操做的幀數.如60fps.
- 咱們一般都會提到60fps與16ms,這是由於人眼與大腦之間的協做沒法感知超過60fps的畫面更新.
- 開發app的性能目標就是保持60fps,這意味着每一幀只有16ms=1000/60的時間來處理全部的任務
- 刷新率:屏幕在1秒內刷新屏幕的次數.如60Hz,每16ms刷新1次屏幕.
- GPU獲取圖形數據進行渲染,而後屏幕將渲染後的內容展現在屏幕上.
- 大多數手機屏幕的刷新率是60Hz,若是GPU渲染1幀的時間低於1000/60=16ms,那麼在屏幕刷新時候都有最新幀可顯示.若是GPU渲染某1幀 f 的時間超過16ms,在屏幕刷新時候,f並無被GPU渲染完成則沒法展現,屏幕只能繼續展現f的上1幀的內容.這就是掉幀,形成了UI界面的卡頓.
<br>
下面展現了幀率正常和幀率低於刷新率(掉幀)的情形
2.GPU渲染:GPU渲染依賴2個組件:CPU和GPU
- CPU負責Measure,Layout,Record,Execute操做.
- GPU負責Rasterization(柵格化)操做.
- Resterization柵格化是繪製那些Button,Shape,Path,String,Bitmap等組件最基礎的操做.它把組件拆分到不一樣的像素上進行顯示.這是一個很費時的操做.
- CPU負責把UI組件計算成Polygons(多邊形),Texture(紋理),而後交給GPU進行柵格化渲染.
- 爲了App流暢,咱們須要確保在16ms內完成全部CPU和GPU的工做.
3.過分繪製
Overdraw過分繪製是指屏幕上的某個像素在同一幀的時間內被繪製了屢次.過分繪製會大量浪費CPU及GPU資源/佔用CPU和GPU的處理時間php
- 過分繪製的緣由
- UI佈局存在大量重疊
- 非必須的背景重疊.
- 如Activity有背景,Layout又有背景,子View又有背景.僅僅移除非必要背景就能夠顯著提高性能.
- 子View在onDraw中存在重疊部分繪製的狀況,好比Bitmap重疊繪製
4.如何提高渲染性能
- 移除XML佈局文件中非必要的Background
- 保持佈局扁平化,儘可能避免佈局嵌套
- 在任什麼時候候都避免調用requestLayout(),調用requestLayout會致使該layout的全部父節點都發生從新layout的操做
-
在自定義View的onDraw中避免過分繪製.
<br>代碼實例:html<br>效果圖:java
2.Android性能優化以內存篇
1.Android虛擬機的 分代堆內存/Generational Heap Memory模型
- 和JVM不一樣:Android的堆內存多了1個永久代/Permanent Generation.
- 和JVM相似:
- 新建立的對象存儲在新生代/Young Generation
- GC所佔用的時間和它是哪個Generation有關,Young Generation的每次GC操做時間是最短的,Old Generation其次,Permanent Generation最長
- 不管哪一代,觸發GC後,全部非垃圾回收線程暫停,GC結束後全部線程恢復執行
- 若是短期內進行過多GC,屢次暫停線程進行垃圾回收的累積時間就會增大.佔用過多的幀間隔時間/16ms,致使CPU和GPU用於計算渲染的時間不足,致使卡頓/掉幀.
2.內存泄漏和內存溢出
內存泄漏就是無用對象佔據的內存空間沒有及時釋放,致使內存空間浪費的狀況.memory leak.
<br>
內存溢出是App爲1個對象申請內存空間,內存空間不足的狀況.out of memory.
<br>
內存泄漏數量足夠大,就會引發內存溢出.或者說內存泄漏是內存溢出的緣由之一.python
3.Android性能優化典範-第2季
1.提高動畫性能
- Bitmap的縮放,旋轉,裁剪比較耗性能.例如在一個圓形的鐘表圖上,咱們把時鐘的指針摳出來當作單獨的圖片進行旋轉會比旋轉一張完整的圓形圖性能好.
- 儘可能減小每次重繪的元素能夠極大提高性能.能夠把複雜的View拆分會更小的View進行組合,在須要刷新界面時候僅對指定View進行重繪.
- 假如鐘錶界面上有不少組件,能夠把這些組件作拆分,背景圖片單獨拎出來設置爲一個獨立的View,經過setLayerType()方法使得這個View強制用Hardware來進行渲染.至於界面上哪些元素須要作拆分,他們各自的更新頻率是多少,須要有針對性的單獨討論
2.對象池
- 假如鐘錶界面上有不少組件,能夠把這些組件作拆分,背景圖片單獨拎出來設置爲一個獨立的View,經過setLayerType()方法使得這個View強制用Hardware來進行渲染.至於界面上哪些元素須要作拆分,他們各自的更新頻率是多少,須要有針對性的單獨討論
- 短期內大量對象被建立而後很快被銷燬,會屢次觸發Android虛擬機在Young generation進行GC,使用AS查看內存曲線,會看到內存曲線劇烈起伏,稱爲"內存抖動".
- GC會暫停其餘線程,短期屢次GC/內存抖動會引發CPU和GPU在16ms內沒法完成當前幀的渲染,引發界面卡頓.
- 避免內存抖動,可使用對象池
-
實例android
3.for index,for simple,iterator三種遍歷性能比較
- 不要用for index去遍歷鏈表,由於LinkedList在get任何一個位置的數據的時候,都會把前面的數據走一遍.應該使用Iterator去遍歷
- get(0),直接拿到0位的Node0的地址,拿到Node0裏面的數據
- get(1),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,拿到Node1裏面的數據
- get(2),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,從1位的Node1中找到下一個2位的Node2的地址,找到Node2,拿到Node2裏面的數據
- Vector和ArrayList,使用for index遍歷效率較高
4.Merge:經過Merge減小1個View層級
- 能夠將merge當作1個ViewGroup v,若是v的類型和v的父控件的類型一致,那麼v其實不必存在,由於白白增長了佈局的深度.因此merge使用時必須保證merge中子控件所應該在的ViewGroup類型和merge所在的父控件類型一致.
- Merge的使用場景有2個:
- Activity的佈局文件的根佈局是FrameLayout,則將FrameLayout替換爲merge
- 由於setContentView本質就是將佈局文件inflate後加載到了id爲android.id.content的FrameLayout上.
- merge做爲根佈局的佈局文件經過include標籤被引入其餘佈局文件中.這時候include所在的父控件,必須和merge所在的佈局文件"本來根佈局"一致.
- Activity的佈局文件的根佈局是FrameLayout,則將FrameLayout替換爲merge
-
代碼示例<br>
merge做爲根佈局的佈局文件,用於Activity的setContentView:nginx<br>
merge做爲根佈局的佈局文件,被include標籤引入其餘佈局文件中:git
5.使用.9.png做爲背景
- 典型場景是1個ImageView須要添加1個背景圖做爲邊框.這樣邊框所在矩形的中間部分和實際顯示的圖片就好重疊發生Overdraw.
- 能夠將背景圖製做成.9.png.和前景圖重疊部分設置爲透明.Android的2D渲染器會優化.9.png的透明區域.
6.減小透明區域對性能的影響
- 不透明的View,顯示它只須要渲染一次;若是View設置了alpha值,會至少須要渲染兩次,性能很差
- 設置透明度setAlpha的時候,會把當前view繪製到offscreen buffer中,而後再顯示出來.offscreen buffer是 一個臨時緩衝區,把View放進來並作透明度的轉化,而後顯示到屏幕上,這個過程性能差,因此應該儘可能避免這個過程
- 如何避免使用offscreen buffer
- 對於不存在過分繪製的View,如沒有背景的TextView,就能夠直接設置文字顏色;ImageView設置圖片透明度setImageAlpha;自定義View設置繪製時的paint的透明度
- 若是是自定義View,肯定不存在過分繪製,能夠重寫hasOverlappingRendering返回false便可.這樣設置alpha時android會自動優化,避免使用offscreen buffer.
- 若是不是1,2兩種狀況,要設置View的透明度,則須要讓GPU來渲染指定View,而後再設置透明度.
4.Android性能優化典範-第3季
1.避免使用枚舉,用註解進行替代
- 枚舉的問題
- 每一個枚舉值都是1個對象,相比較Integer和String常量,枚舉的內存開銷至少是其2倍.
- 過多枚舉會增長dex大小及其中的方法數量,增長App佔用的空間及引起65536概率
-
如何替代枚舉:使用註解github
- android.support.annotation中的@IntDef,@StringDef來包裝Integer和String常量.
- 3個步驟
- 首先定義常量
- 而後自定義註解,設置取值範圍就是剛剛定義的常量,並設置自定義註解的保留範圍爲源碼時/SOURCE
- 位指定的屬性及方法添加自定義註解.
-
代碼實例算法
5.Android內存優化之OOM
如何避免OOM:
- 減少對象的內存佔用
- 內存對象複用防止重建
- 避免內存泄漏
- 內存使用策略優化
1.減少對象的內存佔用
- 避免使用枚舉,用註解替代
- 減少建立的Bitmap的內存,使用合適的縮放比例及解碼格式
- inSampleSize:縮放比例
- decode format:解碼格式
- 如今不少圖片資源的URL均可以添加圖片尺寸做爲參數.在經過網絡獲取圖片時選擇合適的尺寸,減少網絡流量消耗,並減少生成的Bitmap的大小.
2.內存對象的重複利用
- 對象池技術:減小頻繁建立和銷燬對象帶來的成本,實現對象的緩存和複用
- 儘可能使用Android系統內置資源,可下降APK大小,在必定程度下降內存開銷
- ConvertView的複用
- LRU的機制實現Bitmap的緩存(圖片加載框架的必備機制)
- 在for循環中,用StringBuilder代替String實現字符串拼接
3.避免內存泄漏
- 在App中使用leakcanary檢測內存泄漏:leakcanary
-
Activity的內存泄漏
-
Handler引發Activity內存泄漏
- 緣由:Handler做爲Activity的1個非靜態內部類實例,持有Activity實例的引用.若Activity退出後Handler依然有待接收的Message,這時候發生GC,Message-Handler-Activity的引用鏈致使Activity沒法被回收.
-
2種解決方法
- 在onDestroy調用Handler.removeCallbacksAndMessages(null)移除該Handler關聯的全部Message及Runnable.再發生GC,Message已經不存在,就能夠順利的回收Handler及Activity
-
自定義靜態內部類繼承Handler,靜態內部類實例不持有外部Activity的引用.在自定義Handler中定義外部Activity的弱引用,只有弱引用關聯的外部Activity實例未被回收的狀況下才繼續執行handleMessage.自定義Handler持有外部Activity的弱引用,發生GC時不耽誤Activity被回收.
- 在避免內存泄漏的前提下,若是要求Activity退出就不執行後續動做,用方法1.若是要求後續動做在GC發生前繼續執行,使用方法2
-
- Context:儘可能使用Application Context而不是Activity Context,避免不經意的內存泄漏
- 資源對象要及時關閉
4.內存使用策略優化
- 圖片選擇合適的文件夾進行存放
- hdpi/xhdpi/xxhdpi等等不一樣dpi的文件夾下的圖片在不一樣的設備上會通過scale的處理。例如咱們只在hdpi的目錄下放置了一張100100的圖片,那麼根據換算關係,xxhdpi的手機去引用那張圖片就會被拉伸到200200。須要注意到在這種狀況下,內存佔用是會顯著提升的。對於不但願被拉伸的圖片,須要放到assets或者nodpi的目錄下
- 謹慎使用依賴注入框架.依賴注入框架會掃描代碼,須要大量的內存空間映射代碼.
- 混淆能夠減小沒必要要的代碼,類,方法等.下降映射代碼所需的內存空間
- onLowMemory()與onTrimMemory():沒想到應該怎麼用
- onLowMemory
- 當全部的background應用都被kill掉的時候,forground應用會收到onLowMemory()的回調.在這種狀況下,須要儘快釋放當前應用的非必須的內存資源,從而確保系統可以繼續穩定運行
- onTrimMemory(int level)
- 當系統內存達到某些條件的時候,全部正在運行的應用都會收到這個回調,同時在這個回調裏面會傳遞如下的參數,表明不一樣的內存使用狀況,收到onTrimMemory()回調的時候,須要根據傳遞的參數類型進行判斷,合理的選擇釋放自身的一些內存佔用,一方面能夠提升系統的總體運行流暢度,另外也能夠避免本身被系統判斷爲優先須要殺掉的應用
- onLowMemory
6.Android開發最佳實踐
1.注意對隱式Intent的運行時檢查保護
- 相似打開相機等隱式Intent,不必定可以在全部的Android設備上都正常運行.
- 例如系統相機應用被關閉或者不存在相機應用,或者某些權限被關閉均可能致使拋出ActivityNotFoundException的異常.
- 預防這個問題的最佳解決方案是在發出這個隱式Intent以前調用resolveActivity作檢查
- 代碼實例
2.Android 6.0的權限
3.MD新控件的使用:Toolbar替代ActionBar,AppBarLayout,Navigation Drawer, DrawerLayout, NavigationView等
7.Android性能優化典範-第4季
1.網絡數據的緩存.okHttp,Picasso都支持網絡緩存
okHttp Picasso
<br>
MVP架構實現的Github客戶端(4-加入網絡緩存)
2.代碼混淆
2.1.AS中生成keystore.jks應用於APK打包
<br>
-
1:生成keystore.jks
<br> - 2:查看.jks文件的SHA1安全碼
<br>
在AS的Terminal中輸入:
<br>
keytool -list -v -keystore C:\Users\Administrator\Desktop\key.jks
<br>
keytool -list -v -keystore .jks文件詳細路徑
<br>
回車後,輸入密鑰庫口令/就是.jks的密碼,輸入過程不可見,輸入完畢回車便可!
<br>
2.2.proguard-rules關鍵字及部分通配符含義
<table align="center">
<tr>
<td>關鍵字</td>
<td>描述</td>
</tr>
<tr>
<td>keep</td>
<td>保留類和類中的成員,防止它們被混淆或移除</td>
</tr>
<tr>
<td>keepnames</td>
<td>保留類和類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除</td>
</tr>
<tr>
<td>keepclasseswithmembers</td>
<td>保留類和類中的成員,防止它們被混淆或移除,前提是指名的類中的成員必須存在,若是不存在則仍是會混淆</td>
</tr>
<tr>
<td>keepclasseswithmembernames</td>
<td>保留類和類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除,前提是指名的類中的成員必須存在,若是不存在則仍是會混淆</td>
</tr>
<tr>
<td>keepclassmembers</td>
<td>只保留類中的成員,防止它們被混淆或移除</td>
</tr>
<tr>
<td>keepclassmembernames</td>
<td>只保留類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除</td>
</tr>
</table>
<br>
<table align="center">
<tr>
<td>通配符</td>
<td>描述</td>
</tr>
<tr>
<td>< field ></td>
<td>匹配類中的全部字段</td>
</tr>
<tr>
<td>< method ></td>
<td>匹配類中的全部方法</td>
</tr>
<tr>
<td>< init ></td>
<td>匹配類中的全部構造函數</td>
</tr>
<tr>
<td></td>
<td>
1.和字符串聯合使用,表明任意長度的不包含包名分隔符(.)的字符串:<br>
a.b.c.MainActivity: a...MainActivity能夠匹配;a.就匹配不上;<br>
2.*單獨使用,就能夠匹配全部東西
</td>
</tr>
<tr>
<td></td>
<td>
匹配任意長度字符串,包含包名分隔符(.)<br>
a.b.能夠匹配a.b包下全部內容,包括子包
</td>
</tr>
<tr>
<td></td>
<td>
匹配任意參數類型.好比:<br>
void set(**)就能匹配任意傳入的參數類型;<br>
get*()就能匹配任意返回值的類型
</td>
</tr>
<tr>
<td>…</td>
<td>
匹配任意長度的任意類型參數.好比:<br>
void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)
</td>
</tr>
</table>
<br>
- keep 完整類名{*;}, 能夠對指定類進行徹底保留,不混淆類名,變量名,方法名.<br>
- 在App中,咱們會定義不少實體bean.每每涉及到bean實例和json字符串間互相轉換.部分json庫會經過反射調用bean的set和get方法.於是實體bean的set,get方法不能被混淆,或者說咱們本身寫的方法,若是會被第三方庫或其餘地方經過反射調用,則指定方法要keep避免混淆.
- 咱們本身寫的使用了反射功能的類,必須keep
- 若是咱們要保留繼承了指定類的子類,或者實現了指定接口的類
2.3.proguard-rules.pro通用模板
2.4.混淆jar包
<br>
郭霖大神博客有介紹,本身沒試過
2.5.幾條實用的Proguard rules
<br>
在上面提供的通用模板上繼續添加下面幾行:
- repackageclasses:除了keep的類,會把咱們本身寫的全部類以及所使用到的各類第三方庫代碼通通移動到咱們指定的單個包下.
- 好比一些比較敏感的被keep的類在包a.b.min下,咱們可使用 -repackageclasses a.b.min,這樣就有成千上萬的被混淆的類和未被混淆的敏感的類在a.b.min下面,正常人根本就找不到關鍵類.尤爲是keep的類也只是保留關鍵方法,名字也被混淆過.
- -obfuscationdictionary,-classobfuscationdictionary和-packageobfuscationdictionary分別指定變量/方法名,類名,包名混淆後的字符串集.
- 默認咱們的代碼命名會被混淆成字母組合,使用這些配置能夠用亂碼或中文內容進行命名.中文命名能夠破壞部分反編譯軟件的正常工做,亂碼則極大加大了查看代碼的難度.
- dict.txt:須要放到和app模塊的proguard-rules.pro同級目錄.dict.txt具體內容能夠本身寫,參考開源項目:一種生成閱讀極其困難的proguard字典的算法
- -assumenosideeffects class android.util.Log是在編譯成 APK 以前把日誌代碼所有刪掉.
- Androidstudio 混淆去掉日誌 assumenosideeffects 不起做用
- 由於默認狀況下,使用的是proguard-android.txt的混淆規則.proguard-android.txt中包含-dontoptimize.-dontoptimize致使日誌語句不會被優化掉.因此咱們提供的模板不包含-dontoptimize這一句.
- Androidstudio 混淆去掉日誌 assumenosideeffects 不起做用
2.6.字符串硬編碼
- 對於反編譯者來講,最簡單的入手點就是字符串搜索.硬編碼留在代碼裏的字符串值都會在反編譯過程當中被原樣恢復,不要使用硬編碼.
- 若是必定要使用硬編碼
- 新建1個存儲硬編碼的常量類,靜態存放字符串常量,即便找到了常量類,反編譯者很難搜索到哪裏用了這些字符串.
- 常量類中的靜態常量字符串,用名稱做爲真正內容,而值用難以理解的編碼表示.
2.7.res資源混淆及多渠道打包
<br>
簡單講,使用騰訊的2個gradle插件來實現res資源混淆及多渠道打包.
<br>
res資源混淆:AndResGuard
<br>
多渠道打包:VasDolly
<br>
多渠道打包原理+VasDolly和其餘多渠道打包方案對比
<br><br>
具體流程:
<br>
AndResGuard使用了chaychan的方法,單首創建gradle文件
<br>
-
項目根目錄下build.gradle中,添加插件的依賴,具體以下
-
在app目錄下單首創建gradle文件and_res_guard.gradle.內容以下
-
模塊app下的build.gradle文件添加依賴,具體以下
-
首先使用AndResGuard實現資源混淆,再使用VasDolly實現多渠道打包
-
在Gradle界面中,找到app模塊下andresguard的task.
- 若是想打debug包,則執行resguardDebug指令;
- 若是想打release包,則執行resguardRelease指令.
- 此處咱們雙擊執行resguardRelease指令,在app目錄下的/build/output/apk/release/AndResGuard_{apk_name}/ 文件夾中找到混淆後的Apk,其中app-release_aligned_signed.apk爲進行混淆並簽名過的apk.
- 咱們查看app-release_aligned_signed.apk,res文件夾改名爲r,裏面的目錄名稱以及xml文件已經被混淆.
- 將app-release_aligned_signed.apk放到app模塊下,在Gradle界面中,找到app模塊下channel的task,執行reBuildChannel指令.
- 雙擊執行reBuildChannel指令,幾秒鐘就生成了20個經過app-release_aligned_signed.apk的多渠道apk.
- 經過helper類庫中的ChannelReaderUtil類讀取渠道信息
- 雙擊執行reBuildChannel指令,幾秒鐘就生成了20個經過app-release_aligned_signed.apk的多渠道apk.
-
3.APK瘦身
4.更高效的數據序列化:只是看看從沒用過,Protocal Buffers,Nano-Proto-Buffers,FlatBuffers
5.數據呈現的順序以及結構會對序列化以後的空間產生不小的影響
-
gzip
- gzip概念:HTTP協議上的GZIP編碼是一種用來改進WEB應用程序性能的技術
- 通常對純文本內容可壓縮到原大小的40%
- 減小文件大小有兩個明顯的好處,一是能夠減小存儲空間,二是經過網絡傳輸文件時,能夠減小傳輸的時間
-
okHttp對gzip的支持
- okHttp支持gzip自動解壓縮,不須要設置Accept-Encoding爲gzip
- 開發者手動設置Accept-Encoding,okHttp不負責解壓縮
- 開發者沒有設置Accept-Encoding時,則自動添加Accept-Encoding: gzip,自動添加的request,response支持自動解壓
- 自動解壓時移除Content-Length,因此上層Java代碼想要contentLength時爲-1
- 自動解壓時移除 Content-Encoding
- 自動解壓時,若是是分塊傳輸編碼,Transfer-Encoding: chunked不受影響
-
咱們在向服務器提交大量數據的時候,但願對post的數據進行gzip壓縮,須要使用自定義攔截器
- okHttp支持gzip自動解壓縮,不須要設置Accept-Encoding爲gzip
- gzip概念:HTTP協議上的GZIP編碼是一種用來改進WEB應用程序性能的技術
-
改變數據結構,就是將原始集合按照集合中對象的屬性進行拆分,變成多個屬性集合的形式.
- 改變數據結構後JSON字符串長度有明顯下降
- 使用GZIP對JSON字符串進行壓縮,在原始集合中元素數據重複率逐漸變大的狀況下,GZIP壓縮後的原始JSON字符串長度/GZIP壓縮後的改變數據結構的JSON字符串會明顯大於1.
-
代碼及測試結果以下,結果僅供參考(ZipUtils是網上蕩的,且沒有使用網上用過的Base64Decoder,Base64Encoder)
8.Android性能優化典範-第5季
多線程大部份內容源自凱哥的課程,我的以爲比優化典範寫得清晰得多
1.線程
- 線程就是代碼線性執行,執行完畢就結束的一條線.UI線程不會結束是由於其初始化完畢後會執行死循環,因此永遠不會執行完畢.
- 如何簡單建立新線程:
- 兩種方式建立新線程性能無差異,使用Runnable實例適用於但願Runnable複用的情形
- 經常使用的建立線程池2種方式
- Executors.newCachedThreadPool():通常狀況下使用newCachedThreadPool便可.
- Executors.newFixedThreadPool(int number):短時批量處理/好比要並行處理多張圖片,能夠直接建立包含圖片精確數量的線程的線程池並行處理.
- 《阿里巴巴Java開發手冊》規定:
- 線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式.這樣
的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險 - 看Android中Executors源碼.Executors.newCachedThreadPool/newScheduledThreadPool容許的建立線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,從而致使 OOM.而newFixedThreadPool,newSingleThreadExecutor不會存在這種風險.
- 線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式.這樣
- [如何正確建立ThreadPoolExecutor:有點麻煩,晚點詳述]()
- ExecutorService的shutdown和shutdownNow
- shutdown:在調用shutdown以前ExecutorService中已經啓動的線程,在調用shutdown後,線程若是執行未結束會繼續執行完畢並結束,但不會再啓動新的線程執行新任務.
- shutdownNow:首先中止啓動新的線程執行新任務;並嘗試結束全部正在執行的線程,正在執行的線程可能被終止也可能會繼續執行完成.
-
如何正確建立ThreadPoolExecutor<br>
3.1:ThreadPoolExecutor構造參數- int corePoolSize:該線程池中核心線程最大數量.默認狀況下,即便核心線程處於空閒狀態也不會被銷燬.除非經過allowCoreThreadTimeOut(true),則核心線程在空閒時間達到keepAliveTime時會被銷燬<br>
- int maximumPoolSize:該線程池中線程最大數量<br>
- long keepAliveTime:該線程池中非核心線程被銷燬前最大空閒時間,時間單位由unit決定.默認狀況下核心線程即便空閒也不會被銷燬,在調用allowCoreThreadTimeOut(true)後,該銷燬時間設置也適用於核心線程<br>
- TimeUnit unit:keepAliveTime/被銷燬前最大空閒時間的單位<br>
- BlockingQueue<Runnable> workQueue:該線程池中的任務隊列.維護着等待被執行的Runnable對象.BlockingQueue有幾種類型,下面會詳述<br>
- ThreadFactory threadFactory:建立新線程的工廠.通常狀況使用Executors.defaultThreadFactory()便可.固然也能夠自定義.<br>
- RejectedExecutionHandler handler:拒絕策略.當須要建立的線程數量達到maximumPoolSize而且等待執行的Runnable數量超過了任務隊列的容量,該如何處理.<br>
3.2:當1個任務被放進線程池,ThreadPoolExecutor具體執行策略以下:<br>
- 若是線程數量沒有達到corePoolSize,有核心線程空閒則核心線程直接執行,沒有空閒則直接新建核心線程執行任務;
- 若是線程數量已經達到corePoolSize,且核心線程無空閒,則將任務添加到等待隊列;
- 若是等待隊列已滿,則新建非核心線程執行該任務;
- 若是等待隊列已滿且總線程數量已達到maximumPoolSize,則會交由RejectedExecutionHandler handler處理.<br>
3.3:阻塞隊列/BlockingQueue<Runnable> workQueue<br>
- BlockingQueue有以下幾種:SynchronousQueue/LinkedBlockingQueue/LinkedTransferQueue/ArrayBlockingQueue/PriorityBlockingQueue/DelayQueue.
- SynchronousQueue:SynchronousQueue的容量是0,不存儲任何Runnable實例.新任務到來會直接嘗試交給線程執行,如全部線程都在忙就建立新線程執行該任務.
- LinkedBlockingQueue:默認狀況下沒有容量限制的隊列.
- ArrayBlockingQueue:一個有容量限制的隊列.
- DelayQueue:一個沒有容量限制的隊列.隊列中的元素必須實現了Delayed接口.元素在隊列中的排序按照當前時間的延遲值,延遲最小/最先要被執行的任務排在隊列頭部,依次排序.延遲時間到達後執行指定任務.
- PriorityBlockingQueue:一個沒有容量限制的隊列.隊列中元素必須實現了Comparable接口.隊列中元素排序依賴元素的天然排序/compareTo的比較結果.
- 各類BlockingQueue的問題<br>
1.SynchronousQueue缺點:由於不具有存儲元素的能力,於是當任務很頻繁時候,爲了防止線程數量超標,咱們每每設置maximumPoolSize是Integer.MAX_VALUE,建立過多線程會致使OOM.《阿里巴巴Java開發手冊》中強調不能使用Executors直接建立線程池,就是對應Android源碼中newCachedThreadPool和newScheduledThreadPool,本質上就是建立了maximumPoolSize爲Integer.MAX_VALUE的ThreadPoolExecutor.<br>
2.LinkedBlockingQueue由於沒有容量限制,因此咱們使用LinkedBlockingQueue建立ThreadPoolExecutor,設置maximumPoolSize是無心義的,若是線程數量已經達到corePoolSize,且核心線程都在忙,那麼新來的任務會一直被添加到隊列中.只要核心線程無空閒則一直得不到被執行機會.<br>
3.DelayQueue和PriorityBlockingQueue也具備一樣的問題.因此corePoolSize必須設置合理,不然會致使超出核心線程數量的任務一直得不到機會被執行.這兩類隊列分別適用於定時及優先級明確的任務.<br>
3.4:RejectedExecutionHandler handler/拒絕策略有4種<br>
1.hreadPoolExecutor.AbortPolicy:丟棄任務,並拋出RejectedExecutionException異常.ThreadPoolExecutor默認就是使用AbortPolicy.<br>
2.ThreadPoolExecutor.DiscardPolicy:丟棄任務,但不會拋出異常.<br>
3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄排在隊列頭部的任務,不拋出異常,並嘗試從新執行任務.<br>
4.ThreadPoolExecutor.CallerRunsPolicy:丟棄任務,但不拋出異常,並將該任務交給調用此ThreadPoolExecutor的線程執行. - synchronized 的本質
- 保證synchronized方法或者代碼塊內部資源/數據的互斥訪問
- 即同一時間,由同一個Monitor監視的代碼,最多隻有1個線程在訪問
- 保證線程之間對監視資源的數據同步.
- 任何線程在獲取Monitor後,會第一時間將共享內存中的數據複製到本身的緩存中;
- 任何線程在釋放Monitor後,會第一時間將緩存中的數據複製到共享內存中
- 保證synchronized方法或者代碼塊內部資源/數據的互斥訪問
-
volatile
- 保證被volatile修飾的成員的操做具備原子性和同步性.至關於簡化版的synchronized
- 原子性就是線程間互斥訪問
- 同步性就是線程之間對監視資源的數據同步
-
volatile生效範圍:基本類型的直接複製賦值 + 引用類型的直接賦值
- volatile型變量自增操做的隱患
- volatile類型變量每次在讀取的時候,會越過線程的工做內存,直接從主存中讀取,也就不會產生髒讀
- ++自增操做,在Java對應的彙編指令有三條
- 從主存讀取變量值到cpu寄存器
- 寄存器裏的值+1
- 寄存器的值寫回主存
- 若是N個線程同時執行到了第1步,那麼最終變量會損失(N-1).第二步第三步只有一個線程是執行成功.
- 對變量的寫操做不依賴於當前值,才能用volatile修飾.
- volatile型變量自增操做的隱患
- 保證被volatile修飾的成員的操做具備原子性和同步性.至關於簡化版的synchronized
- 針對num++這類複合類的操做,可使用java併發包中的原子操做類原子操做類:AtomicInteger AtomicBoolean等來保證其原子性.
2.線程間交互
-
一個線程終結另外一個線程
-
Thread.stop不要用:
- 由於線程在運行過程當中隨時有可能會被暫停切換到其餘線程,stop的效果至關於切換到其餘線程繼續執行且之後不再會切換回來.咱們執行A.stop的時候,徹底沒法預知A的run方法已經執行了多少,執行百分比徹底不可控.
private static void t2(){
Thread t = new Thread(){br/>@Override
public void run() {
for(int i=0;i<1000000;i++){
System.out.println(""+i);
}
}
};
t.start();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.stop();
}
-
-
Thread.interrupt:僅僅設置當前線程爲被中斷狀態.在運行的線程依然會繼續運行.
- Thread.isInterrupted:獲取當前線程是否被中斷
- Thread.interrupted():若是線程A調用了Thread.interrupted()
- 若是A以前已經被中斷,調用Thread.interrupted()返回false,A已經不是被中斷狀態
- 若是A以前不是被中斷狀態,調用Thread.interrupted()返回true,A變成被中斷狀態.
-
單純調用A.interrupt是無效果的,interrupt須要和isInterrupted聯合使用
- 用於咱們但願線程處於被中斷狀態時結束運行的場景.
- interrupt和stop比較的優勢:stop後,線程直接結束,咱們徹底沒法控制當前執行到哪裏;<br>
interrupt後線程默認會繼續執行,咱們經過isInterrupted來獲取被中斷狀態,只有被中斷且知足咱們指定條件才return,能夠精確控制線程的執行百分比.
......
799999
800000
Process finished with exit code 0 -
InterruptedException:
- 若是線程A在sleep過程當中被其餘線程調用A.interrupt(),會觸發InterruptedException.
- 若是調用A.interrupt()時候,A並不在sleep狀態,後面再調用A.sleep,也會當即拋出InterruptedException.
老子被叫醒了:睡了493ms
Process finished with exit code 0
-
線程等待:wait,notifyAll,notify
- wait,notifyAll,notify是屬於Object的方法.用於線程等待的場景,需用Monitor進行調用
- wait:
- 當1個線程A持有Monitor M.
- 此時調用M.wait,A會釋放M並處於等待狀態.並記錄A在當前代碼執行的位置Position.
- notify:
- 當調用M.notify(),就會喚醒1個由於調用M.wait()而處於等待狀態的線程
- 若是有A,B,C--多個線程都是由於調用M.wait()而處於等待狀態,不必定哪一個會被喚醒並嘗試獲取M
- notifyAll:
- 當調用M.notifyAll(),全部由於調用M.wait()而處於等待狀態的線程都被喚醒,一塊兒競爭嘗試獲取M
- 調用notify/notifyAll被喚醒並獲取到M的線程A,會接着以前的代碼執行位置Position繼續執行下去
線程:Thread-2 嘗試printStr時間:1539247468146
線程:Thread-1 嘗試printStr時間:1539247468944
setStr時間:1539247469944
線程:Thread-1 printStr時間:1539247469944
str:老子設置一下
線程:Thread-2 printStr時間:1539247469944
str:老子設置一下
3.Executor、 AsyncTask、 HandlerThead、 IntentService 如何選擇
- HandlerThead就不要用,HandlerThead設計目的就是爲了主界面死循環刷新界面,無其餘應用場景.
- 能用線程池就用線程池,由於最簡單.
- 涉及後臺線程推送任務到UI線程,可使用Handler或AsyncTask
- Service:就是爲了作後臺任務,不要UI界面,須要持續存活.有複雜的須要長期存活/等待的場景使用Service.
- IntentService:屬於Service.當咱們須要使用Service,且須要後臺代碼執行完畢後該Service自動被銷燬,使用IntentService.
4.AsyncTask的內存泄漏
- GC Roots:由堆外指向堆內的引用,包括:
- Java方法棧幀中的局部變量
- 已加載類的靜態變量
- native代碼的引用
- 運行中的Java線程
- AsyncTask內存泄漏本質:正在運行的線程/AsyncTask 在虛擬機中屬於GC ROOTS,AsyncTask持有外部Activity的引用.被GC ROOTS引用的對象不能被回收.
- 因此AsyncTask和其餘線程工具同樣,只要是使用線程,都有可能發生內存泄漏,都要及時關閉,AsyncTask並不比其餘工具更差.
5.RxJava.
講的太多了這裏推薦1個專題RxJava2.x<br>
下面記錄一下本身不太熟的幾點<br>
- RxJava總體結構:
- 鏈的最上游:生產者Observable
- 鏈的最下游:觀察者Observer
- 鏈的中間多個節點:雙重角色.便是上一節點的觀察者Observer,也是下一節點的生產者Observable.
- Scheduler切換線程的原理:源碼跟蹤下去,實質是經過Excutor實現了線程切換.
6.Android M對Profile GPU Rendering工具的更新
- Swap Buffers:CPU等待GPU處理的時間
- Command Issur:OpenGL渲染Display List所須要的時間
- Sync&Upload:一般表示的是準備當前界面上有待繪製的圖片所耗費的時間,爲了減小該段區域的執行時間,咱們能夠減小屏幕上的圖片數量或者是縮小圖片自己的大小
- Draw:測量繪製Display List的時間
- Measure & Layout:這裏表示的是佈局的onMeasure與onLayout所花費的時間.一旦時間過長,就須要仔細檢查本身的佈局是否是存在嚴重的性能問題
- Animation:表示的是計算執行動畫所須要花費的時間.包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等.一旦這裏的執行時間過長,就須要檢查是否是使用了非官方的動畫工具或者是檢查動畫執行的過程當中是否是觸發了讀寫操做等等
- Input Handling:表示的是系統處理輸入事件所耗費的時間,粗略等於對於的事件處理方法所執行的時間.一旦執行時間過長,意味着在處理用戶的輸入事件的地方執行了複雜的操做
- Misc/Vsync Delay:若是稍加註意,咱們能夠在開發應用的Log日誌裏面看到這樣一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread。這意味着咱們在主線程執行了太多的任務,致使UI渲染跟不上vSync的信號而出現掉幀的狀況
9.Android性能優化典範-第6季
1.啓動閃屏
- 當點擊桌面圖標啓動APP的時候,App會出現短暫的白屏,一直到第一個Activity的頁面的渲染加載完畢
-
爲了消除白屏,咱們能夠爲App入口Activity單獨設置theme.
- 在單獨設置的theme中設置android:background屬性爲App的品牌宣傳圖片背景.
-
在代碼執行到入口Activity的onCreate的時候設置爲程序正常的主題.
AndroidManifest.xmlandroid:icon="@mipmap/ic_launcher"<br "="" rel="nofollow">br/><application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"br/>android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"br/>android:theme="@style/AppTheme">
<activity android:name=".MainActivity"br/>android:theme="@style/ThemeSplash">//爲入口Activity單獨指定theme
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</manifest>public class MainActivity extends AppCompatActivity {br/>@Override
protected void onCreate(Bundle savedInstanceState) {
//在代碼執行到入口Activity時候設置入口Activity爲默認主題
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_all = findViewById(R.id.tv_all);
tv_local = findViewById(R.id.tv_local);
//註冊全局廣播
registerReceiver(globalReceiver,new IntentFilter("global"));
//註冊本地廣播
LocalBroadcastManager.getInstance(this).registerReceiver(localBroadReceiver,new IntentFilter("localBroadCast"));
}
}
- 若是是隻有1張圖片,放在mipmap-nodpi,或mipmap-xxxhdpi下
- 全部的大背景圖片,統一放在mipmap-nodpi目錄,用一套1080P素材能夠解決大部分手機適配問題,不用每一個資源目錄下放一套素材
- 通過試驗,不論ImageView寬高是不是wrap_content,只要圖片所在文件夾和當前設備分辨率不匹配,都會涉及到放大或壓縮,佔用的內存都會相應的變化.尤爲對於大圖,放在低分辨率文件夾下直接OOM.
<br>具體緣由:<br>
郭霖:Android drawable微技巧,你所不知道的drawable的那些細節當咱們使用資源id來去引用一張圖片時,Android會使用一些規則來去幫咱們匹配最適合的圖片。什麼叫最適合的圖片?好比個人手機屏幕密度是xxhdpi,那麼drawable-xxhdpi文件夾下的圖片就是最適合的圖片。所以,當我引用android_logo這張圖時,若是drawable-xxhdpi文件夾下有這張圖就會優先被使用,在這種狀況下,圖片是不會被縮放的。可是,若是drawable-xxhdpi文件夾下沒有這張圖時, 系統就會自動去其它文件夾下找這張圖了,優先會去更高密度的文件夾下找這張圖片,咱們當前的場景就是drawable-xxxhdpi文件夾,而後發現這裏也沒有android_logo這張圖,接下來會嘗試再找更高密度的文件夾,發現沒有更高密度的了,這個時候會去drawable-nodpi文件夾找這張圖,發現也沒有,那麼就會去更低密度的文件夾下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。
整體匹配規則就是這樣,那麼好比說如今終於在drawable-mdpi文件夾下面找到android_logo這張圖了,可是系統會認爲你這張圖是專門爲低密度的設備所設計的,若是直接將這張圖在當前的高密度設備上使用就有可能會出現像素太低的狀況,因而系統自動幫咱們作了這樣一個放大操做。
那麼一樣的道理,若是系統是在drawable-xxxhdpi文件夾下面找到這張圖的話,它會認爲這張圖是爲更高密度的設備所設計的,若是直接將這張圖在當前設備上使用就有可能會出現像素太高的狀況,因而會自動幫咱們作一個縮小的操做
3.儘可能複用已經存在的圖片.<br>
好比一張圖片O已經存在,若是有View的背景就是O旋轉事後的樣子,能夠直接用O建立RotateDrawable.而後將設置給View使用.<br>
注意:RotateDrawable已經重寫了其onLevelChange方法,因此必定要設置level纔會生效<br>
實例:
4.開啓混淆和資源壓縮:在app模塊下的的build.gradle中
5.對於簡單/規則紋理的圖片,使用VectorDrawable來替代多個分辨率圖片.VectorDrawable 有不少注意事項,後面單獨一篇文章總結
10.網絡優化
網絡優化主要有幾個方面:下降網絡請求數量,下降單次請求響應的數據量,在弱網環境下將非必要網絡請求延緩至網絡環境好的時候.
1.下降網絡請求數量:獲取一樣的數據,屢次網絡請求會增長電量消耗,且屢次請求整體上將消耗服務端更多的時間及資源<br>
- 接口Api設計要合理.能夠將多個接口合併,屢次請求顯示1個界面,改造後1個接口便可提供完整數據.
- 根據具體場景實時性需求,在App中加入網絡緩存,在實時性有效區間避免重複請求:主要包括網絡框架和圖片加載框架的緩存.
2.下降單次請求的數據量
- 網絡接口Api在設計時候,去除多餘的請求參數及響應數據.
- 網絡請求及響應數據的傳輸開啓GZIP壓縮,下降傳輸數據量.
- okHttp對gzip的支持前面已記錄
- Protocal Buffers,Nano-Proto-Buffers,FlatBuffers代替GSON執行序列化.
- Protocal Buffers網上有使用的方法,相對GSON有點繁瑣.若是對網絡傳輸量很敏感,能夠考慮使用.其餘幾種方案的文章很少.
- 網絡請求圖片,添加圖片寬高參數,避免下載過大圖片增長流量消耗.
3.弱網環境優化這塊沒有經驗,直接看anly_jun的文章
App優化之網絡優化<br>
文章中提到:用戶點贊操做, 能夠直接給出界面的點同意功的反饋, 使用JobScheduler在網絡狀況較好的時候打包請求.
11.電量優化
12.JobScheduler,AlarmManager和WakeLock
JobScheduler在網絡優化中出現過,WakeLock涉及電量優化,AlarmManager和WakeLock有類似,但側重點不一樣.
- WakeLock:好比一段關鍵邏輯T已經在執行,執行未完成Android系統就進入休眠,會致使T執行中斷.WakeLock目的就在於阻止Android系統進入休眠狀態,保證T得以繼續執行.
- 休眠過程當中自定義的Timer、Handler、Thread、Service等都會暫停
- AlarmManager:Android系統自帶的定時器,能夠將處於休眠狀態的Android系統喚醒
- 保證Android系統在休眠狀態下被及時喚醒,執行 定時/延時/輪詢任務
- JobScheduler:JobScheduler目的在於將當下不緊急的任務延遲到後面更合適的某個時間來執行.咱們能夠控制這些任務在什麼條件下被執行.
- JobScheduler能夠節約Android設備當下網絡,電量,CPU等資源.在指定資源充裕狀況下再執行"不緊要"的任務.
JobScheduler:<br>
Android Jobscheduler使用<br>
Android開發筆記(一百四十三)任務調度JobScheduler<br>
WakeLock:<br>
Android WakeLock詳解<br>
Android PowerManager.WakeLock使用小結<br>
Android的PowerManager和PowerManager.WakeLock用法簡析<br>
AlarmManager和WakeLock使用:<br>
後臺任務 - 保持設備喚醒狀態
13.性能檢測工具
1.Android Studio 3.2以後,Android Device Monitor已經被移除.Android Device Monitor原先包含的工具由新的方案替代.Android Device Monitor
- DDMS:由Android Profiler代替.能夠進行CPU,內存,網絡分析.
- TraceView:能夠經過Debug類在代碼中調用Debug.startMethodTracing(String tracePath)和Debug.stopMethodTracing()來記錄二者之間全部線程及線程中方法的耗時,生成.trace文件.經過abd命令能夠將trace文件導出到電腦,經過CPU profiler分析.
- Systrace:能夠經過命令行生成html文件,經過Chrome瀏覽器進行分析.
- 生成html文件已實現.但文件怎麼分析暫未掌握,看了網上一些文章說實話仍是沒搞懂
- Hierarchy Viewer:由Layout Inspector代替.但當前版本的Layout Inspector不能查看每一個View具體的onMeasure,onLayout,onDraw耗時,功能是不足的.咱們可以使用系統提供的Window.OnFrameMetricsAvailableListener來計算指定View的onLayout及onDraw耗時.
- Network Traffic tool:由Network Profiler代替.
2.其中Android Profiler如何使用,直接看官網便可.Profile your app performance.
3.TraceView
- TraceView能夠直接經過CPU profiler中點擊Record按鈕後,任意時間後點擊Stop按鈕.便可生成trace文件.並可將.trace文件導出.
- TraceView也能夠經過Debug類在代碼中精確控制要統計哪一個區間代碼/線程的CPU耗時.
<br>這種用法是 anly_jun大神文章裏學到的代碼執行完畢,會在Android設備中生成JetApp.trace文件.經過Device File Explorer,找到sdcard/Android/data/app包名/files/JetApp.trace<br>
在JetApp.trace上點擊右鍵->Copy Path,將trace文件路徑複製下來.<br>
Windows下cmd打開命令行,執行 adb pull 路徑,便可trace文件導出到電腦. - trace文件分析很簡單.咱們能夠看到每一個線程及線程中每一個方法調用消耗的時間.
4.Layout Inspector很簡單,在App運行後,點擊Tools->Layout Inspector便可.
下面只看Window.OnFrameMetricsAvailableListener怎麼用.
從Android 7.0 (API level 24)開始,Android引入Window.OnFrameMetricsAvailableList接口用於提供每一幀繪製各階段的耗時,數據源與GPU Profile相同.
-
在Activity中使用OnFrameMetricsAvailableListener:
- 經過調用this.getWindow().addOnFrameMetricsAvailableListener(@NonNull OnFrameMetricsAvailableListener listener,Handler handler)來添加監聽.
- 經過調用this.getWindow().removeOnFrameMetricsAvailableListener(OnFrameMetricsAvailableListener listener)取消監聽.
-
解析ConstraintLayout的性能優點中引用了Google使用OnFrameMetricsAvailableListener的例子android-constraint-layout-performance.其中Activity是Kotlin寫的,嘗試將代碼轉爲java,解決掉報錯後運行.
- 在Application中使用OnFrameMetricsAvailableListener,則能夠統一設置,不須要每一個Activity單獨設置,推薦使用開源項目ActivityFrameMetrics
- 在Application的onCreate中設置單幀渲染總時間超過W毫秒和E毫秒,會在Logcat中打印警告和錯誤的Log信息.
- Application設置完成運行App,出現單幀渲染總耗時超過指定時間,便可看到Logcat中的信息.
5.Systrace:經過命令行生成html文件,經過Chrome瀏覽器進行分析
- 首先電腦要安裝python,這裏有幾個坑:<br>
- Python要安裝2.7x版本,不能安裝最新的3.x.
- 好比本身電腦中systrace文件夾路徑是:C:\Users\你的用戶名\AppData\Local\Android\Sdk\platform-tools\systrace,若是咱們安裝的是3.x版本,在這個路徑下執行python systrace.py *** 命令會報錯,提示你應該安裝2.7
- Python安裝時候,要記得勾選"Add python.exe to Path".
- 這時候直接執行python systrace.py ***命令仍是會報錯:ImportError: No module named win32com
- Python要安裝2.7x版本,不能安裝最新的3.x.
- 生成html及如何分析