目錄
最全的性能優化點總結一
、啟動優化 1、項目背景 2、檢測啟動時間 3、列印啟動時間 4、優化理念 5、啟動時透明頁優化 6、MultiDex優化 7、多程序時,防止sdk多次初始化 8、最終結果:... 5二
、記憶體優化... 6 1、項目背景... 6 2、性能優化的理念... 6 3、了解對象之間的引用關系和對象大小的占用... 6 4、了解Android中經常造成記憶體洩漏的點... 6 (1)、耗時任務:網絡請求、屬性動畫、Timer 6 (2)、handler 6 (3)、匿名/非靜态内部類... 7 (4)、單例、applicationContext 、ThreadLocal等... 7 (5)、WebView記憶體洩露... 7 (6)、資源未及時關閉... 7 5、記憶體溢出問題... 7 6、記憶體抖動的問題... 7 7、體驗優化... 7 8、記憶體視圖檢視;... 8 9、了解對象産生和配置設定過程;... 9 10、對象建立的過程中代碼執行順序... 9 11、SP檔案的優化... 9 12、詳細的優化過程... 9 13、優化結果... 10三
、 安裝包瘦身... 10四
、 網絡優化... 13五
、 第三方庫的一些簡單替代方案... 14六
、 圖檔加載庫的問題以及性能比較... 16 1、問題背景:... 16 2、圖檔庫測試結果:... 17 3、測試過程... 17 4、使用建議:... 17七、
鎖的優化synchronized和lock. 17 鎖的優化方向:... 17 延伸單例寫法推薦... 18八
、MVP設計模式的弊端及解決方案(重點) 19 Mvp存在的問題:... 19 (1) 首先是設計思路問題... 19 (2) 過多的present和model的問題... 19 (3) 記憶體洩露的問題... 19 (4) 解決辦法... 19 (5) 此mvp設計總結:... 25九
、View繪制方面的優化... 25 (1)多嵌套問題:... 25 (2)各種控件性能比較:... 25 (3)使用占位符ViewStub:... 25 (4)結語:... 25 十、一些知名的記憶體管理監測軟體... 26 (1) LeakCanary. 26 (2) koom... 26 十一、資料加密優化... 26 項目背景:... 26 優化方案:... 26 對稱加密... 26 非對稱加密... 27 雜湊演算法:... 27 常用的加密方案:... 27 總結:... 27 十二、修正一下程式設計思想!面向對象 or 面向過程... 27 (1)面向對象開發和面向過程開發... 27 十三、架構方面:對app代碼業務邏輯的一些設計思考... 30 1、項目背景:... 30 2、優化思路:... 30 3、詳細内容,舉例說明... 30 十四、資料庫和高并發優化... 33 1、問題背景... 33 2、高并發優化... 33 3、資料庫優化... 33 十五、android特有的庫和一些代碼基礎寫法... 33最全的性能優化點總結:
一、啟動優化
1 、項目背景
19公司裡的一個項目,是一個第三方庫特别多的app,會啟動10個廣告sdk、若幹遊戲sdk。初期都是在application裡面直接初始化的
adb shell am start -W 包名/界面名
3 、列印啟動時間
onCreate、onResume等方法的耗時,尤其是初始化許多sdk 的方法(可使用aop方法、或者system時間相減,都可以;aop的包有apply plugin: 'android-aspectjx' implement 'org.aspectj:aspectjrt:1.8.+')
篩選啟動時必須加載的sdk,其他的則放在子線程裡面或者延時加載
4、優化理念:
把十幾個要執行個體化的sdk分成三部分:先加載、延遲加載、異步加載、懶加載
先加載:因為首頁有開屏廣告,是以開屏廣告的sdk必須有先加載
把各種庫分成四種情況都是為了盡可能提高啟動速度;
5、啟動時透明頁優化:
設定透明主體或者提早設定背景頁面;都比較正常的方法,我選擇了設定背景頁面,給使用者一個第一時間我就打開了app的感覺,否則會有一種卡頓的感覺
這個問題,在我們第三方sdk特别多的項目裡尤為明顯;體積小的apk一般就一個dex;開啟多dex打包之後,就會産生很多dex檔案;這個我用的主流的方法;異步加載其他dex;最惡心的過程就是手動分包,因為要保證啟主dex體積比較小;能快速的加載出來,然後其他dex就放在子線程裡邊去加載
項目當中有一些音樂播放是誇程序,是以需要防止一些sdk多次初始化,這個比較簡單,不貼代碼裡額
8、最終結果:
啟動時間節省了一半以上
二、記憶體優化
1、 項目背景
這個是15年銀行的電商app項目,由于當時的項目前後端整體設計不是特别完善;整個的圖檔的處理都是在用戶端完成的;包括加水印、壓縮等等,而且圖檔不管是上傳還是下載下傳的都是高清原圖,是以對app的性能是極大的考驗;也是當時的app前後端設計不是特别合理,是以草導緻了這個問題,不過也是以我鍛煉了記憶體優化的能力和知識
2、性能優化的理念
兩個強引用對象之間有引用關系,且其中一個對象生命周期較長,在15年優化一個項目的時候,許多人會手動調用System.gc(),這是性能優化一大禁忌;簡單說我們創造對象是可回收的環境,讓GC需要的時候自己就來處理垃圾收,本身垃圾回收的操作也是消耗性能的;這就像在一個小區裡邊,有專門收垃圾的車進來,你隻需要把垃圾放到垃圾桶中,其他的就不要管了,不要自己頻繁的去呼叫垃圾車來收垃圾;一旦來收垃圾的時候你自己也要停止工作(stop the world),大家都耽誤事兒
3、了解對象之間的引用關系和對象大小的占用
按預設開啟指針壓縮來算,除了基本資料類型之外,其他類型是占用四個位元組,也就是多寫一個成員變量對象就增大四個位元組;和這個成員變量有沒有具體引用到那個對象沒關系,隻要是生命了就會多占用記憶體
4、了解Android中經常造成記憶體洩漏的點
還有第三方控件,那個放在後面常見的記憶體洩漏情況有:
(1) 、耗時任務:網絡請求、屬性動畫、Timer
解決辦法就是及時解除強引用關系、把指針置空、及時停止耗時任務;或者用弱引用設定指針關系
(2)、handler
Handler一般是因為有延時任務,message裡有個Handler類型的target變量,這個就是引用handler的成員變量,是以如果延時任務不結束,那麼handler所在對象就無法被回收掉。解決辦法就是重寫handler或者及時清除延時任務
(3)、匿名/非靜态内部類
這個就是内部類對象會持有外部類的引用,是以如果非靜态内部類對象/匿名對象的成生命周期較長就會影響外部類對象的回收。
(4)、單例、applicationContext、ThreadLocal、内部類廣播等
其實都是強引用關系和生命周期不同造成的的;解決辦法就是用弱引用和及時解除他們的關系,因為一般單例、appContext、ThreadLocal不會被幹掉,生命周期比較長,是以就隻能及時解除關聯關系,把變量的引用置空就可以了
(5)、WebView記憶體洩露
這個問題存在很久了,也沒注意現在有沒有被修複,主要原因是webView核心webkit和application有注冊關系,是以一定要反注冊,解決辦法就是activity關閉之前,擷取webView的父布局,然後remove掉WebView,然後停止webView的一些方法、清楚曆史清除views。最後再執行WebView.destroy();
(6)、資源未及時關閉
這個就是IO流、File檔案流、Sqlite這些隻用完畢時候要及時關閉
(7)、要反注冊
比如:eventBus,廣播,以及ContentProvider等等,都需要在onDestory裡面反注冊;我寫的mvp模式的evm也需要反注冊;
5、記憶體溢出問題
這個在Android中,一般就是bitmap或者視訊之類的容易造成記憶體溢出,記憶體溢出分兩種,一種是直接溢出,一種是記憶體洩露累積起來的記憶體溢出,都是堆空間裡滿了造成的oom;bitmap就是壓縮圖檔,bitmap的大小就是和像素的面積和每個像素點的色位決定的;這個就是按照bitmap要顯示的實際大小進行壓縮;一般就是計算壓縮比例,然後抽取bitmap圖檔;
6、記憶體抖動的問題
這個就是要優化程式了,現在這個問題出的情況比較少,也是15年的時候優化過,裡面有一些比較大的對象進行頻發的建立和銷毀就會造成記憶體抖動,其實就是會頻繁的出發GC操作;延長這個對象的生命周期或者優化這個對象的大小
7、體驗優化
這一般是整體架構的問題,加載一些圖檔的時候,就算手機記憶體足夠大,但是網速也限制圖檔的下載下傳速度,尤其是在一些清單圖檔功能裡,體驗就會很差,如果圖檔體積很小,那給人的感覺就不同;現在都比較正規了,這個問題也幾乎沒有了
8、記憶體視圖檢視;
學會使用Android profiler,先手動gc,然後根據類名尋找對象是否存在,就可以查找到Android對象是否記憶體洩漏了;除了記憶體外,cpu、網絡、能耗都可以查得到
搜尋對象是否存在
9、了解對象産生和配置設定過程;
可配置設定在堆記憶體、棧記憶體;堆記憶體中還包括新生代、老年代,還要根據對象的大小去判斷;
10 、對象建立的過程中代碼執行順序
靜态變量、靜态代碼塊,代碼塊;構造方法、父類和子類中這幾種情況的執行順序,了解這些會有助于了解代碼的執行過程和記憶體的配置設定
11 、SP檔案的優化
其實就是同步還是異步的問題,想要性能好肯定選擇異步,然後合并多次commit();
commit(同步)apply(異步)
替代方案:mmkv 騰訊出的,從15年開始一直在微信上使用,可見其性能得到了考驗
12 、詳細的優化過程
(1)、選擇最優圖檔加載庫:格比較了各種圖檔庫的優缺點和性能問題,選擇一個最穩定的,最終确定了使用frsco(選擇過程後面,解析第三方圖檔庫有講解)
(2)、清除項目裡手動GC操作
(3)、解除activity裡的網絡請求操作和主acitvity的匿名回調對象的關系;采用統一的回調方式,這樣網絡請求的耗時操作就不會影響正常的acitivty對象回收了
(4)、針對項目裡的記憶體抖動問題,盡量延長對象的聲明周期、壓縮對象的體積,目的旨在減少system.gc出來工作的次數
(5)、bitmap優化,設定bitmap.Config為ARGB_4444或者565;再就是根據控件實際顯示的大小,計算壓縮比例,建立像素合适的bitmap;最後就是及時釋放資源執行recycle()方法并且置空;因為項目裡有bit加水印的操作,是以需要自己處理一下
(6)、寫法上的優化,把加載fragment的寫法從add改成replace。activity裡邊減少成員變量,有一些隻有個點選事件,并無其他操作,是以不必在activity建立成員變量,少寫一個成員變量就節省了4個位元組的空間量變引質變。直接再xml檔案裡寫onClick方法,而且效率要遠遠比先findViewById然後再setOnclickListener高出很多很多;再就是盡量減少布局的嵌套、多用include和megre、viewStub
(7)、測試一些父控件的執行效率,效率最差的RelativeLayout,LinearLayout和Fragmentlayout性能相差無幾;那時候ConstraintLayout還未流行起來,是以未做處理,一些簡單布局優先使用Linear/FrameLayout;
(8)、用android studio逐個activity檢查記憶體洩漏的情況;檢查過程就是先打開此activity頁面,然後關閉,然後手動調用幾次gc操作;最後檢視此activity對象是否還存在,如果還存在就表示有記憶體洩漏,再逐個排查,多數都是匿名對象造成的(9)、檢查有和單例、ApplicationContext扯上關系的對象,這些都是記憶體洩露潛在的點,是以要重點檢查
(10)、檢查handler的延時任務,那時候還不會弱引用,是以隻是手動的在activity關閉的時候,自己手動清除handler的延時任務
(11)、把項目裡的ListView替換成RecyclerView,RecyclerView性能比ListView好很多,也是經過測試的;
(12)、app建立線程池,來統一管理一些定時任務,包括輪播、短信驗證碼等。
(13)、RecyclerView、ListView之類的控件,在滑動的時候要停止圖檔的加載
13、優化結果
項目從原來的崩潰率特别高,降到非常低,崩潰情況不會出現了
三、安裝包瘦身
項目背景
也是16年銀行的直銷銀行項目,android包體積過大,其中也主要是因為加入人臉識别、地圖等各種so庫造成的
優化方案和過程
1、so檔案過多,按照cpu架構有個多個類型,這個看app的使用潛在人群,如果是手機一般隻用v7就可以,如果是pad那就用x64,x86之類的,總之要具體選擇
當時的項目用的是v7
2、 預設國際化:關掉預設國際化
因為android打包成apk之後,apk裡會有一個叫resources.arsc的檔案,裡邊都是res/values檔案夾下檔案生成的,而且預設會生成很多國家的語言像這樣
我們隻要設定隻支援中文或者英文就可以了
3、 一些圖檔轉換成webp格式,當然了壓縮的時候肯定會有是真的情況,這個就看你怎麼取舍了,如果失真不明顯那麼能壓縮還是要壓縮的;一些小的圖示可以使用Vector矢量圖,這個體積也是比png小很多,而且不需要進行适配,矢量圖可以根據實際需求顯示大小,不想使用png還要區分各種螢幕大小
矢量圖vector,現在建立個項目android的logo就是vector矢量圖
4、 正常手段:混淆(代碼混淆、資源檔案混淆)、去除無用檔案;下圖是去處無用檔案的,混淆我就不解釋了
5、 優化結果:項目體積減小了很多,主要是so檔案的功勞,so檔案的選擇對于android體積減小是最客觀的,其他的作用都不是太大,但是積少成多效果很明顯的;上面的優化方案:資源檔案的混淆和Vector矢量圖的優化方案在當時的項目是沒用上的,那時候還不知道這些,但是借這次機會也總結出來分享給大家
四、網絡優化
1、這個我覺得沒什麼太好的辦法,首先根據目前網絡情況來處理資料,如果是網絡比較差的時候,可疑更換網絡協定,直接使用tcp、mqtt之類的;如果是http那麼打開gzip 壓縮;使用ip位址免解析等等
2、 自己建立資料解析和壓縮的字典;
3、 資料緩存、連接配接池複用、合并請求
五、第三方庫的一些簡單替代方案
EventBus
這雖然是一個很好用的庫,但是有很嚴重的性能問題,對于我這種代碼潔癖的人來說不可忍受;他提供的庫雖然很豐富,但是要周遊一個類裡的所有方法,然後識别出需要的,尤其是在activity裡邊,本身activity的的方法和變量都特别多,無形之中就是消耗的許多性能;解決方案比如可以這樣寫:
想在哪裡接受資料,就在哪裡執行注冊,比如在acitivty中:
面也有記憶體洩露的問題,匿名内部類對象;此處隻做案例,如果想切換線程,也可在裡面建立一個handler,就可以做到eventBus的全部工作,但是性能會比它好很多
注解綁定控件庫:
注解去執行個體化View控件也是存在這個問題;會周遊所有的成員變量;并且會額外産生類,并且包含你所有的控件成員變量,相當于acitivty站用記憶體相當于翻倍了,多聲明一個成員變量就多占用四個位元組的空間,而且還會多建立一個對象,16個位元組;如果頁面比較多累計起來不僅消耗記憶體還消耗性能,像Xuitls、buffterknife等等都是相同的原理
RxJava的替代方案:
可疑自己寫一個簡單,隻要能符合目前app的業務需求,隻是不滿足,也可以繼續改進滿足,比如下面的例子:當然,如果你的業務中特别複雜,第三方庫了的所有功能基本都能用到,那還是直接用他們的比較好;這個就是開發友善和性能之間的一個平衡。
使用方法:
1、 結語:大多數app的業務沒那麼複雜,第三方庫提供能的功能可以說異常的豐富;對于大部分項目來說過于豐富,很多功能是用不上的,是以就造成了額外的性能消耗;當然自己寫的時候要注意記憶體洩露的問題,如果對記憶體洩漏問題掌握的不夠自信,那老老實實用第三方庫也是可以的
六、圖檔加載庫的問題以及性能比較
、問題背景:
這個也是15年銀行的電商app項目性能優化的時候,碰到的問題,因為内容比較多,是以單拿出來說一說;比如Imageloader性能最好,但是有很嚴重的記憶體洩露的問題;Fresco 會徹底解決記憶體的問題,但是使用起來卻沒有imageLoader流暢;picsso 在我看來和ImageLoader差不多;現在最長的是glide,因為glide有一些自動管理圖檔加載的機制和context的生命周期的管理;在15年的時候,清單類的圖檔,在滑動的過程中要自己手動停止圖檔加載功能;這個gilde都幫我們處理了;還有context的聲明周期管理,避免的記憶體洩露的産生
2 、圖檔庫測試結果:
Fresco穩定性最好;Glide體驗最流暢也沒有記憶體洩露的問題;有自動的生命周期管理,ImageLoader、Xutils、piacsso基本被淘汰了;現如今反編譯很多知名廠商app、使用的圖檔庫都是Glide
、測試過程:
當時是寫了一個相冊的功能,分别使用以上圖檔加載庫去加載相冊,然後反複打開關閉相冊頁面,記錄打開次數、流暢度;測的最終上面的結論
4、使用建議:
個人建議:xUitls3、Imageloader、picasso可以抛棄了;Imageloader有很嚴重的記憶體洩露問題,而且也不更新了xUtils和picasso估計也有,這是機制的問題;畢竟網絡請求是一個耗時的 操作,都需要傳入context上下文
建議使用Glide:理由
(1)、絕大多數主流app,反編譯這些源碼,使用的都是Glide;可見它的受歡迎程度
(2)、它自有的Context生命周期管理,可以在activity/frgment頁面關閉的時候Glide可以在第一時間檢測到,停止圖檔的下載下傳,這樣就防止了記憶體的洩露和cpu資源的消耗;
(3)、glide支援gif圖檔
(4)、支援配合滑動清單滑動時候停止加載圖檔
(5)、最後說Fresco,這是個壓箱底的東西,如果Glide都不能滿足的時候,再把它拿出來;Fresco用c寫的庫,把圖檔資料存儲在了ashmem區域,這樣就不占用jvm堆記憶體了,是以說它的終極大招;但是他使用起來卻沒有前面幾個java的體驗感更加流暢,是以把它放在最後的選擇;
至于其他的一些緩存機制、緩存政策這些,比如緩存的是壓縮後的圖檔還是原圖,這個差別不大,而且都可以手動修改設定,是以這些機制就不做參考了
七、鎖的優化synchronized和lock
這個就簡單一說吧,要了解synchronized的鎖更新過程、粗化、消除等等;lock裡的抽象隊列同步器,cas自旋、使用者态、核心态、作業系統互斥量;内容較多,可以自行學習,我就簡單說下結論:并發量小就用sync關鍵字,并發量大就使用Lock
鎖的優化方向:
1、 盡量減小加鎖部分代碼的執行時間,因為可能有其他線程在等待,等待的線程越多,最後的那個線程能執行到加鎖裡的内容的時間越長
2、減小鎖的力度或者是範圍:比如Map中的ConcurrentHashMap,它是一個線程安全的集合,鎖隻是鎖住了單獨的桶,就算是兩個線程同時寫入資料,隻要hash值算的下标不再同一個位置就不會有影響;
3、鎖分離:把兩種互不影響的操作,分别加鎖,比如linkedBlockQueue
我們看到針對不同的操作,分别用不同的鎖;
延伸單例寫法推薦:
單例推薦靜态内部類單例,即使線程安全的也是懶加載,而且不需要枷鎖,是以性能上會節省一丢丢;
八、嗎MVP設計模式的弊端及解決方案(重點)
Mvp 存在的問題:
從最開始接觸mvp模式,不斷的思考和改進mvp的寫法,以達到最好的要求,代碼量小,解耦;業務邏輯清晰,嘗試過很多次,今年自己要寫開源項目,是以幹脆就根據多年積累重新整理了一個mvp的寫法,已經放到github上了,歡迎交流
(1)首先是設計思路問題
是按照業務就劃分View還按照接口去劃分View;如何去劃分Persent;present和model還有View之間是不是要一一對應的關系;其實這就牽扯了activity;如果按照接口去劃分View那麼View直接定義成ResponseData類型的傳回值就行了;有幾個接口就定義多少個View;另外一種方式就是按照業務去劃分,比如登入的業務可能包括登入、驗證碼、短信登入、忘記密碼,這些操作,把這些業務都寫到一個LoginContract裡邊,再分别定義其他各種業務,我是比較推崇後者的
(2)過多的present和model的問題
如果一個頁面中,隻有一個接口,一個簡單的功能,也需要額外寫一個present、model、view嗎?這樣就很備援;也不利于開發速度;如果這樣的activity還特别多,那就是寫一個actvity就要寫一個view和present,顯然這樣代碼量太大太蠢了
(3)記憶體洩露的問題
我們知道網絡請求是耗時操作,一旦網絡不好的情況使用者又關閉了activity,此時這個activity是無法被回收掉的這就造成了記憶體洩露問題。Activity一般是記憶體占用大戶,雖然可以用弱引用去處理,但是弱引用也會額外的建立對象,會增大記憶體的占用
(4)解決辦法
建立Evm中間類,讓present和View、model中間,不産生強引用關系,是以也就不會産生記憶體洩露的問題;再就是present和modle都可以自由定義,想定義幾個就定義幾個,都可以通過EVM中間類進行關聯,而且采用的是反射的方法,隻擷取接口,是以對性能無影響,不像EventBus和ButterKnife那樣要周遊所有的方法或者變量,而且如果View已經回收,則會生成一個臨時該接口類型的對象,不需要是否為空判斷
代碼是:
/**
* description:
* author: tianhonglong
* new date: 2021/7/9
* version: v 1.0
*/
public class EVM {
private EVM() {
}
private Map<String, EasyView> views = new HashMap<>();
private Map<String, EasyPresent> presents = new HashMap<>();
private <T extends EasyView> T getView(Class<T> clazz) {
EasyView easyView = views.get(clazz.getSimpleName());
if (easyView == null) {
try {
return clazz.newInstance();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
return (T) easyView;
private <T extends EasyPresent> T getPre(Class<T> clazz) {
EasyPresent present = presents.get(clazz.getSimpleName());
if (present == null) {
try {
T t = clazz.newInstance();
presents.put(clazz.getSimpleName(), t);
return t;
return (T) present;
private void managerView(EasyView easyView, boolean registerOrNot) {
Class[] classes = easyView.getClass().getInterfaces();
for (Class clazz : classes) {
if (EasyView.class.isAssignableFrom(clazz)) {
if (registerOrNot) {
views.put(clazz.getSimpleName(), easyView);
} else {
views.remove(clazz.getSimpleName());
}
public static void register(EasyView easyView) {
EVM.ins().managerView(easyView, true);
public static void unregister(EasyView easyView) {
EVM.ins().managerView(easyView, false);
public static <T extends EasyView> T getV(Class<T> clazz) {
return EVM.ins().getView(clazz);
public static <T extends EasyPresent> T getP(Class<T> clazz) {
return EVM.ins().getPre(clazz);
private static class InnerClass {
private static EVM easyPresent = new EVM();
private static EVM ins() {
return InnerClass.easyPresent;
}
在basectivity中使用:
而且隻搜尋存在接的接口,一個類的接口不會太多,是以不會影響性能
定義一個基類接口
寫一個LoginContract,把和登入有關的View和present都定義在裡邊
LoginActivity中,
在看看present代碼:優雅的很
(5)此mvp設計總結:
1、 徹底解除View、Present、model之間的關聯,其中model、View、present各自想寫幾個就寫幾個,通過evm中間類關聯,都可以互相調用;
2、 解決了View的記憶體洩露問題,不産生直接強引用就能互相調用,是以不會影響記憶體回收;
3、 隻需要關注業務的開發,針對接口去設計model
4、 隻需要在present裡面處理業務流程,actvity處理業務的發起和結果接受;modle按照接口文檔傻瓜式寫上就可以了,也不需要加各種try catch;底層會幫業務處理好,直接回調給目前的View
View繪制方面的優化
(1)多嵌套問題:
優化别人代碼的時候,布局檔案中最常見的就是過多嵌套問題,很多子空間就可以實作的,很多人非要加個父控件,比如LinearLayout、RelativeLayout等等,再就是用merge可以減少嵌套,由于空間渲染計算是遞歸形式,在以前的老舊機型裡,隻要嵌套7層Layout,就會有明顯的卡頓産生,但是如果同級别裡,就算是14層也不會有任何問題,是以某些界面使用LinearLayout并不會比RelativeLayout帶來更多控件層級時,優先考慮LinearLayout;;
(2)各種控件性能比較:
性能最差的是RelativeLayout;如果LinearLayout和FrameLaout都滿足的情況下,優先排除RelativeLayout; 如果再複雜就是使用google新出的ConstraintLayout; 還有RecyclerView的性能都要比ListView好很多,比如加載fragment除了用懶加載外,replace也add要節省記憶體
(3)使用占位符 ViewStub:
在一些界面裡的控件,需要最開始狀态是View.GONE;但即使是View.GONE,這個控件依然會執行各種執行個體化方法建立對象占用記憶體,隻是最後沒有通過wms渲染在手機螢幕上而已,ViewStub是一個輕量級的View;使用ViewStub後,那麼原來的控件就相當于懶加載了,隻有使用者用手操作讓它顯示的時候才會去加載,如果一直不要求顯示那就永遠不會加載;隻是會多一個輕量級的ViewStub
(4)結語
寫代碼的過程中,有許多不經意的點都是可以節省記憶體的,隻要節省每一處的記憶體和cpu的使用;累計起來就是一個好的項目;如果對記憶體不省吃儉用,累積起來的記憶體洩露造成的oom是最難解決和優化的;
九、一些知名的記憶體管理監測軟體
(1) LeakCanary
就是用WeakReference和ReferenceQueue這兩個類的機制,來檢查記憶體是否洩露的;可疑幫助你快速定位記憶體洩露的點;但是不能幫你解決記憶體洩露的問題,隻是幫你發現問題
(2) koom
快手自研OOM解決方案。效率據說比leakCanary好的多,具體還沒使用過,推薦給大家;
十、資料加密優化
這個是20年優化的一個項目,因為在銀行和金融app行業經驗豐富一些,是以在一些非銀行類app中的資料安全這一塊,做的有問題;直接用非對稱去加密、解密資料;這個性能是有很大問題的;
優化方案
要把對稱加密和非對稱加密結合起來使用,通信資料要用對稱加密去加密和解密,然後把對稱加密的鑰匙,用非對稱加密進行加解密,然後把加密後的鑰匙拼接在隐藏在資料中,這樣在安全性不變的情況下,性能會大大的提升
對稱加密
名稱 | 密鑰長度 | 運算速度 | 安全性 | 資源消耗 |
DES | 56位 | 較快 | 低 | 中 |
3DES | 112位或168位 | 慢 | 高 | |
AES | 128、192、256位 | 快 |
非對稱加密
成熟度 | 安全性(取決于密鑰長度) | |||
RSA | ||||
DSA | 隻能用于數字簽名 | |||
ECC | 低(計算量小,存儲空間占用小,帶寬要求低) |
雜湊演算法:
速度 | ||
SHA-1 | ||
MD5 |
常用的加密方案:
MD5:比如使用者輸入的密碼,用MD5進行加密,直接存儲在背景的就是MD5資料,再就是校驗資料的完整性
Sha-1:一般簽名密鑰裡東西
對稱加密:加密基本資料,因為性能比非對稱加密好的多
非對稱加密:加密對稱加密的鑰匙,組合使用,這樣安全性即達到了非對稱級别,性能也上去了;
總結:
MD5校驗完整性+對稱加密加密全部資料+非對稱加密對稱加密的密鑰
最常用的就是MD5+RSA+AES
十一、修正一下程式設計思想!面向對象 or 面向過程
(1)面向對象開發和面向過程開發
這個是優化現在公司裡的一個項目;雖然java是面向對象語言,但是在很多項目中的同僚,仍然使用面向過程的思維模式開發;舉個簡單的例子,比如曾經做的人臉識别app中,有這樣一個場景,在人臉識别拿到資料之後,産生了一個使用者對象,裡面包含了使用者的起止時間使用者身份;先看面向過程寫法,極其簡單的一個案例:
再看面向對象寫法:
可讀性和簡潔性對比明顯;就是在寫代碼的時候要厘清業務流程和業務細節;此處的流程就是此人是否可通行,業務細節就是判斷此人是否可通行的過程;這部分代碼不要出現在流程裡邊
上面隻是寫了個簡單的例子,我們項目中實際的是否允許通過的判斷要複雜的多,包括多分組、多時段、是以判斷過程很複雜代碼量也不少
十二、架構方面:對app代碼業務邏輯的一些設計思考
1、項目背景:
這個是優化了代碼的業務邏輯,其中最典型的人臉識别頁面activity,有7500+行代碼,裡面包含了各種業務包括:資料的同步、UI的顯示(識别結果展示)、人臉認證的過程(包括人臉、溫度、口罩、距離識别、硬體接口回調等等),還包括一些業務細節的處理比如最後兩次人臉是否同一個人,wifi狀态監聽的等等吧;
2、優化思路:
按照業務分類,可分為UI部分、驗證流程部分、驗證細節處理部分、面向對象部分;拆分成三個activity,原來是一個FaceVerifyActivity,如今拆分成FaceBusinessActivity,FaceUIActivity,其中繼承關系:
FaceVerifyActivity 繼承FaceBusinessActivity繼承 FaceUIActivity
FaceUIActivity的功能應該隻包含UI部分,和人臉認證沒有任何邏輯關系,隻是提供了人臉結果出來的時候,可能需要顯示的各種dialog、或者其他UI
FaceBusinessActivity裡面包含最後兩次是否同一個人的判斷方法,資料同步的方法等等
FaceVerifyActivity:純粹是業務流程的判斷,然後根據每次判斷的結果,來調用FaceUIActivity和FaceBusinessActivity的方法,這樣整個業務邏輯就清晰很多
3、詳細内容,舉例說明
由于原項目代碼量特别大,是以也不可能全部貼出來;是以此處舉例說明:
首先是UIActivity,定義了一些需要顯示的UI對話框之類的
其次是業務Activity,定義了開門、資料同步,是否同一個人等等
最後是主流程activity:純粹的流程判斷
1、 在案例中每個頁面30~40行代碼,如果不拆分,不按照面向對象寫那麼在一個頁面中就會有110行的代碼,這隻是案例,真實項目中把案例代碼量擴充70倍,才是我們項目中的實際代碼,一個activity有7000多行代碼,誰不頭疼?拆分後分成三部分,每部分就兩千多行,而且除了FaceVerifyActivity外,其他幾個類中方法之間沒有調用,都是單獨的業務方法,以後修改起來會簡單容易的多;這其實也是屬于子產品化思想,按類型分類,就是目的都是為了讓代碼單一、簡潔、易修改、便于擴充;
十三、資料庫和高并發優化
1、問題背景
首先說慚愧;這個問題不是我實際工作的經驗;這個是去某家公司面試的時候,面試官問的問題,問的高并發的資料存儲和資料優化,回答的不是太好,也确實沒這方面經驗,本着不會就要學習的态度,還是研究了很多文章和代碼,在此也分享出來,在此直說用戶端如何解決
2、萬人群高并發優化
問題1:消息從未讀到已讀,這個消息如何發送? 如果是實時的,那一條已讀消息要發送給一萬個人,如果一萬個人同僚讀了這條消息,就會發生一萬個人同僚給一萬個人發消息,并發量可想而知;這個解決辦法就是降低頻率,是否已讀,10秒鐘才去重新整理一次,這樣就大大降低了消息的并發數
問題2:一個人接受到不同人的多條資訊時,建立緩沖區。,合并多條消息給一人,甲乙丙丁同時給A發送消息,那麼就建立緩沖機制,把甲乙丙丁合成一條消息,這樣最後技術層面A隻收到一套消息,總之目的就是降低并發數量;
3、資料庫優化
資料庫的并發優化和IM通信類似,資料裡邊有事務,其實就相當于建立緩沖區合并多條資料,然後開啟專門的現成去執行資料庫的操作
(1)、開啟事務
Android中的sqlite是預設開啟事務的,就算隻是隻有一條資料的插入更新也會幫你開啟事務,是以當并發量大的時候,把多條執行語句放在一個事務裡,這樣就提高了性能,不用每次都打開關閉事務;
(2)、建立索引
索引就是把資料庫裡的資料,建立一個目錄,在查找的時候不用一頁一頁去查了,索引一般是平衡樹結構;簡單說就是利用算法和資料結構提高效率,但是貌似在資料量特别大的時候,維護索引也會産生不小的開銷;
(3)、耗時的話進行異步操作
比如有些資料的存儲和同步不需要知道傳回結果,這樣建立一個線程池去執行這些任務,這樣就不會影響主線程操作
十四、常見的ANR操作
原理也很簡單,系統服務ams和wms會檢測app響應時間,也就是本地的applicationThread是否會及時的給ams和wsm發送binder資訊,如果超過一定的時間未發送,系統就會認為你卡住了;就會提示無響應,因為通過applicationThread給系統服務發送資訊都是通過主線程來執行的,是以一旦在主線程中執行耗時操作就會引起ANR
十五、android特有的庫和一些代碼基礎寫法
1、對象的序列化android 特有的Parcelable比Serializable的性能好
2、 Android特有的集合:在資料量小的情況下使用SparseArray、ArrayMap代替java集合
3、 Map的多種周遊方式,那種效率最快
4、 在寫基礎庫的時候,尤其是處理資料,線程安全的情況下使用StringBuilder、線程不全的情況下使用StringBuffer
5、 根據資料存儲和使用情況來判斷使用連結清單集合還是數組集合
6、 盡量使用基本資料類型,比如int類型的成員變量一共就占用四個位元組的堆空間。如果用Integer除了所在類中成員變量的四個位元組外,還會有Integer對象的16個位元組;
7、 循環中減少對變量的重新計算
比如:for(int I = 0; i < list.size(); i++) 改為for(int I = 0, len = list.size(); i = len; i++)
8、 避免使用二位數組,資料比較特殊,不管你是否存入對象,數組建立的那一刻,記憶體已經消耗掉了,數組裡每個指針占用四個位元組;不算對象頭和數組長度,一個長度為10的二維空數組建立的那一刻就是占用10*10*4 = 400個位元組;還不算對象頭類指針數組長度;像ArrayList也一樣,因為都是數組
Json序列化性能對比:資料量小就用gson,資料量大就用阿裡巴巴的fastjson
作者:田洪龍