搞懂這些面試知識點,吊打面試官

一丶設計模式與使用場景

建造者模式:

將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。

使用場景比如最常見的 AlertDialog,拿我們開發過程中舉例,比如 Camera 開發過 程中,可能需要設置一個初始化的相機配置,設置攝像頭方向,閃光燈開閉,成 像質量等等,這種場景下就可以使用建造者模式

裝飾者模式:

動態的給一個對象添加一些額外的職責,就增加功能來說,裝飾模 式比生成子類更爲靈活。裝飾者模式可以在不改變原有類結構的情況下曾強類的 功能,比如 Java 中的 BufferedInputStream 包裝 FileInputStream,舉個開發中的 例子,比如在我們現有網絡框架上需要增加新的功能,那麼再包裝一層即可,裝 飾者模式解決了繼承存在的一些問題,比如多層繼承代碼的臃腫,使代碼邏輯更 清晰

還有等等.......觀察者模式, 代理模式,門面模式,單例模式,生產者消費者模式。

二丶java 中的線程創建方式,線程池的工作原理

java 中有三種創建線程的方式,或者說四種

1.繼承 Thread 類實現多線程
2.實現 Runnable 接口
3.實現 Callable 接口
4.通過線程池

線程池的工作原理: 線程池可以減少創建和銷燬線程的次數,從而減少系統資源 的消耗,當一個任務提交到線程池時

a. 首先判斷核心線程池中的線程是否已經滿了,如果沒滿,則創建一個核心線 程執行任務,否則進入下一步
b. 判斷工作隊列是否已滿,沒有滿則加入工作隊列,否則執行下一步
c. 判斷線程數是否達到了最大值,如果不是,則創建非核心線程執行任務,否 則執行飽和策略,默認拋出異常

三丶handler 原理

HandlerMessagelooperMessageQueue 構成了安卓的消息機制,handler 創 建後可以通過 sendMessage 將消息加入消息隊列,然後 looper 不斷的將消息從 MessageQueue 中取出來,回調到 HanderhandleMessage 方法,從而實現線程 的通信。

從兩種情況來說,第一在 UI 線程創建 Handler,此時我們不需要手動開啓 looper, 因爲在應用啓動時,在 ActivityThread 的 main 方法中就創建了一個當前主線程的 looper,並開啓了消息隊列,消息隊列是一個無限循環,爲什麼無限循環不會 ANR? 因爲可以說,應用的整個生命週期就是運行在這個消息循環中的,安卓是由事件 驅動的,Looper.loop 不斷的接收處理事件,每一個點擊觸摸或者 Activity 每一個 生命週期都是在 Looper.loop 的控制之下的,looper.loop 一旦結束,應用程序的 生命週期也就結束了。我們可以想想什麼情況下會發生 ANR,第一,事件沒有得 到處理,第二,事件正在處理,但是沒有及時完成,而對事件進行處理的就是looper,所以只能說事件的處理如果阻塞會導致 ANR,而不能說 looper 的無限循 環會 ANR

另一種情況就是在子線程創建Handler,此時由於這個線程中沒有默認開啓的消息 隊列,所以我們需要手動調用 looper.prepare(),並通過 looper.loop 開啓消息 主線程 Looper 從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程 往消息隊列發送消息,並且往管道文件寫數據,主線程即被喚醒,從管道文件讀 取數據,主線程被喚醒只是爲了讀取消息,當消息讀取完畢,再次睡眠。因此 loop 的循環並不會對 CPU 性能有過多的消耗。

四丶內存泄漏的場景和解決辦法

1.非靜態內部類的靜態實例

非靜態內部類會持有外部類的引用,如果非靜態內部類的實例是靜態的,就會長 期的維持着外部類的引用,組織被系統回收,解決辦法是使用靜態內部類

2.多線程相關的匿名內部類和非靜態內部類

匿名內部類同樣會持有外部類的引用,如果在線程中執行耗時操作就有可能發生 內存泄漏,導致外部類無法被回收,直到耗時任務結束,解決辦法是在頁面退出 時結束線程中的任務

3.Handler 內存泄漏

Handler 導致的內存泄漏也可以被歸納爲非靜態內部類導致的,Handler 內部 message 是被存儲在 MessageQueue 中的,有些 message 不能馬上被處理,存在 的時間會很長,導致 handler 無法被回收,如果 handler 是非靜態的,就會導致 它的外部類無法被回收,
解決辦法是 :

1.使用靜態 handler,外部類引用使用弱引 用處理
2.在退出頁面時移除消息隊列中的消息

4.Context 導致內存泄漏

根據場景確定使用 Activity 的 Context 還是 ApplicationContext,因爲二者生命周 期不同,對於不必須使用 Activity 的 Context 的場景(Dialog),一律採用 ApplicationContext,單例模式是最常見的發生此泄漏的場景,比如傳入一個 Activity 的 Context 被靜態類引用,導致無法回收.

5.靜態 View 導致泄漏

使用靜態 View 可以避免每次啓動 Activity 都去讀取並渲染 View,但是靜態 View會持有 Activity 的引用,導致無法回收,解決辦法是在 Activity 銷燬的時候將靜態 View 設置爲 null(View 一旦被加載到界面中將會持有一個 Context 對象的引用, 在這個例子中,這個 context 對象是我們的 Activity,聲明一個靜態變量引用這個 View,也就引用了 activity)

6.WebView 導致的內存泄漏

WebView 只要使用一次,內存就不會被釋放,所以 WebView 都存在內存泄漏的 問題,通常的解決辦法是爲 WebView 單開一個進程,使用 AIDL 進行通信,根據 業務需求在合適的時機釋放掉

7.資源對象未關閉導致

如 Cursor,File 等,內部往往都使用了緩衝,會造成內存泄漏,一定要確保關閉 它並將引用置爲 null

8.集合中的對象未清理

集合用於保存對象,如果集合越來越大,不進行合理的清理,尤其是入股集合是 靜態的

9.Bitmap 導致內存泄漏

bitmap 是比較佔內存的,所以一定要在不使用的時候及時進行清理,避免靜態 變量持有大的 bitmap 對象

10.監聽器未關閉

很多需要 registerunregister 的系統服務要在合適的時候進行 unregister,手動添 加的 listener 也需要及時移除

五丶如何避免 OOM?

1.使用更加輕量的數據結構: 如使用 ArrayMap/SparseArray 替代 HashMap,HashMap 更耗內存,因爲它需要額外的實例對象來記錄 Mapping 操作, SparseArray 更加高效,因爲它避免了 Key Value的自動裝箱,和裝箱後的解箱操 作

2.便面枚舉的使用,可以用靜態常量或者註解@IntDef 替代

3.Bitmap 優化:
a.尺寸壓縮: 通過 InSampleSize 設置合適的縮放
b.顏色質量: 設置合適的 format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差異.
c.inBitmap: 使用 inBitmap 屬性可以告知 Bitmap 解碼器去嘗試使用已經存在的內 存區域,新解碼的 Bitmap 會嘗試去使用之前那張 Bitmap 在 Heap 中所佔據的 pixel data 內存區域,而不是去問內存重新申請一塊區域來存放 Bitmap。利用這種特 性,即使是上千張的圖片,也只會僅僅只需要佔用屏幕所能夠顯示的圖片數量的 內存大小,但複用存在一些限制,具體體現在:在 Android 4.4 之前只能重用相 同大小的 Bitmap 的內存,而 Android 4.4 及以後版本則只要後來的 Bitmap 比之前 的小即可。使用 inBitmap 參數前,每創建一個 Bitmap 對象都會分配一塊內存供 其使用,而使用了 inBitmap 參數後,多個 Bitmap 可以複用一塊內存,這樣可以 提高性能

4.StringBuilder 替代 String: 在有些時候,代碼中會需要使用到大量的字符串拼接 的操作,這種時候有必要考慮使用 StringBuilder 來替代頻繁的“+”

5.避免在類似 onDraw 這樣的方法中創建對象,因爲它會迅速佔用大量內存,引 起頻繁的 GC 甚至內存抖動

6.減少內存泄漏也是一種避免 OOM 的方法

六丶onRestart 的調用場景

(1)按下 home 鍵之後,然後切換回來,會調用 onRestart()
(2)從本 Activity 跳轉到另一個 Activity 之後,按 back 鍵返回原來 Activity,會 調用 onRestart()
(3)從本 Activity 切換到其他的應用,然後再從其他應用切換回來,會調用 onRestart()

說下 Activity 的橫豎屏的切換的生命週期,用那個方法來保存數據,兩者的區別。 觸發在什麼時候在那個方法裏可以獲取數據等。

七丶實現進程保活

a: Service 設置成 START_STICKY kill 後會被重啓(等待 5 秒左右),重傳 Intent,保 持與重啓前一樣
b: 通過 startForeground 將進程設置爲前臺進程, 做前臺服務,優先級和前臺 應用一個級別,除非在系統內存非常缺,否則此進程不會被 kill
c: 雙進程 Service: 讓 2 個進程互相保護對方,其中一個 Service 被清理後,另 外沒被清理的進程可以立即重啓進程
d: 用 C 編寫守護進程(即子進程) : Android 系統中當前進程(Process)fork 出來的子 進程,被系統認爲是兩個不同的進程。當父進程被殺死的時候,子進程仍然可以 存活,並不受影響(Android5.0 以上的版本不可行)聯繫廠商,加入白名單
e. 鎖屏狀態下,開啓一個一像素 Activity

八丶ANR 的原因

1.耗時的網絡訪問
2.大量的數據讀寫
3.數據庫操作
4.硬件操作(比如 camera)
5.調用 thread 的 join()方法、sleep()方法、wait()方法或者等待線程鎖的時候
6.service binder 的數量達到上限
7.system server 中發生 WatchDog ANR
8.service 忙導致超時無響應
9.其他線程持有鎖,導致主線程等待超時
10.其它線程終止或崩潰導致主線程一直等待

九丶三級緩存原理

當 Android 端需要獲得數據時比如獲取網絡中的圖片,首先從內存中查找(按鍵 查找),內存中沒有的再從磁盤文件或 sqlite 中去查找,若磁盤中也沒有才通過 網絡獲取

十丶線程

1. 什麼是線程

線程就是進程中運行的多個子任務,是操作系統調用的最小單元

2. 線程的狀態

New: 新建狀態,new 出來,還沒有調用 start

Runnable: 可運行狀態,調用 start 進入可運行狀態,可能運行也可能沒有運行, 取決於操作系統的調度

Blocked: 阻塞狀態,被鎖阻塞,暫時不活動,阻塞狀態是線程阻塞在進入synchronized 關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態。

Waiting: 等待狀態,不活動,不運行任何代碼,等待線程調度器調度,wait sleep

Timed Waiting: 超時等待,在指定時間自行返回

Terminated: 終止狀態,包括正常終止和異常終止

3. 線程的創建

a.繼承 Thread 重寫 run 方法

b.實現 Runnable 重寫 run 方法

c.實現 Callable 重寫 call 方法 實現 Callable 和實現 Runnable 類似,但是功能更強大,具體表現在

  • 可以在任務結束後提供一個返回值,Runnable不行
  • call 方法可以拋出異常,Runnable 的 run 方法不行
  • 可以通過運行 Callable 得到的 Fulture對象監聽目標線程調用 call 方法的結果, 得到返回值,(fulture.get(),調用後會阻塞,直到獲取到返回值)

4. 線程中斷

一般情況下,線程不執行完任務不會退出,但是在有些場景下,我們需要手動控 制線程中斷結束任務,Java 中有提供線程中斷機制相關的 Api,每個線程都一個狀 態位用於標識當前線程對象是否是中斷狀態

public boolean isInterrupted() //判斷中斷標識位是否是 true,不會改變標 識位 public void interrupt() //將中斷標識位設置爲 truepublic static boolean interrupted() //判斷當前線程是否被中斷,並且該方法調用結束的時 候會清空中斷標識位

需要注意的是 interrupt()方法並不會真的中斷線程,它只是將中斷標識位設置 爲 true,具體是否要中斷由程序來判斷,如下,只要線程中斷標識位爲 false,也就 是沒有中斷就一直執行線程方法

  new Thread(new Runnable(){ 
       while(!Thread.currentThread().isInterrupted()){ 
            //執行線程方法  
  } }).start();

前邊我們提到了線程的六種狀態,New Runnable Blocked Waiting Timed Waiting Terminated,那麼在這六種狀態下調用線程中斷的代碼會怎樣呢,New 和 Terminated 狀態下,線程不會理會線程中斷的請求,既不會設置標記位,在 Runnable 和 Blocked 狀態下調用 interrupt 會將標誌位設置位 true,在 Waiting 和 Timed Waiting 狀態下會發生 InterruptedException 異常,針對這個異常我們如何 處理?

1.在 catch 語句中通過 interrupt 設置中斷狀態,因爲發生中斷異常時,中斷標誌 位會被複位,我們需要重新將中斷標誌位設置爲 true,這樣外界可以通過這個狀 態判斷是否需要中斷線程

  try{
     .... 
  }catch(InterruptedException e){ 
     Thread.currentThread().interrupt(); 
  }

2.更好的做法是,不捕獲異常,直接拋出給調用者處理,這樣更靈活

5.Thread 爲什麼不能用 stop 方法停止線程

從 SUN 的官方文檔可以得知,調用 Thread.stop()方法是不安全的,這是因爲當調 用 Thread.stop()方法時,會發生下面兩件事:

  1. 即刻拋出 ThreadDeath 異常,在線程的 run()方法內,任何一點都有可能拋出 ThreadDeath Error,包括在 catchfinally 語句中。
  2. 釋放該線程所持有的所有的鎖。調用 thread.stop()後導致了該線程所持有的所有鎖的 突然釋放,那麼被保護數據就有可能呈現不一致性,其他線程在使用這些被破壞的數據時, 有可能導致一些很奇怪的應用程序錯誤。

6.進程線程的區別

1.地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地 址空間。
2.資源擁有:同一進程內的線程共享本進程的資源如內存、I/O、cpu 等,但是進 程之間的資源是獨立的。
3.一個進程崩潰後,在保護模式下不會對其他進程產生影響,但是一個線程崩潰 整個進程都死掉。所以多進程要比多線程健壯。
4.進程切換時,消耗的資源大,效率不高。所以涉及到頻繁的切換時,使用線程 要好於進程。同樣如果要求同時進行並且又要共享某些變量的併發操作,只能用 線程不能用進程
5.執行過程:每個獨立的進程程有一個程序運行的入口、順序執行序列和程序入 口。但是線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程 執行控制。
6.線程是處理器調度的基本單位,但是進程不是。
7.兩者均可併發執行。

未完待續......
更多內容的面試彙總PDF版本,含有BATJ.字節跳動面試專題,算法專題,高端技術專題,混合開發專題,java面試專題,Android,Java小知識,到性能優化.線程.View.OpenCV.NDK。

Android面試大全+視頻教程+學習筆記
PDF內容.png

搞懂這些面試知識點,吊打面試官

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章