藉助友盟+U-APM實現安卓效能優化總結

效能優化實踐者發表於2021-11-19

最全的效能優化點總結:

零、 啟動優化

1、專案背景

19公司裡的一個專案,是一個第三方庫特別多的app,會啟動10個廣告sdk、若干遊戲sdk。初期都是在application裡面直接初始化的

2、 檢測啟動時間

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的感覺,否則會有一種卡頓的感覺

6、MultiDex優化

這個問題,在我們第三方sdk特別多的專案裡尤為明顯;體積小的apk一般就一個dex;開啟多dex打包之後,就會產生很多dex檔案;這個我用的主流的方法;非同步載入其他dex;最噁心的過程就是手動分包,因為要保證啟主dex體積比較小;能快速的載入出來,然後其他dex就放在子執行緒裡邊去載入

7、多程式時,防止sdk多次初始化

專案當中有一些音樂播放是誇程式,所以需要防止一些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的業務沒那麼複雜,第三方庫提供能的功能可以說異常的豐富;對於大部分專案來說過於豐富,很多功能是用不上的,所以就造成了額外的效能消耗;當然自己寫的時候要注意記憶體洩露的問題,如果對記憶體洩漏問題掌握的不夠自信,那老老實實用第三方庫也是可以的

五、 圖片載入庫的問題以及效能比較

1、問題背景:

這個也是15年銀行的電商app專案效能優化的時候,碰到的問題,因為內容比較多,所以單拿出來說一說;比如Imageloader效能最好,但是有很嚴重的記憶體洩露的問題;Fresco 會徹底解決記憶體的問題,但是使用起來卻沒有imageLoader流暢;picsso 在我看來和ImageLoader差不多;現在最長的是glide,因為glide有一些自動管理圖片載入的機制和context的生命週期的管理;在15年的時候,列表類的圖片,在滑動的過程中要自己手動停止圖片載入功能;這個gilde都幫我們處理了;還有context的宣告週期管理,避免的記憶體洩露的產生

2、圖片庫測試結果:

Fresco穩定性最好;Glide體驗最流暢也沒有記憶體洩露的問題;有自動的生命週期管理,ImageLoader、Xutils、piacsso基本被淘汰了;現如今反編譯很多知名廠商app、使用的圖片庫都是Glide

3、測試過程

當時是寫了一個相簿的功能,分別使用以上圖片載入庫去載入相簿,然後反覆開啟關閉相簿頁面,記錄開啟次數、流暢度;測的最終上面的結論

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;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
    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

九、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中的資料安全這一塊,做的有問題;直接用非對稱去加密、解密資料;這個效能是有很大問題的;

優化方案:

要把對稱加密和非對稱加密結合起來使用,通訊資料要用對稱加密去加密和解密,然後把對稱加密的鑰匙,用非對稱加密進行加解密,然後把加密後的鑰匙拼接在隱藏在資料中,這樣在安全性不變的情況下,效能會大大的提升
對稱加密

非對稱加密

雜湊演算法:

常用的加密方案:

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的二維空陣列建立的那一刻就是佔用10104 = 400個位元組;還不算物件頭類指標陣列長度;像ArrayList也一樣,因為都是陣列
9、 Json序列化效能對比:資料量小就用gson,資料量大就用阿里巴巴的fastjson

作者:田洪龍

相關文章