天天看點

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

作者:Focusing
注:因為實際開發與參考答案會有所不同,再者怕誤導大家,是以這些面試題答案還是自己去了解!面試官會針對履歷中提到的知識點由淺入深提問,是以不要背答案,多了解。

Handler

1、談談消息機制Handler作用 ?有哪些要素 ?流程是怎樣的 ?

參考回答:

1、負責跨線程通信,這是因為在主線程不能做耗時操作,而子線程不能更新UI,是以當子線程中進行耗時操作後需要更新UI時,通過Handler将有關UI的操作切換到主線程中執行。

2、具體分為四大要素

Message(消息):需要被傳遞的消息,消息分為硬體産生的消息(如按鈕、觸摸)和軟體生成的消息。

MessageQueue(消息隊列):負責消息的存儲與管理,負責管理由 Handler發送過來的Message。讀取會自動删除消息,單連結清單維護,插入和删除上有優勢。在其next()方法中會無限循環,不斷判斷是否有消息,有就傳回這條消息并移除。

Handler(消息處理器):負責Message的發送及處理。主要向消息池發送各種消息事件(Handler.sendMessage())和處理相應消息事件(Handler.handleMessage()),按照先進先出執行,内部使用的是單連結清單的結構。

Looper(消息池):負責關聯線程以及消息的分發,在該線程下從 MessageQueue擷取 Message,分發給Handler,Looper建立的時候會建立一個 MessageQueue,調用loop()方法的時候消息循環開始,其中會不斷調用messageQueue的next()方法,當有消息就處理,否則阻塞在messageQueue的next()方法中。當Looper的quit()被調用的時候會調用messageQueue的quit(),此時next()會傳回null,然後loop()方法也就跟着退出。

3、具體流程如下

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

1、在主線程建立的時候會建立一個Looper,同時也會在在Looper内部建立一個消息隊列。而在創鍵Handler的時候取出目前線程的Looper,并通過該Looper對象獲得消息隊列,然後Handler在子線程中通過MessageQueue.enqueueMessage在消息隊列中添加一條Message。

2、通過Looper.loop() 開啟消息循環不斷輪詢調用 MessageQueue.next(),取得對應的Message并且通過Handler.dispatchMessage傳遞給Handler,最終調用Handler.handlerMessage處理消息。

2、一個線程能否建立多個Handler,Handler跟Looper之間的對應關系 ?

參考回答:

1、一個Thread隻能有一個Looper,一個MessageQueen,可以有多個Handler

2、以一個線程為基準,他們的數量級關系是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)

3、軟引用跟弱引用的差別

參考回答:

1、軟引用(SoftReference):如果一個對象隻具有軟引用,則記憶體空間充足時,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些對象的記憶體。隻要垃圾回收器沒有回收它,該對象就可以一直被程式使用。

2、弱引用(WeakReference):如果一個對象隻具有弱引用,那麼在垃圾回收器線程掃描的過程中,一旦發現了隻具有弱引用的對象,不管目前記憶體空間足夠與否,都會回收它的記憶體。

3、兩者之間根本差別在于:隻具有弱引用的對象擁有更短暫的生命周期,可能随時被回收。而隻具有軟引用的對象隻有當記憶體不夠的時候才被回收,在記憶體足夠的時候,通常不被回收。

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

4、Handler 引起的記憶體洩露原因以及最佳解決方案

參考回答:

1、洩露原因:

Handler 允許我們發送延時消息,如果在延時期間使用者關閉了 Activity,那麼該 Activity 會洩露。 這個洩露是因為 Message 會持有 Handler,而又因為 Java 的特性,内部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導緻 Activity 洩露。

解決方案: 将 Handler 定義成靜态的内部類,在内部持有Activity的弱引用,并在Acitivity的onDestroy()中調用handler.removeCallbacksAndMessages(null)及時移除所有消息。

5、為什麼系統不建議在子線程通路UI?

參考回答:

1、Android的UI控件不是線程安全的,如果在多線程中并發通路可能會導緻UI控件處于不可預期的狀态

2、這時你可能會問為何系統不對UI控件的通路加上鎖機制呢?因為

加鎖機制會讓UI通路邏輯變的複雜

加鎖機制會降低UI的通路效率,因為加鎖會阻塞某些線程的執行

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

6、Looper死循環為什麼不會導緻應用卡死?

參考回答:

1、主線程的主要方法就是消息循環,一旦退出消息循環,那麼你的應用也就退出了,Looer.loop()方法可能會引起主線程的阻塞,但隻要它的消息循環沒有被阻塞,能一直處理事件就不會産生ANR異常。

2、造成ANR的不是主線程阻塞,而是主線程的Looper消息處理過程發生了任務阻塞,無法響應手勢操作,不能及時重新整理UI。

3、阻塞與程式無響應沒有必然關系,雖然主線程在沒有消息可處理的時候是阻塞的,但是隻要保證有消息的時候能夠立刻處理,程式是不會無響應的。

7、使用Handler的postDealy後消息隊列會有什麼變化?

參考回答:

1、如果隊列中隻有這個消息,那麼消息不會被發送,而是計算到時喚醒的時間,先将Looper阻塞,到時間就喚醒它。但如果此時要加入新消息,該消息隊列的對頭跟delay時間相比更長,則插入到頭部,按照觸發時間進行排序,隊頭的時間最小、隊尾的時間最大

8、可以在子線程直接new一個Handler嗎?怎麼做?

參考回答:

1、不可以,因為在主線程中,Activity内部包含一個Looper對象,它會自動管理Looper,處理子線程中發送過來的消息。而對于子線程而言,沒有任何對象幫助我們維護Looper對象,是以需要我們自己手動維護。是以要在子線程開啟Handler要先建立Looper,并開啟Looper循環

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

9、Message可以如何建立?哪種效果更好,為什麼?

參考回答:

可以通過三種方法建立:

1、直接生成執行個體Message m = new Message

2、通過Message m = Message.obtain

3、通過Message m = mHandler.obtainMessage()

後兩者效果更好,因為Android預設的消息池中消息數量是10,而後兩者是直接在消息池中取出一個Message執行個體,這樣做就可以避免多生成Message執行個體。

線程

1、線程池的好處? 四種線程池的使用場景,線程池的幾個參數的了解?

參考回答:

1、使用線程池的好處是減少在建立和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統建立大量同類線程而導緻消耗完記憶體或則“過度切換”的問題,歸納總結就是

重用存在的線程,減少對象建立、消亡的開銷,性能佳。

可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競争,避免堵塞。

提供定時執行、定期執行、單線程、并發數控制等功能。

2、Android中的線程池都是直接或間接通過配置ThreadPoolExecutor來實作不同特性的線程池.Android中最常見的類具有不同特性的線程池分别為:

newCachedThreadPool:隻有非核心線程,最大線程數非常大,所有線程都活動時會為新任務建立新線程,否則會利用空閑線程 ( 60s空閑時間,過了就會被回收,是以線程池中有0個線程的可能 )來處理任務.

優點:任何任務都會被立即執行(任務隊列SynchronousQuue相當于一個空集合);比較适合執行大量的耗時較少的任務.

newFixedThreadPool:隻有核心線程,并且數量固定的,所有線程都活動時,因為隊列沒有限制大小,新任務會等待執行,當線程池空閑時不會釋放工作線程,還會占用一定的系統資源。 優點:更快的響應外界請求

newScheduledThreadPool:核心線程數固定,非核心線程(閑着沒活幹會被立即回收數)沒有限制.

優點:執行定時任務以及有固定周期的重複任務

newSingleThreadExecutor:隻有一個核心線程,確定所有的任務都在同一線程中按序完成 優點:不需要處理線程同步的問題

通過源碼可以了解到上面的四種線程池實際上還是利用 ThreadPoolExecutor 類實作的

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

2、Android中還了解哪些友善線程切換的類?

參考回答:

1、AsyncTask:底層封裝了線程池和Handler,便于執行背景任務以及在子線程中進行UI操作。

2、HandlerThread:一種具有消息循環的線程,其内部可使用Handler。

3、IntentService:是一種異步、會自動停止的服務,内部采用HandlerThread。

3、講講AsyncTask的原理

參考回答:

1、AsyncTask中有兩個線程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler),其中線程池SerialExecutor用于任務的排隊,而線程池THREAD_POOL_EXECUTOR用于真正地執行任務,InternalHandler用于将執行環境從線程池切換到主線程。

2、sHandler是一個靜态的Handler對象,為了能夠将執行環境切換到主線程,這就要求sHandler這個對象必須在主線程建立。由于靜态成員會在加載類的時候進行初始化,是以這就變相要求AsyncTask的類必須在主線程中加載,否則同一個程序中的AsyncTask都将無法正常工作。

4、IntentService有什麼用 ?

參考回答:

1、IntentService可用于執行背景耗時的任務,當任務執行完成後會自動停止,同時由于IntentService是服務的原因,不同于普通Service,IntentService可自動建立子線程來執行任務,這導緻它的優先級比單純的線程要高,不容易被系統殺死,是以IntentService比較适合執行一些高優先級的背景任務。

5、直接在Activity中建立一個thread跟在service中建立一個thread之間的差別?

參考回答:

1、在Activity中被建立:該Thread的就是為這個Activity服務的,完成這個特定的Activity交代的任務,主動通知該Activity一些消息和事件,Activity銷毀後,該Thread也沒有存活的意義了。

2、在Service中被建立:這是保證最長生命周期的Thread的唯一方式,隻要整個Service不退出,Thread就可以一直在背景執行,一般在Service的onCreate()中建立,在onDestroy()中銷毀。是以,在Service中建立的Thread,适合長期執行一些獨立于APP的背景任務,比較常見的就是:在Service中保持與伺服器端的長連接配接。

6、ThreadPoolExecutor的工作政策 ?

參考回答:ThreadPoolExecutor執行任務時會遵循如下規則

1、如果線程池中的線程數量未達到核心線程的數量,那麼會直接啟動一個核心線程來執行任務。

2、如果線程池中的線程數量已經達到或則超過核心線程的數量,那麼任務會被插入任務隊列中排隊等待執行。

3、如果在第2點無法将任務插入到任務隊列中,這往往是由于任務隊列已滿,這個時候如果線上程數量未達到線程池規定的最大值,那麼會立刻啟動一個非核心線程來執行任務。

4、如果第3點中線程數量已經達到線程池規定的最大值,那麼就拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution方法來通知調用者。

7、Handler、Thread和HandlerThread的差别?

參考回答:

1、Handler:在android中負責發送和處理消息,通過它可以實作其他支線線程與主線程之間的消息通訊。

2、Thread:Java程序中執行運算的最小機關,亦即執行處理機排程的基本機關。某一程序中一路單獨運作的程式。

3、HandlerThread:一個繼承自Thread的類HandlerThread,Android中沒有對Java中的Thread進行任何封裝,而是提供了一個繼承自Thread的類HandlerThread類,這個類對Java的Thread做了很多便利的封裝。HandlerThread繼承于Thread,是以它本質就是個Thread。與普通Thread的差别就在于,它在内部直接實作了Looper的實作,這是Handler消息機制必不可少的。有了自己的looper,可以讓我們在自己的線程中分發和處理消息。如果不用HandlerThread的話,需要手動去調用Looper.prepare()和Looper.loop()這些方法。

8、ThreadLocal的原理

參考回答:

1、ThreadLocal是一個關于建立線程局部變量的類。使用場景如下所示:

實作單個線程單例以及單個線程上下文資訊存儲,比如交易id等。

實作線程安全,非線程安全的對象使用ThreadLocal之後就會變得線程安全,因為每個線程都會有一個對應的執行個體。 承載一些線程相關的資料,避免在方法中來回傳遞參數。

2、當需要使用多線程時,有個變量恰巧不需要共享,此時就不必使用synchronized這麼麻煩的關鍵字來鎖住,每個線程都相當于在堆記憶體中開辟一個空間,線程中帶有對共享變量的緩沖區,通過緩沖區将堆記憶體中的共享變量進行讀取和操作,ThreadLocal相當于線程内的記憶體,一個局部變量。每次可以對線程自身的資料讀取和操作,并不需要通過緩沖區與 主記憶體中的變量進行互動。并不會像synchronized那樣修改主記憶體的資料,再将主記憶體的資料複制到線程内的工作記憶體。ThreadLocal可以讓線程獨占資源,存儲于線程内部,避免線程堵塞造成CPU吞吐下降。

3、在每個Thread中包含一個ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的對象,value是獨享資料。

9、多線程是否一定會高效(優缺點)

參考回答:

1、多線程的優點:

友善高效的記憶體共享 - 多程序下記憶體共享比較不便,且會抵消掉多程序程式設計的好處

較輕的上下文切換開銷 - 不用切換位址空間,不用更改CR3寄存器,不用清空TLB

線程上的任務執行完後自動銷毀

2、多線程的缺點:

開啟線程需要占用一定的記憶體空間(預設情況下,每一個線程都占512KB)

如果開啟大量的線程,會占用大量的記憶體空間,降低程式的性能

線程越多,cpu在調用線程上的開銷就越大

程式設計更加複雜,比如線程間的通信、多線程的資料共享

綜上得出,多線程不一定能提高效率,在記憶體空間緊張的情況下反而是一種負擔,是以在日常開發中,應盡量

不要頻繁建立,銷毀線程,使用線程池

減少線程間同步和通信(最為關鍵)

避免需要頻繁共享寫的資料

合理安排共享資料結構,避免僞共享(false sharing)

使用非阻塞資料結構/算法

避免可能産生可伸縮性問題的系統調用(比如mmap)

避免産生大量缺頁異常,盡量使用Huge Page

可以的話使用使用者态輕量級線程代替核心線程

10、多線程中,讓你做一個單例,你會怎麼做

參考回答:

1、多線程中建立單例模式考慮的因素有很多,比如線程安全 -延遲加載-代碼安全:如防止序列化攻擊,防止反射攻擊(防止反射進行私有方法調用) -性能因素

2、實作方法有多種,餓漢,懶漢(線程安全,線程非安全),雙重檢查(DCL),内部類,以及枚舉

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

11、除了notify還有什麼方式可以喚醒線程

參考回答:

1、當一個擁有Object鎖的線程調用 wait()方法時,就會使目前線程加入object.wait 等待隊列中,并且釋放目前占用的Object鎖,這樣其他線程就有機會擷取這個Object鎖,獲得Object鎖的線程調用notify()方法,就能在Object.wait 等待隊列中随機喚醒一個線程(該喚醒是随機的與加入的順序無關,優先級高的被喚醒機率會高)

2、如果調用notifyAll()方法就喚醒全部的線程。注意:調用notify()方法後并不會立即釋放object鎖,會等待該線程執行完畢後釋放Object鎖。

12、什麼是ANR ? 什麼情況會出現ANR ?如何避免 ? 在不看代碼的情況下如何快速定位出現ANR問題所在 ?

參考回答:

1、ANR(Application Not Responding,應用無響應):當操作在一段時間内系統無法處理時,會在系統層面會彈出ANR對話框

2、産生ANR可能是因為5s内無響應使用者輸入事件、10s内未結束BroadcastReceiver、20s内未結束Service

3、想要避免ANR就不要在主線程做耗時操作,而是通過開子線程,方法比如繼承Thread或實作Runnable接口、使用AsyncTask、IntentService、HandlerThread等

Bitmap

1、Bitmap使用需要注意哪些問題 ?

參考回答:

1、要選擇合适的圖檔規格(bitmap類型):通常我們優化Bitmap時,當需要做性能優化或者防止OOM,我們通常會使用RGB_565,因為ALPHA_8隻有透明度,顯示一般圖檔沒有意義,Bitmap.Config.ARGB_4444顯示圖檔不清楚,Bitmap.Config.ARGB_8888占用記憶體最多。

ALPHA_8 每個像素占用1byte記憶體

ARGB_4444 每個像素占用2byte記憶體

ARGB_8888 每個像素占用4byte記憶體(預設)

RGB_565 每個像素占用2byte記憶體

2、降低采樣率:BitmapFactory.Options 參數inSampleSize的使用,先把options.inJustDecodeBounds設為true,隻是去讀取圖檔的大小,在拿到圖檔的大小之後和要顯示的大小做比較通過calculateInSampleSize()函數計算inSampleSize的具體值,得到值之後。options.inJustDecodeBounds設為false讀圖檔資源。

3、複用記憶體:即通過軟引用(記憶體不夠的時候才會回收掉),複用記憶體塊,不需要再重新給這個bitmap申請一塊新的記憶體,避免了一次記憶體的配置設定和回收,進而改善了運作效率。

4、使用recycle()方法及時回收記憶體。

5、壓縮圖檔。

2、Bitmap.recycle()會立即回收麼?什麼時候會回收?如果沒有地方使用這個Bitmap,為什麼垃圾回收不會直接回收?

參考回答:

1、通過源碼可以了解到,加載Bitmap到記憶體裡以後,是包含兩部分記憶體區域的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap對象是由Java部分配置設定的,不用的時候系統就會自動回收了

2、但是那個對應的C可用的記憶體區域,虛拟機是不能直接回收的,這個隻能調用底層的功能釋放。是以需要調用recycle()方法來釋放C部分的記憶體

3、bitmap.recycle()方法用于回收該Bitmap所占用的記憶體,接着将bitmap置空,最後使用System.gc()調用一下系統的垃圾回收器進行回收,調用System.gc()并不能保證立即開始進行回收過程,而隻是為了加快回收的到來。

3、一張Bitmap所占記憶體以及記憶體占用的計算

參考回答:

1、Bitamp 所占記憶體大小 = 寬度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一個像素所占的記憶體位元組大小

注:這裡inDensity表示目标圖檔的dpi(放在哪個資源檔案夾下),inTargetDensity表示目标螢幕的dpi,是以你可以發現inDensity和inTargetDensity會對Bitmap的寬高進行拉伸,進而改變Bitmap占用記憶體的大小。

2、在Bitmap裡有兩個擷取記憶體占用大小的方法。

getByteCount():API12 加入,代表存儲 Bitmap 的像素需要的最少記憶體。

getAllocationByteCount():API19 加入,代表在記憶體中為 Bitmap 配置設定的記憶體大小,代替了 getByteCount() 方法。

在不複用 Bitmap 時,getByteCount() 和 getAllocationByteCount 傳回的結果是一樣的。在通過複用 Bitmap 來解碼圖檔時,那麼 getByteCount() 表示新解碼圖檔占用記憶體的大 小,getAllocationByteCount() 表示被複用 Bitmap 真實占用的記憶體大小

4、Android中緩存更新政策 ?

參考回答:

1、Android的緩存更新政策沒有統一的标準,一般來說,緩存政策主要包含緩存的添加、擷取和删除這三類操作,但不管是記憶體緩存還是儲存設備緩存,它們的緩存容量是有限制的,是以删除一些舊緩存并添加新緩存,如何定義緩存的新舊這就是一種政策,不同的政策就對應着不同的緩存算法

2、比如可以簡單地根據檔案的最後修改時間來定義緩存的新舊,當緩存滿時就将最後修改時間較早的緩存移除,這就是一種緩存算法,但不算很完美

5、LRU的原理 ?

參考回答:

1、為減少流量消耗,可采用緩存政策。常用的緩存算法是LRU(Least Recently Used):當緩存滿時, 會優先淘汰那些近期最少使用的緩存對象。主要是兩種方式:

LruCache(記憶體緩存):LruCache類是一個線程安全的泛型類:内部采用一個LinkedHashMap以強引用的方式存儲外界的緩存對象,并提供get和put方法來完成緩存的擷取和添加操作,當緩存滿時會移除較早使用的緩存對象,再添加新的緩存對象。

DiskLruCache(磁盤緩存): 通過将緩存對象寫入檔案系統進而實作緩存效果

性能優化

1、圖檔的三級緩存中,圖檔加載到記憶體中,如果記憶體快爆了,會發生什麼?怎麼處理?

參考回答: 首先我們要清楚圖檔的三級緩存是如何的
吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後
如果記憶體足夠時不回收。記憶體不夠時就回收軟引用對象

2、記憶體中如果加載一張500*500的png高清圖檔.應該是占用多少的記憶體?

參考回答:

1、不考慮螢幕比的話:占用記憶體=500 * 500 * 4 = 1000000B ≈ 0.95MB

2、考慮螢幕比的的話:占用記憶體= 寬度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一個像素所占的記憶體位元組大小

3、inDensity表示目标圖檔的dpi(放在哪個資源檔案夾下),inTargetDensity表示目标螢幕的dpi

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

3、WebView的性能優化 ?

參考回答:

1、一個加載網頁的過程中,native、網絡、後端處理、CPU都會參與,各自都有必要的工作和依賴關系;讓他們互相并行處理而不是互相阻塞才可以讓網頁加載更快:

WebView初始化慢,可以在初始化同時先請求資料,讓後端和網絡不要閑着。

常用 JS 本地化及延遲加載,使用第三方浏覽核心

後端處理慢,可以讓伺服器分trunk輸出,在後端計算的同時前端也加載網絡靜态資源。

腳本執行慢,就讓腳本在最後運作,不阻塞頁面解析。

同時,合理的預加載、預緩存可以讓加載速度的瓶頸更小。

WebView初始化慢,就随時初始化好一個WebView待用。

DNS和連結慢,想辦法複用用戶端使用的域名和連結。

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

4、Bitmap如何處理大圖,如一張30M的大圖,如何預防OOM?

參考回答:避免OOM的問題就需要對大圖檔的加載進行管理,主要通過縮放來減小圖檔的記憶體占用。

1、BitmapFactory提供的加載圖檔的四類方法(decodeFile、decodeResource、decodeStream、decodeByteArray)都支援BitmapFactory.Options參數,通過inSampleSize參數就可以很友善地對一個圖檔進行采樣縮放

2、比如一張10241024的高清圖檔來說。那麼它占有的記憶體為102410244,即4MB,如果inSampleSize為2,那麼采樣後的圖檔占用記憶體隻有512512*4,即1MB(注意:根據最新的官方文檔指出,inSampleSize的取值應該總是為2的指數,即1、2、4、8等等,如果外界輸入不足為2的指數,系統也會預設選擇最接近2的指數代替,比如2)

3、綜合考慮。通過采樣率即可有效加載圖檔,流程如下

将BitmapFactory.Options的inJustDecodeBounds參數設為true并加載圖檔

從BitmapFactory.Options中取出圖檔的原始寬高資訊,它們對應outWidth和outHeight參數

根據采樣率的規則并結合目标View的所需大小計算出采樣率inSampleSize

将BitmapFactory.Options的inJustDecodeBounds參數設為false,重新加載圖檔

吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

5、記憶體回收機制與GC算法(各種算法的優缺點以及應用場景);GC原理時機以及GC對象

參考回答:

1、記憶體判定對象可回收有兩種機制:

引用計數算法:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。然而在主流的Java虛拟機裡未選用引用計數算法來管理記憶體,主要原因是它難以解決對象之間互相循環引用的問題,是以出現了另一種對象存活判定算法。

可達性分析法:通過一系列被稱為『GCRoots』的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊,當一個對象到GC Roots沒有任何引用鍊相連時,則證明此對象是不可用的。其中可作為GC Roots的對象:虛拟機棧中引用的對象,主要是指棧幀中的本地變量*、本地方法棧中Native方法引用的對象、方法區中類靜态屬性引用的對象、方法區中常量引用的對象

2、GC回收算法有以下四種:

分代收集算法:是目前商業虛拟機都采用的一種算法,根據對象存活周期的不同,将Java堆劃分為新生代和老年代,并根據各個年代的特點采用最适當的收集算法。 新生代:大批對象死去,隻有少量存活。使用『複制算法』,隻需複制少量存活對象即可。

複制算法:把可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用盡後,把還存活着的對象『複制』到另外一塊上面,再将這一塊記憶體空間一次清理掉。實作簡單,運作高效。在對象存活率較高時就要進行較多的複制操作,效率将會變低

3、老年代:對象存活率高。使用『标記—清理算法』或者『标記—整理算法』,隻需标記較少的回收對象即可。

标記-清除算法:首先『标記』出所有需要回收的對象,然後統一『清除』所有被标記的對象。标記和清除兩個過程的效率都不高,清除之後會産生大量不連續的記憶體碎片,空間碎片太多可能會導緻以後在程式運作過程中需要配置設定較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。

标記-整理算法:首先『标記』出所有需要回收的對象,然後進行『整理』,使得存活的對象都向一端移動,最後直接清理掉端邊界以外的記憶體。标記整理算法會将所有的存活對象移動到一端,并對不存活對象進行處理,是以其不會産生記憶體碎片

6、記憶體洩露和記憶體溢出的差別 ?AS有什麼工具可以檢測記憶體洩露

參考回答:

1、記憶體溢出(out of memory):是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是記憶體溢出。

2、記憶體洩露(memory leak):是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆積後果很嚴重,無論多少記憶體,遲早會被占光。memory leak會最終會導緻out of memory!

3、查找記憶體洩漏可以使用Android Studio 自帶的AndroidProfiler工具或MAT

7、性能優化,怎麼保證應用啟動不卡頓? 黑白屏怎麼處理?

參考回答:

1、應用啟動速度,取決于你在application裡面時候做了什麼事情,比如你內建了很多sdk,并且sdk的init操作都需要在主線程裡實作是以會有卡頓的感覺。在非必要的情況下可以把加載延後或則開啟子線程處理

2、另外,影響界面卡頓的兩大因素,分别是界面繪制和資料處理。

布局優化(使用include,merge标簽,複雜布局推薦使用ConstraintLayout等)

onCreate() 中不執行耗時操作 把頁面顯示的 View 細分一下,放在 AsyncTask 裡逐漸顯示,用 Handler 更好。這樣使用者的看到的就是有層次有步驟的一個個的 View 的展示,不會是先看到一個黑屏,然後一下顯示所有 View。最好做成動畫,效果更自然。

利用多線程的目的就是盡可能的減少 onCreate() 和 onReume() 的時間,使得使用者能盡快看到頁面,操作頁面。

減少主線程阻塞時間。

提高 Adapter 和 AdapterView 的效率。

3、黑白屏産生原因:當我們在啟動一個應用時,系統會去檢查是否已經存在這樣一個程序,如果不存在,系統的服務會先檢查startActivity中的intent的資訊,然後在去建立程序,最後啟動Acitivy,即冷啟動。而啟動出現白黑屏的問題,就是在這段時間内産生的。系統在繪制頁面加載布局之前,首先會初始化視窗(Window),而在進行這一步操作時,系統會根據我們設定的Theme來指定它的Theme 主題顔色,我們在Style中的設定就決定了顯示的是白屏還是黑屏。 windowIsTranslucent和windowNoTitle,将這兩個屬性都設定成true (會有明顯的卡頓體驗,不推薦)

如果啟動頁隻是是一張圖檔,那麼為啟動頁專一設定一個新的主題,設定主題的android:windowBackground屬性為啟動頁背景圖即可

使用layer-list制作一張圖檔launcher_layer.xml,将其設定為啟動頁專一主題的背景,并将其設定為啟動頁布局的背景。

8、強引用置為null,會不會被回收?

參考回答:

1、不會立即釋放對象占用的記憶體。 如果對象的引用被置為null,隻是斷開了目前線程棧幀中對該對象的引用關系,而 垃圾收集器是運作在背景的線程,隻有當使用者線程運作到安全點(safe point)或者安全區域才會掃描對象引用關系,掃描到對象沒有被引用則會标記對象,這時候仍然不會立即釋放該對象記憶體,因為有些對象是可恢複的(在 finalize方法中恢複引用 )。隻有确定了對象無法恢複引用的時候才會清除對象記憶體。

9、ListView跟RecyclerView的差別

參考回答:

1、動畫差別:

在RecyclerView中,内置有許多動畫API,例如:notifyItemChanged(), notifyDataInserted(), notifyItemMoved()等等;如果需要自定義動畫效果,可以通過實作(RecyclerView.ItemAnimator類)完成自定義動畫效果,然後調用RecyclerView.setItemAnimator();

但是ListView并沒有實作動畫效果,但我們可以在Adapter自己實作item的動畫效果;

2、重新整理差別:

ListView中通常重新整理資料是用全局重新整理notifyDataSetChanged(),這樣一來就會非常消耗資源;本身無法實作局部重新整理,但是如果要在ListView實作局部重新整理,依然是可以實作的,當一個item資料重新整理時,我們可以在Adapter中,實作一個onItemChanged()方法,在方法裡面擷取到這個item的position(可以通過getFirstVisiblePosition()),然後調用getView()方法來重新整理這個item的資料;

RecyclerView中可以實作局部重新整理,例如:notifyItemChanged();

3、緩存差別:

RecyclerView比ListView多兩級緩存,支援多個離ItemView緩存,支援開發者自定義緩存處理邏輯,支援所有RecyclerView共用同一個RecyclerViewPool(緩存池)。

ListView和RecyclerView緩存機制基本一緻,但緩存使用不同

10、ListView的adapter是什麼adapter

參考回答:
吊打面試官篇:2020國中級Android開發社招面試解答(中)Handler線程Bitmap性能優化JNI設計模式最後

BaseAdapter:抽象類,實際開發中我們會繼承這個類并且重寫相關方法,用得最多的一個擴充卡!

ArrayAdapter:支援泛型操作,最簡單的一個擴充卡,隻能展現一行文字〜

SimpleAdapter:同樣具有良好擴充性的一個擴充卡,可以自定義多種效果!

SimpleCursorAdapter:用于顯示簡單文本類型的listView,一般在資料庫那裡會用到,不過有點過時,不推薦使用!

11、LinearLayout、FrameLayout、RelativeLayout性能對比,為什麼?

參考回答:

1、RelativeLayout會讓子View調用2次onMeasure,LinearLayout 在有weight時,也會調用子 View 2次onMeasure

2、RelativeLayout的子View如果高度和RelativeLayout不同,則會引發效率問題,當子View很複雜時,這個問題會更加嚴重。如果可以,盡量使用padding代替margin。

3、在不影響層級深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。

JNI

1、對JNI是否了解

參考回答:

1、Java的優點是跨平台,但也因為其跨平台的的特性導緻其本地互動的能力不夠強大,一些和作業系統相關的的特性Java無法完成,于是Java提供JNI專門用于和本地代碼互動,通過JNI,使用者可以調用C、C++編寫的本地代碼

2、NDK是Android所提供的一個工具集合,通過NDK可以在Android中更加友善地通過JNI通路本地代碼,其優點在于

提高代碼的安全性。由于so庫反編譯困難,是以NDK提高了Android程式的安全性

可以很友善地使用目前已有的C/C++開源庫

便于平台的移植。通過C/C++實作的動态庫可以很友善地在其它平台上使用

提高程式在某些特定情形下的執行效率,但是并不能明顯提升Android程式的性能

2、如何加載NDK庫 ?如何在JNI中注冊Native函數,有幾種注冊方法 ?

參考回答:
public class JniTest{
        //加載NDK庫 
        static{
            System.loadLirary("jni-test");
        }
    }
           

注冊JNI函數的兩種方法

靜态方法

動态注冊

3、你用JNI來實作過什麼功能 ? 怎麼實作的 ?(加密處理、影音方面、圖形圖像處理)

參考回答: 推薦文章:Android JNI 篇 - ffmpeg 擷取音視訊縮略圖

設計模式

1、你所知道的設計模式有哪些?

參考回答:

1、建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。

2、結構型模式,共七種:擴充卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

3、行為型模式,共十一種:政策模式、模闆方法模式、觀察者模式、疊代子模式、責任鍊模式、指令模式、備忘錄 模式、狀态模式、通路者模式、中介者模式、解釋器模式。

2、談談MVC、MVP和MVVM,好在哪裡,不好在哪裡 ?

參考回答:

1、MVC:

視圖層(View) 對應于xml布局檔案和java代碼動态view部分

控制層(Controller) MVC中Android的控制層是由Activity來承擔的,Activity本來主要是作為初始化頁面,展示資料的操作,但是因為XML視圖功能太弱,是以Activity既要負責視圖的顯示又要加入控制邏輯,承擔的功能過多。

模型層(Model) 針對業務模型,建立資料結構和相關的類,它主要負責網絡請求,資料庫處理,I/O的操作。

2、總結

具有一定的分層,model徹底解耦,controller和view并沒有解耦層與層之間的互動盡量使用回調或者去使用消息機制去完成,盡量避免直接持有 controller和view在android中無法做到徹底分離,但在代碼邏輯層面一定要厘清業務邏輯被放置在model層,能夠更好的複用和修改增加業務。

3、MVP

通過引入接口BaseView,讓相應的視圖元件如Activity,Fragment去實作BaseView,實作了視圖層的獨立,通過中間層Preseter實作了Model和View的完全解耦。MVP徹底解決了MVC中View和Controller傻傻分不清楚的問題,但是随着業務邏輯的增加,一個頁面可能會非常複雜,UI的改變是非常多,會有非常多的case,這樣就會造成View的接口會很龐大。

4、MVVM

MVP中我們說過随着業務邏輯的增加,UI的改變多的情況下,會有非常多的跟UI相關的case,這樣就會造成View的接口會很龐大。而MVVM就解決了這個問題,通過雙向綁定的機制,實作資料和UI内容,隻要想改其中一方,另一方都能夠及時更新的一種設計理念,這樣就省去了很多在View層中寫很多case的情況,隻需要改變資料就行。

5、三者如何選擇?

如果項目簡單,沒什麼複雜性,未來改動也不大的話,那就不要用設計模式或者架構方法,隻需要将每個子產品封裝好,友善調用即可,不要為了使用設計模式或架構方法而使用。

對于偏向展示型的app,絕大多數業務邏輯都在後端,app主要功能就是展示資料,互動等,建議使用mvvm。

對于工具類或者需要寫很多業務邏輯app,使用mvp或者mvvm都可。

3、封裝p層之後.如果p層資料過大,如何解決?

參考回答:

對于MVP模式來說,P層如果資料邏輯過于臃腫,建議引入RxJava或則Dagger,越是複雜的邏輯,越能展現RxJava的優越性

4、是否能從Android中舉幾個例子說說用到了什麼設計模式 ?

參考回答:

1、AlertDialog、Notification源碼中使用了Builder(建造者)模式完成參數的初始化

2、Okhttp内部使用了責任鍊模式來完成每個Interceptor攔截器的調用

3、RxJava的觀察者模式;單例模式;GridView的擴充卡模式;Intent的原型模式

4、日常開發的BaseActivity抽象工廠模式

5、裝飾模式和代理模式有哪些差別 ?

參考回答:

1、裝飾器模式與代理模式的差別就在于

兩者都是對類的方法進行擴充,但裝飾器模式強調的是增強自身,在被裝飾之後你能夠在被增強的類上使用增強後的功能。

而代理模式則強調要讓别人幫你去做一些本身與你業務沒有太多關系的職責(記錄日志、設定緩存)代理模式是為了實作對象的控制,因為被代理的對象往往難以直接獲得或者是其内部不想暴露出來。

6、實作單例模式有幾種方法 ?懶漢式中雙層鎖的目的是什麼 ?兩次判空的目的又是什麼 ?

參考回答:

1、單例模式實作方法有多種:餓漢,懶漢(線程安全,線程非安全),雙重檢查(DCL),内部類,以及枚舉

2、所謂雙層檢驗鎖(在加鎖前後對執行個體對象進行兩次判空的檢驗):加鎖是為了第一次對象執行個體化的線程同步,而鎖内還要有第二層判空是因為可能會有多個線程進入第一層if判斷内部,而在加鎖代碼塊外排隊等候,如果鎖内不進行第二次檢驗,仍然會出現執行個體化多個對象的情況。

7、用到的一些開源架構,介紹一個看過源碼的,内部實作過程。

參考回答:

1、面試常客:Okhttp,Retrofit,Glide,RxJava,GreenDao,Dagger等

8、Fragment如果在Adapter中使用應該如何解耦?

參考回答:

1、接口回調

2、廣播

最後

這裡我分享一份大佬收錄整理的Android學習PDF+架構視訊+源碼筆記,進階架構技術進階腦圖、Android開發面試專題資料,進階進階架構資料

這些都是我現在閑暇還會反複翻閱的精品資料。 相信可以有效的幫助大家掌握知識、了解原理。

你也可以拿去查漏補缺,提升自身的競争力。

網際網路行業是一個知識疊代非常快的行業,如果你不養成學習的習慣,其他人不會停下來等你,這樣的話你就等于一直在退步!

如果你覺得自己學習效率低,缺乏正确的指導,可以加入資源豐富,學習氛圍濃厚的技術圈一起學習交流吧!