
前言
各位小伙伴們新年好呀,度過了愉快的春節假期,不知道大伙兒又長了幾斤肉肉呢~
哈哈,開個玩笑,春節過去,馬上就要迎來我們的年后金三銀四跳槽季了,大家做好面試造火箭,進廠擰螺絲的準備了嘛?在此特意給大家整理了2020年度,性能優化相關面試頻率最高的知識點,給大家面試復習做個參考,后續也會更新其他知識板塊的面試題集,各位大佬點個關注唄~
好了,廢話不多說,直接沖沖沖!
前排溫馨提示:閱讀本文前,請自備豆漿!

如果你已經有 2 - 3 年以上Android開發經驗還不懂的怎么去優化自己的專案,那就有點說不過去了,無論是日常作業中還是跳槽面試的時候,性能優化都是我們打怪成長為一名優秀的高級開發工程師所必備的技能,下面是我總結了一套通用級別的 Android 性能優化知識點以及大小廠高頻的性能優化面試真題,
一、APP啟動優化
1、 你對 APP 的啟動有過研究嗎? 有做過相關的啟動優化嗎?
之前做熱修復的時候研究過 Application 的啟動原理,專案中也做過一些啟動優化,
面試官:
哦,你之前研究過熱修復? (這個時候有可能就會深入的問問熱修復的原理,這里咱們就不討論熱修復原理) 那你說說對啟動方面都做了哪些優化?
程式員:
- 我發現程式在冷啟動的時候,會有 1s 左右的白屏閃現,低版本是黑屏的現象,在這期間我通過翻閱系統主題原始碼,發現了系統 AppTheme 設定了一個
windowBackground,由此推斷就是這個屬性搗的鬼,開始我是通過設定windowIsTranslucent透明屬性,發現雖然沒有了白屏,但是中間還是有一小段不可見,這個用戶體驗還是不好的,最后我觀察了市面上大部分的 Android 軟體在冷啟動的時候都會有一個Splash的廣告頁,同時在增加一個倒數的計時器,最后才進入到登錄頁面或者主頁面,我最后也是這樣做的,原因是這樣做的好處可以讓用戶先基于廣告對本 APP 有一個基本認識,而且在倒數的時候也預留給咱們一些對插件和一些必須或者耗時的初始化做一些準備,
Ps:這里會讓面試官感覺你是一個注重用戶體驗的
- 通過翻閱 Application 啟動的原始碼,當我們點擊桌面圖示進入我們軟體應用的時候,會由 AMS 通過 Socket 給 Zygote 發送一個 fork 子行程的訊息,當 Zygote fork 子行程完成之后會通過反射啟動 ActivityThread##main 函式,最后又由 AMS 通過 aidl 告訴 ActivityThread##H 來反射啟動創建Application 實體,并且依次執行
attachBaseContext、onCreate生命周期,由此可見我們不能在這 2 個生命周期里做主執行緒耗時操作,
Ps: 這里會讓面試官感覺你對 App 應用的啟動流程研究的比較深,有過真實的翻閱底層原始碼,而并不是背誦答案,
- 知道了 attachBaseContext 、onCreate 在應用中最先啟動,那么我們就可以通過 TreceView 等性能檢測工具,來檢測具體函式耗時時間,然后來對其做具體的優化:
1、專案不及時需要的代碼通過異步加載,
2、將對一些使用率不高的初始化,做懶加載,
3、將對一些耗時任務通過開啟一個 IntentService來處理,
4、還通過 redex 重排列 class 檔案,將啟動階段需要用到的檔案在 APK 檔案中排布在一起,盡可能的利用 Linux 檔案系統的 pagecache 機制,用最少的磁盤 IO 次數,讀取盡可能多的啟動階段需要的檔案,減少 IO 開銷,從而達到提升啟動性能的目的,
5、通過抖音發布的文章知曉在 5.0 低版本可以做 MultiDex 優化,在第一次啟動的時候,直接加載沒有經過 OPT 優化的原始 DEX,先使得 APP 能夠正常啟動,然后在后臺啟動一個單獨行程,慢慢地做完 DEX 的 OPT 作業,盡可能避免影響到前臺 APP 的正常使用,
Ps:1. 面試官這里會覺得你對啟動優化確實了解的不錯,有一定的啟動優化經驗,
2.在第五點面試官會覺得你比較關注該圈子的動態,發現好的解決方案,并能用在自己專案上,這一點是加分項!
- Application 啟動完之后,AMS 會找出前臺堆疊頂待啟動的 Activity , 最后也是通過 AIDL 通知 ActivityThread#H 來進行對 Activity 的實體化并依次執行生命周期
onCreate、onStart、onRemuse函式,那么這里由于 onCreate 生命周期中如果呼叫了setContentView函式,底層就會通過將 XML2View 那么這個程序肯定是耗時的,所以要精簡 XML 布局代碼,盡可能的使用ViewStub、include、merge標簽來優化布局,接著在 onResume 宣告周期中會請求 JNI 接收 Vsync (垂直同步重繪的信號) 請求,16ms 之后如果接收到了重繪的訊息,那么就會對DecorView 進行 onMeasure->onLayout->onDraw繪制,最后才是將 Activity 的根布局 DecorView 添加到 Window 并交于 SurfaceFlinger 顯示,
所以這一步除了要精簡 XML 布局,還有對自定義 View 的測量,布局,繪制等函式不能有耗時和導致 GC 的操作,最后也可以通過 TreaceView 工具來檢測這三個宣告周期耗時時間,從而進一步優化,達到極限,
這一步給面試官的感覺你對整個 Activity 的啟動和 View 的繪制還有重繪機制都有深入的研究,那么此刻你肯定給面試官留了一個好印象,說明你平時對這些原始碼級別的研究比較廣泛,透徹,
最后我基于以上的優化減少了 50% 啟動時間,
面試官:
嗯,研究的挺深的,原始碼平時不少看吧,
二、App穩定性優化
1、你們做了哪些穩定性方面的優化?
隨著專案的逐漸成熟,用戶基數逐漸增多,DAU持續升高,我們遇到了很多穩定性方面的問題,對于我們技術同學遇到了很多的挑戰,用戶經常使用我們的App卡頓或者是功能不可用,因此我們就針對穩定性開啟了專項的優化,我們主要優化了三項:
- Crash專項優化(=>2)
- 性能穩定性優化(=>2)
- 業務穩定性優化(=>3)
通過這三方面的優化我們搭建了移動端的高可用平臺,同時,也做了很多的措施來讓App真正地實作了高可用,
2、性能穩定性是怎么做的?
- 全面的性能優化:啟動速度、記憶體優化、繪制優化
- 線下發現問題、優化為主
- 線上監控為主
- Crash專項優化
我們針對啟動速度,記憶體、布局加載、卡頓、瘦身、流量、電量等多個方面做了多維的優化,
我們的優化主要分為了兩個層次,即線上和線下,針對于線下呢,我們側重于發現問題,直接解決,將問題盡可能在上線之前解決為目的,而真正到了線上呢,我們最主要的目的就是為了監控,對于各個性能緯度的監控呢,可以讓我們盡可能早地獲取到例外情況的報警,
同時呢,對于線上最嚴重的性能問題性問題:Crash,我們做了專項的優化,不僅優化了Crash的具體指標,而且也盡可能地獲取了Crash發生時的詳細資訊,結合后端的聚合、報警等功能,便于我們快速地定位問題,
3、業務穩定性如何保障?
- 資料采集 + 報警
- 需要對專案的主流程與核心路徑進行埋點監控,
- 同時還需知道每一步發生了多少例外,這樣,我們就知道了所有業務流程的轉換率以及相應界面的轉換率
- 結合大盤,如果轉換率低于某個值,進行報警
- 例外監控 + 單點追查
- 兜底策略
移動端業務高可用它側重于用戶功能完整可用,主要是為了解決一些線上一些例外情況導致用戶他雖然沒有崩潰,也沒有性能問題,但是呢,只是單純的功能不可用的情況,我們需要對專案的主流程、核心路徑進行埋點監控,來計算每一步它真實的轉換率是多少,同時呢,還需要知道在每一步到底發生了多少例外,這樣我們就知道了所有業務流程的轉換率以及相應界面的轉換率,有了大盤的資料呢,我們就知道了,如果轉換率或者是某些監控的成功率低于某個值,那很有可能就是出現了線上例外,結合了相應的報警功能,我們就不需要等用戶來反饋了,這個就是業務穩定性保障的基礎,
同時呢,對于一些特殊情況,比如說,開發程序當中或代碼中出現了一些catch代碼塊,捕獲住了例外,讓程式不崩潰,這其實是不合理的,程式雖然沒有崩潰,當時程式的功能已經變得不可用,所以呢,這些被catch的例外我們也需要上報上來,這樣我們才能知道用戶到底出現了什么問題而導致的例外,此外,線上還有一些單點問題,比如說用戶點擊登錄一直進不去,這種就屬于單點問題,其實我們是無法找出其和其它問題的共性之處的,所以呢,我們就必須要找到它對應的詳細資訊,
最后,如果發生了例外情況,我們還采取了一系列措施進行快速止損,(=>4)
4、如果發生了例外情況,怎么快速止損?
- 功能開關
- 統跳中心
- 動態修復:熱修復、資源包更新
- 自主修復:安全模式
首先,需要讓App具備一些高級的能力,我們對于任何要上線的新功能,要加上一個功能的開關,通過配置中心下發的開關呢,來決定是否要顯示新功能的入口,如果有例外情況,可以緊急關閉新功能的入口,那就可以讓這個App處于可控的狀態了,
然后,我們需要給App設立路由跳轉,所有的界面跳轉都需要通過路由來分發,如果我們匹配到需要跳轉到有bug的這樣一個新功能時,那我們就不跳轉了,或者是跳轉到統一的例外正處理中的界面,如果這兩種方式都不可以,那就可以考慮通過熱修復的方式來動態修復,目前熱修復的方案其實已經比較成熟了,我們完全可以低成本地在我們的專案中添加熱修復的能力,當然,如果有些功能是由RN或WeeX來實作就更好了,那就可以通過更新資源包的方式來實作動態更新,而這些如果都不可以的話呢,那就可以考慮自己去給應用加上一個自主修復的能力,如果App啟動多次的話,那就可以考慮清空所有的快取資料,將App重置到安裝的狀態,到了最嚴重的等級呢,可以阻塞主執行緒,此時一定要等App熱修復成功之后才允許用戶進入,
三、有做過相關的記憶體優化嗎?
程式員:
有做過,目前的專案記憶體優化還是挺多的,要不我先說一下優化記憶體有什么好處吧?咱們不能盲目的去優化!
有的時候對于自己熟悉的領域,一定要主動出擊,自己主導這場面試,
面試官:
可以,
Ps:這里大多數面試官會同意你的請求,除非遇見裝B的,
程式員:
好處:
- 減少 OOM ,可以提高程式的穩定性,
- 減少卡頓,提高應用流暢性,
- 減少記憶體占用,提高應用后臺存活性,
- 減少程式例外,降低應用 Crash 率, 提高穩定性,
那么我基于這四點,我的程式做了如下優化:
-
1.減少 OOM
在應用開發階段我比較喜歡用 LeakCanary 這款性能檢測工具,好處是它能實時的告訴我具體哪個類發現了記憶體泄漏(如果你對 LeakCanary 的原理了解的話,可以說一說它是怎么檢測的),
還有我們要明白為什么應用程式會發送 OOM ,又該怎么去避免它?
發生 OOM 的場景是當申請 1M 的記憶體空間時,如果你要往該記憶體空間存入 2M 的資料,那么此時就會發生 OOM,
在應用程式中我們不僅要避免直接導致 OOM 的場景還要避免間接導致 OOM 的場景,間接的話也就是要避免記憶體泄漏的場景,
記憶體泄漏的場景是這個物件不再使用時,應用完整的執行最后的生命周期,但是由于某些原因,物件雖然已經不再使用,仍然會在記憶體中存在而導致 GC 不會去回收它,這就意味著發生了記憶體泄漏,(這里可以介紹下 GC 回識訓制,回收演算法,知識點盡量往外擴展而不脫離本題)
最后在說一下在實際開發中避免記憶體泄漏的場景:
-
資源型物件未關閉: Cursor,File
-
注冊物件未銷毀: 廣播,回呼監聽
-
類的靜態變數持有大資料物件
-
非靜態內部類的靜態實體
-
Handler 臨時性記憶體泄漏: 使用靜態 + 弱參考,退出即銷毀
-
容器中的物件沒清理造成的記憶體泄漏
-
WebView: 使用單獨行程
其實這些都是基礎,把它記下就行了,記得多了在實際開發中就有印象了,
-
-
2.減少卡頓
怎么減少卡頓? 那么我們可以從 2 個原理方面來探討卡頓的根本原因,第一個原理方面是繪制原理,另一個就是重繪原理,
- 繪制原理:

- 繪制原理:
- 重繪原理:
View 的 requestLayout 和 ViewRootImpl##setView 最終都會呼叫 ViewRootImpl 的 requestLayout 方法,然后通過 scheduleTraversals 方法向 Choreographer 提交一個繪制任務,然后再通過 DisplayEventReceiver 向底層請求 vsync 垂直同步信號,當 vsync 信號來的時候,會通過 JNI 回呼回來,在通過 Handler 往訊息佇列 post 一個異步任務,最終是 ViewRootImpl 去執行繪制任務,最后呼叫 performTraversals 方法,完成繪制,
詳細流程可以參考下面流程圖:

卡頓的根本原因:
從重繪原理來看卡頓的根本原理是有兩個地方會造成掉幀:
一個是主執行緒有其它耗時操作,導致doFrame 沒有機會在 vsync 信號發出之后 16 毫秒內呼叫;
還有一個就是當前doFrame方法耗時,繪制太久,下一個 vsync 信號來的時候這一幀還沒畫完,造成掉幀,
既然我們知道了卡頓的根本原因,那么我們就可以監控卡頓,從而可以對卡頓優化做到極致,我們可以從下面四個方面來監控應用程式卡頓:
- 基于 Looper 的 Printer 分發訊息的時間差值來判斷是否卡頓,
//1. 開啟監聽
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
//2. 只要分發訊息那么就會在之前和之后分別列印訊息
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; ...
for (;;) {
Message msg = queue.next(); // might block
...
//分發之前列印
final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); }
...
try {
//分發訊息
msg.target.dispatchMessage(msg);
...
//分發之后列印
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
- 基于 Choreographer 回呼函式 postFrameCallback 來監控

-
基于開源框架 BlockCanary 來監控
-
基于開源框架 rabbit-client 來監控
怎么避免卡頓:
一定要避免在主執行緒中做耗時任務,總結一下 Android 中主執行緒的場景:
-
UI 生命周期的控制
-
系統事件的處理
-
訊息處理
-
界面布局
-
界面繪制
-
界面重繪
-
…
還有一個最重要的就是避免記憶體抖動,不要在短時間內頻繁的記憶體分配和釋放,
基于這幾點去說卡頓肯定是沒有問題的,
-
3.減少記憶體占用
可以從如下幾個方面去展開說明:
-
AutoBoxing(自動裝箱): 能用小的堅決不用大的,
-
記憶體復用
-
使用最優的資料型別
-
列舉型別: 使用注解列舉限制替換 Enum
-
圖片記憶體優化(這里可以從 Glide 等開源框架去說下它們是怎么設計的)
- 選擇合適的位圖格式
- bitmap 記憶體復用,壓縮
- 圖片的多級快取
-
基本資料型別如果不用修改的建議全部寫成 static final,因為 它不需要進行初始化作業,直接打包到 dex 就可以直接使用,并不會在 類 中進行申請記憶體
-
字串拼接別用 +=,使用 StringBuffer 或 StringBuilder
-
不要在 onMeause, onLayout, onDraw 中去重繪 UI
-
盡量使用 C++ 代碼轉換 YUV 格式,別用 Java 代碼轉換 RGB 等格式,真的很占用記憶體
-
-
4.減少程式例外
減少程式例外那么我們可以從穩定性和 Crash 來分別說明,
這個我們將在第四點會詳細的介紹程式的穩定性和 Crash ,
如果說出這些,再實際開發中舉例說明一下怎么解決的應該是沒有問題的,
歇會兒

四、App繪制優化
1、你在做布局優化的程序中用到了哪些工具?
我在做布局優化的程序中,用到了很多的工具,但是每一個工具都有它不同的使用場景,不同的場景應該使用不同的工具,下面我從線上和線下兩個角度來進行分析,
比如說,我要統計線上的FPS,我使用的就是Choreographer這個類,它具有以下特性:
- 1、能夠獲取整體的幀率,
- 2、能夠帶到線上使用,
- 3、它獲取的幀率幾乎是實時的,能夠滿足我們的需求,
同時,在線下,如果要去優化布局加載帶來的時間消耗,那就需要檢測每一個布局的耗時,對此我使用的是AOP的方式,它沒有侵入性,同時也不需要別的開發同學進行接入,就可以方便地獲取每一個布局加載的耗時,如果還要更細粒度地去檢測每一個控制元件的加載耗時,那么就需要使用LayoutInflaterCompat.setFactory2這個方法去進行Hook,
此外,我還使用了LayoutInspector和Systrace這兩個工具,Systrace可以很方便地看到每幀的具體耗時以及這一幀在布局當中它真正做了什么,而LayoutInspector可以很方便地看到每一個界面的布局層級,幫助我們對層級進行優化,
2、布局為什么會導致卡頓,你又是如何優化的?
分析完布局的加載流程之后,我們發現有如下四點可能會導致布局卡頓:
- 1、首先,系統會將我們的Xml檔案通過IO的方式映射的方式加載到我們的記憶體當中,而IO的程序可能會導致卡頓,
- 2、其次,布局加載的程序是一個反射的程序,而反射的程序也會可能會導致卡頓,
- 3、同時,這個布局的層級如果比較深,那么進行布局遍歷的程序就會比較耗時,
- 4、最后,不合理的嵌套RelativeLayout布局也會導致重繪的次數過多,
對此,我們的優化方式有如下幾種:
- 1、針對布局加載Xml檔案的優化,我們使用了異步Inflate的方式,即AsyncLayoutInflater,它的核心原理是在子執行緒中對我們的Layout進行加載,而加載完成之后會將View通過Handler發送到主執行緒來使用,所以不會阻塞我們的主執行緒,加載的時間全部是在異步執行緒中進行消耗的,而這僅僅是一個從側面緩解的思路,
- 2、后面,我們發現了一個從根源解決上述痛點的方式,即使用X2C框架,它的一個核心原理就是在開發程序我們還是使用的XML進行撰寫布局,但是在編譯的時候它會使用APT的方式將XML布局轉換為Java的方式進行布局,通過這樣的方式去寫布局,它有以下優點:1、它省去了使用IO的方式去加載XML布局的耗時程序,2、它是采用Java代碼直接new的方式去創建控制元件物件,所以它也沒有反射帶來的性能損耗,這樣就從根本上解決了布局加載程序中帶來的問題,
- 3、然后,我們可以使用ConstraintLayout去減少我們界面布局的嵌套層級,如果原始布局層級越深,它能減少的層級就越多,而使用它也能避免嵌套RelativeLayout布局導致的重繪次數過多,
- 4、最后,我們可以使用AspectJ框架(即AOP)和LayoutInflaterCompat.setFactory2的方式分別去建立線下全域的布局加載速度和控制元件加載速度的監控體系,
3、做完布局優化有哪些成果產出?
- 1、首先,我們建立了一個體系化的監控手段,這里的體系還指的是線上加線下的一個綜合方案,針對線下,我們使用AOP或者ARTHook,可以很方便地獲取到每一個布局的加載耗時以及每一個控制元件的加載耗時,針對線上,我們通過Choreographer.getInstance().postFrameCallback的方式收集到了FPS,這樣我們可以知道用戶在哪些界面出現了丟幀的情況,
- 2、然后,對于布局監控方面,我們設立了FPS、布局加載時間、布局層級等一系列指標,
- 3、最后,在每一個版本上線之前,我們都會對我們的核心路徑進行一次Review,確保我們的FPS、布局加載時間、布局層級等達到一個合理的狀態,
4、你是怎么做卡頓優化的?
從專案的初期到壯大期,最后再到成熟期,每一個階段都針對卡頓優化做了不同的處理,各個階段所做的事情如下所示:
- 1、系統工具定位、解決
- 2、自動化卡頓方案及優化
- 3、線上監控及線下監測工具的建設
我做卡頓優化也是經歷了一些階段,最初我們的專案當中的一些模塊出現了卡頓之后,我是通過系統工具進行了定位,我使用了Systrace,然后看了卡頓周期內的CPU狀況,同時結合代碼,對這個模塊進行了重構,將部分代碼進行了異步和延遲,在專案初期就是這樣解決了問題,但是呢,隨著我們專案的擴大,線下卡頓的問題也越來越多,同時,在線上,也有卡頓的反饋,但是線上的反饋卡頓,我們在線下難以復現,于是我們開始尋找自動化的卡頓監測方案,其思路是來自于Android的訊息處理機制,主執行緒執行任何代碼都會回到Looper.loop方法當中,而這個方法中有一個mLogging物件,它會在每個message的執行前后都會被呼叫,我們就是利用這個前后處理的時機來做到的自動化監測方案的,同時,在這個階段,我們也完善了線上ANR的上報,我們采取的方式就是監控ANR的資訊,同時結合了ANR-WatchDog,作為高版本沒有檔案權限的一個補充方案,在做完這個卡頓檢測方案之后呢,我們還做了線上監控及線下檢測工具的建設,最終實作了一整套完善,多維度的解決方案,
5、你是怎么樣自動化的獲取卡頓資訊?
我們的思路是來自于Android的訊息處理機制,主執行緒執行任何代碼它都會走到Looper.loop方法當中,而這個函式當中有一個mLogging物件,它會在每個message處理前后都會被呼叫,而主執行緒發生了卡頓,那就一定會在dispatchMessage方法中執行了耗時的代碼,那我們在這個message執行之前呢,我們可以在子執行緒當中去postDelayed一個任務,這個Delayed的時間就是我們設定的閾值,如果主執行緒的messaege在這個閾值之內完成了,那就取消掉這個子執行緒當中的任務,如果主執行緒的message在閾值之內沒有被完成,那子執行緒當中的任務就會被執行,它會獲取到當前主執行緒執行的一個堆疊,那我們就可以知道哪里發生了卡頓,
經過實踐,我們發現這種方案獲取的堆疊資訊它不一定是準確的,因為獲取到的堆疊資訊它很可能是主執行緒最終執行的一個位置,而真正耗時的地方其實已經執行完成了,于是呢,我們就對這個方案做了一些優化,我們采取了高頻采集的方案,也就是在一個周期內我們會多次采集主執行緒的堆疊資訊,如果發生了卡頓,那我們就將這些卡頓資訊壓縮之后上報給APM后臺,然后找出重復的堆疊資訊,這些重復發生的堆疊大概率就是卡頓發生的一個位置,這樣就提高了獲取卡頓資訊的一個準確性,
6、卡頓的一整套解決方案是怎么做的?
首先,針對卡頓,我們采用了線上、線下工具相結合的方式,線下工具我們冊中醫藥盡可能早地去暴露問題,而針對于線上工具呢,我們側重于監控的全面性、自動化以及例外感知的靈敏度,
同時呢,卡頓問題還有很多的難題,比如說有的代碼呢,它不到你卡頓的一個閾值,但是執行過多,或者它錯誤地執行了很多次,它也會導致用戶感官上的一個卡頓,所以我們在線下通過AOP的方式對常見的耗時代碼進行了Hook,然后對一段時間內獲取到的資料進行分析,我們就可以知道這些耗時的代碼發生的時機和次數以及耗時情況,然后,看它是不是滿足我們的一個預期,不滿足預期的話,我們就可以直接到線下進行修改,同時,卡頓監控它還有很多容易被忽略的一個盲區,比如說生命周期的一個間隔,那對于這種特定的問題呢,我們就采用了編譯時注解的方式修改了專案當中所有Handler的父類,對于其中的兩個方法進行了監控,我們就可以知道主執行緒message的執行時間以及它們的呼叫堆疊,
對于線上卡頓,我們除了計算App的卡頓率、ANR率等常規指標之外呢,我們還計算了頁面的秒開率、生命周期的執行時間等等,而且,在卡頓發生的時刻,我們也盡可能多地保存下來了當前的一個場景資訊,這為我們之后解決或者復現這個卡頓留下了依據,
五、說說你在專案中網路優化?
程式員:
有,這一點其實可以通過 OKHTTP 連接池和 Http 快取來說一下(當然這里不會再展開分析 OKHTTP 原始碼了)
面試官:
那你具體說一下吧
程式員

移動端獲取網路資料優化的幾個點
-
1、連接復用:節省連接建立時間,如開啟 keep-alive,于Android來說默認情況下HttpURLConnection和HttpClient都開啟了keep-alive,只是2.2之前HttpURLConnection存在影響連接池的Bug,
-
2、請求合并:即將多個請求合并為一個進行請求,比較常見的就是網頁中的CSS Image Sprites,如果某個頁面內請求過多,也可以考慮做一定的請求合并,
-
3、減少請求資料的大小:對于post請求,body可以做gzip壓縮的,header也可以做資料壓縮(不過只支持http 2.0), 回傳資料的body也可以做gzip壓縮,body資料體積可以縮小到原來的30%左右(也可以考慮壓碩訓傳的json資料的key資料的體積,尤其是針對回傳資料格式變化不大的情況,支付寶聊天回傳的資料用到了),
-
4、根據用戶的當前的網路質量來判斷下載什么質量的圖片(電商用的比較多),
-
5、使用HttpDNS優化DNS:DNS存在決議慢和DNS劫持等問題,DNS 不僅支持 UDP,它還支持 TCP,但是大部分標準的 DNS 都是基于 UDP 與 DNS 服務器的 53 埠進行互動,HTTPDNS 則不同,顧名思義它是利用 HTTP 協議與 DNS 服務器的 80 埠進行互動,不走傳統的 DNS 決議,從而繞過運營商的 LocalDNS 服務器,有效的防止了域名劫持,提高域名決議的效率,

說了這些之后,再說一下你當前使用網路框架它們做了哪些優化比如 OKHTTP(Socket 連接池、Http快取、責任鏈)、Retrofit(動態代理),說了這些一般這關也算是過了,
六、在專案中有用過哪些存盤方式? 對它們的性能有過優化嗎?
程式員:
主要用過 sp,File,SQLite 存盤方式,其中對 sp 和 sqlite 做了優化,
面試官:
那你說說都做了哪些優化?
程式員:

這一塊如果你使用過其它第三方的資料庫,可以說說它們的原理和它們存取的方式,
七、用過自定義 View 吧?對它做過什么優化?
程式員:
有做過,比如重復繪制,還有大圖長圖有過優化,
面試官:
那具體說一說
程式員:

最后也是結合真實場景具體說一個,
如何優化自定義View?
為了加速你的view,對于頻繁呼叫的方法,需要盡量減少不必要的代碼,先從onDraw開始,需要特別注意不應該在這里做記憶體分配的事情,因為它會導致GC,從而導致卡頓,在初始化或者影片間隙期間做分配記憶體的動作,不要在影片正在執行的時候做記憶體分配的事情,
你還需要盡可能的減少onDraw被呼叫的次數,大多數時候導致onDraw都是因為呼叫了invalidate().因此請盡量減少呼叫invaildate()的次數,如果可能的話,盡量呼叫含有4個引數的invalidate()方法而不是沒有引數的invalidate(),沒有引數的invalidate會強制重繪整個view,
另外一個非常耗時的操作是請求layout,任何時候執行requestLayout(),會使得Android UI系統去遍歷整個View的層級來計算出每一個view的大小,如果找到有沖突的值,它會需要重新計算好幾次,另外需要盡量保持View的層級是扁平化的,這樣對提高效率很有幫助,
如果你有一個復雜的UI,你應該考慮寫一個自定義的ViewGroup來執行他的layout操作,與內置的view不同,自定義的view可以使得程式僅僅測量這一部分,這避免了遍歷整個view的層級結構來計算大小,
八、有做過日志優化嗎?
程式員:
有優化,在之前沒有考慮任何性能的情況下,我是直接有 log 就寫入檔案,盡管我開了執行緒池去寫檔案,只要軟體在運行那么就會頻繁的使 CPU 進行作業,這也間接的導致了耗電,
傳統日志列印有兩個性能問題,一個是反復操作檔案描述符表,一個是反復進入內核態,所以需要使用mmap的方式去直接讀寫記憶體,
面試官:
那你具體說一下,最后怎么解決這個問題的?
程式員:

展開上面這些點說明之后,面試官一般不會為難你,
九、做過 APK 體積相關的優化嗎?
程式員:
有過優化,在沒有優化之前專案的包體積大小是 80M,優化之后是 50M.
面試官:
說一說是怎么優化的
程式員:

基于這幾點優化方案,一般都能解決 APK 體積問題,最后再把自己專案 APK 體積優化步驟結合上面點說一下就行,
十、為什么WebView加載會慢呢?
這是因為在客戶端中,加載H5頁面之前,需要先初始化WebView,在WebView完全初始化完成之前,后續的界面加載程序都是被阻塞的,
優化手段圍繞著以下兩個點進行:
- 預加載WebView,
- 加載WebView的同時,請求H5頁面資料,
因此常見的方法是:
- 全域WebView,
- 客戶端代理頁面請求,WebView初始化完成后向客戶端請求資料,
- asset存放離線包,
除此之外還有一些其他的優化手段:
- 腳本執行慢,可以讓腳本最后運行,不阻塞頁面決議,
- DNS鏈接慢,可以讓客戶端復用使用的域名與鏈接,
- React框架代碼執行慢,可以將這部分代碼拆分出來,提前進行決議,
其實性能優化點都是息息相關的,比如卡頓會涉及記憶體、顯示,啟動也會涉及 APK dex 的影響,所以說性能優化不僅僅是單方面的優化,一定要掌握最基本的優化方案,才能更加深入探討性能原理問題,
Android性能優化必知必會PDF電子書
由于篇幅原因,如有需要以下完整學習筆記PDF,可以點贊+評論支持下打工人老弟,點擊這里免費自取!
第一章:設計思想與代碼質量優化
1、設計思想六大原則
2、三大設計模式
3、資料結構
4、演算法


第二章:程式性能優化
1、啟動速度和執行效率優化
2、布局檢測與優化
3、記憶體優化
4、耗電優化
5、網路傳輸與資料存盤優化
6、APK大小優化
7、螢屏適配
8、OOM問題原理決議
9、ANR問題決議
10、Crash監控方案


第三章:開發效率優化
1、分布式版本控制系統Git
2、自動化構建系統Gradle


第四章:一線大廠在APP性能優化的實踐
1、啟動優化
支付寶APP:通過安裝包重排布局優化Android端啟動性能
抖音BoostMultiDex 優化實踐:Android 低版本上APP 首次啟動時間減少80%
手機淘寶在APP啟動優化的思考和實踐
2、流暢度
Android中的卡頓丟幀原因分析
應用寶、訊飛輸入法無障礙服務導致的整機卡頓分析
今日頭條圖文詳情頁秒開實踐
3、APK大小優化
抖音在APK包大小資源優化方面的實踐
4、布局優化
優酷APP回應式布局技術全決議
5、網路優化
手機淘寶在網路的鏈路優化
百度APP在網路深度優化的實踐
6、手機淘寶雙十一億萬用戶瀏覽APP性能優化專案揭秘
7、高德APP全鏈路原始碼依賴分析
8、徹底干掉OOM的實戰經驗分享
9、微信 Android終端記憶體優化實踐


由于篇幅原因,如有需要以上完整學習筆記PDF,可以點贊+評論支持下打工人老弟,點擊這里免費自取!
聽說一鍵三連的粉絲都面試成功了?如果本篇博客對你有幫助,請支持下小編哦

Android高級面試精選題、架構師進階實戰檔案傳送門:我的GitHub
整理不易,覺得有幫助的朋友可以幫忙點贊分享支持一下小編~
你的支持,我的動力;
文末
我一直覺得技術面試不是考試,考前背背題,發給你一張考卷,答完交卷等通知,
首先,技術面試是一個 認識自己 的程序,知道自己和外面世界的差距,
更重要的是,技術面試是一個雙向了解的程序,要讓對方發現你的閃光點,同時也要 試圖去找到對方的閃光點,因為他以后可能就是你的同事或者領導,所以,面試官問你有什么問題的時候,不要說沒有了,要去試圖了解他的作業內容、了解這個團隊的氛圍,
找作業無非就是看三點:和什么人、做什么事、給多少錢,要給這三者在自己的心里劃分一個比例,
最后,祝愿大家在這并不友好的環境下都能找到自己心儀的歸宿!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/262587.html
標籤:其他
