金三銀四,衝擊大廠,你值得擁有的一份2019初中級移動端社招面試總結+解答
你當前所處:Android篇:2019初中級Android開發社招面試解答(中)
Android篇:2019初中級Android開發社招面試解答(下)
Android篇:2019初中級Android開發社招面試解答(上)
注:因為實際開發與參考答案會有所不同,再者怕誤導大家,所以這些面試題答案還是自己去理解!面試官會針對簡歷中提到的知識點由淺入深提問,所以不要背答案,多理解。
Handler
1、談談訊息機制Handler作用 ?有哪些要素 ?流程是怎樣的 ?
- 參考回答:
- 負責跨執行緒通訊,這是因為在主執行緒不能做耗時操作,而子執行緒不能更新UI,所以當子執行緒中進行耗時操作後需要更新UI時,通過Handler將有關UI的操作切換到主執行緒中執行。
- 具體分為四大要素
- 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()方法也就跟著退出。
- 具體流程如下
- 在主執行緒建立的時候會建立一個Looper,同時也會在在Looper內部建立一個訊息佇列。而在創鍵Handler的時候取出當前執行緒的Looper,並通過該Looper物件獲得訊息佇列,然後Handler在子執行緒中通過MessageQueue.enqueueMessage在訊息佇列中新增一條Message。
- 通過Looper.loop() 開啟訊息迴圈不斷輪詢呼叫 MessageQueue.next(),取得對應的Message並且通過Handler.dispatchMessage傳遞給Handler,最終呼叫Handler.handlerMessage處理訊息。
2、一個執行緒能否建立多個Handler,Handler跟Looper之間的對應關係 ?
- 參考回答:
- 一個Thread只能有一個Looper,一個MessageQueen,可以有多個Handler
- 以一個執行緒為基準,他們的數量級關係是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)
3、軟引用跟弱引用的區別
- 參考回答:
- 軟引用:如果一個物件只具有軟引用,則記憶體空間充足時,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以一直被程式使用。
- 弱引用:如果一個物件只具有弱引用,那麼在垃圾回收器執行緒掃描的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。
- 兩者之間根本區別在於:只具有弱引用的物件擁有更短暫的生命週期,可能隨時被回收。而只具有軟引用的物件只有當記憶體不夠的時候才被回收,在記憶體足夠的時候,通常不被回收。
- 推薦文章: Java中的四種引用型別:強引用、軟引用、弱引用和虛引用
4、Handler 引起的記憶體洩露原因以及最佳解決方案
- 參考回答:
- 洩露原因:
- Handler 允許我們傳送延時訊息,如果在延時期間使用者關閉了 Activity,那麼該 Activity 會洩露。 這個洩露是因為 Message 會持有 Handler,而又因為 Java 的特性,內部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導致 Activity 洩露。
- 解決方案:
- 將 Handler 定義成靜態的內部類,在內部持有Activity的弱引用,並在Acitivity的onDestroy()中呼叫handler.removeCallbacksAndMessages(null)及時移除所有訊息。
5、為什麼系統不建議在子執行緒訪問UI?
- 參考回答:
- Android的UI控制元件不是執行緒安全的,如果在多執行緒中併發訪問可能會導致UI控制元件處於不可預期的狀態
- 這時你可能會問為何系統不對UI控制元件的訪問加上鎖機制呢?因為
- 加鎖機制會讓UI訪問邏輯變的複雜
- 加鎖機制會降低UI的訪問效率,因為加鎖會阻塞某些執行緒的執行
6、Looper死迴圈為什麼不會導致應用卡死?
- 參考回答:
- 主執行緒的主要方法就是訊息迴圈,一旦退出訊息迴圈,那麼你的應用也就退出了,Looer.loop()方法可能會引起主執行緒的阻塞,但只要它的訊息迴圈沒有被阻塞,能一直處理事件就不會產生ANR異常。
- 造成ANR的不是主執行緒阻塞,而是主執行緒的Looper訊息處理過程發生了任務阻塞,無法響應手勢操作,不能及時重新整理UI。
- 阻塞與程式無響應沒有必然關係,雖然主執行緒在沒有訊息可處理的時候是阻塞的,但是隻要保證有訊息的時候能夠立刻處理,程式是不會無響應的。
7、使用Handler的postDealy後訊息佇列會有什麼變化?
- 參考回答:
- 如果佇列中只有這個訊息,那麼訊息不會被髮送,而是計算到時喚醒的時間,先將Looper阻塞,到時間就喚醒它。但如果此時要加入新訊息,該訊息佇列的對頭跟delay時間相比更長,則插入到頭部,按照觸發時間進行排序,隊頭的時間最小、隊尾的時間最大
8、可以在子執行緒直接new一個Handler嗎?怎麼做?
- 參考回答:
- 不可以,因為在主執行緒中,Activity內部包含一個Looper物件,它會自動管理Looper,處理子執行緒中傳送過來的訊息。而對於子執行緒而言,沒有任何物件幫助我們維護Looper物件,所以需要我們自己手動維護。所以要在子執行緒開啟Handler要先建立Looper,並開啟Looper迴圈
- 推薦文章:Android非同步訊息處理機制完全解析,帶你從原始碼的角度徹底理解
9、Message可以如何建立?哪種效果更好,為什麼?
- 參考回答:可以通過三種方法建立:
- 直接生成例項Message m = new Message
- 通過Message m = Message.obtain
- 通過Message m = mHandler.obtainMessage()
- 後兩者效果更好,因為Android預設的訊息池中訊息數量是10,而後兩者是直接在訊息池中取出一個Message例項,這樣做就可以避免多生成Message例項。
執行緒
1、執行緒池的好處? 四種執行緒池的使用場景,執行緒池的幾個引數的理解?
- 參考回答:
- 使用執行緒池的好處是減少在建立和銷燬執行緒上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用執行緒池,有可能造成系統建立大量同類執行緒而導致消耗完記憶體或則“過度切換”的問題,歸納總結就是
- 重用存在的執行緒,減少物件建立、消亡的開銷,效能佳。
- 可有效控制最大併發執行緒數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
- 提供定時執行、定期執行、單執行緒、併發數控制等功能。
- Android中的執行緒池都是直接或間接通過配置ThreadPoolExecutor來實現不同特性的執行緒池.Android中最常見的類具有不同特性的執行緒池分別為:
- newCachedThreadPool:只有非核心執行緒,最大執行緒數非常大,所有執行緒都活動時會為新任務建立新執行緒,否則會利用空閒執行緒 ( 60s空閒時間,過了就會被回收,所以執行緒池中有0個執行緒的可能 )來處理任務.
- 優點:任何任務都會被立即執行(任務佇列SynchronousQuue相當於一個空集合);比較適合執行大量的耗時較少的任務.
- newFixedThreadPool:只有核心執行緒,並且數量固定的,所有執行緒都活動時,因為佇列沒有限制大小,新任務會等待執行,當執行緒池空閒時不會釋放工作執行緒,還會佔用一定的系統資源。
- 優點:更快的響應外界請求
- newScheduledThreadPool:核心執行緒數固定,非核心執行緒(閒著沒活幹會被立即回收數)沒有限制.
- 優點:執行定時任務以及有固定週期的重複任務
- newSingleThreadExecutor:只有一個核心執行緒,確保所有的任務都在同一執行緒中按序完成
- 優點:不需要處理執行緒同步的問題
- 通過原始碼可以瞭解到上面的四種執行緒池實際上還是利用 ThreadPoolExecutor 類實現的
- 推薦文章:java執行緒池解析和四種執行緒池的使用
2、Android中還了解哪些方便執行緒切換的類?
- 參考回答:
- AsyncTask:底層封裝了執行緒池和Handler,便於執行後臺任務以及在子執行緒中進行UI操作。
- HandlerThread:一種具有訊息迴圈的執行緒,其內部可使用Handler。
- IntentService:是一種非同步、會自動停止的服務,內部採用HandlerThread。
3、講講AsyncTask的原理
- 參考回答:
- AsyncTask中有兩個執行緒池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler),其中執行緒池SerialExecutor用於任務的排隊,而執行緒池THREAD_POOL_EXECUTOR用於真正地執行任務,InternalHandler用於將執行環境從執行緒池切換到主執行緒。
- sHandler是一個靜態的Handler物件,為了能夠將執行環境切換到主執行緒,這就要求sHandler這個物件必須在主執行緒建立。由於靜態成員會在載入類的時候進行初始化,因此這就變相要求AsyncTask的類必須在主執行緒中載入,否則同一個程式中的AsyncTask都將無法正常工作。
4、IntentService有什麼用 ?
- 參考回答:
- IntentService可用於執行後臺耗時的任務,當任務執行完成後會自動停止,同時由於IntentService是服務的原因,不同於普通Service,IntentService可自動建立子執行緒來執行任務,這導致它的優先順序比單純的執行緒要高,不容易被系統殺死,所以IntentService比較適合執行一些高優先順序的後臺任務。
5、直接在Activity中建立一個thread跟在service中建立一個thread之間的區別?
- 參考回答:
- 在Activity中被建立:該Thread的就是為這個Activity服務的,完成這個特定的Activity交代的任務,主動通知該Activity一些訊息和事件,Activity銷燬後,該Thread也沒有存活的意義了。
- 在Service中被建立:這是保證最長生命週期的Thread的唯一方式,只要整個Service不退出,Thread就可以一直在後臺執行,一般在Service的onCreate()中建立,在onDestroy()中銷燬。所以,在Service中建立的Thread,適合長期執行一些獨立於APP的後臺任務,比較常見的就是:在Service中保持與伺服器端的長連線。
6、ThreadPoolExecutor的工作策略 ?
- 參考回答:ThreadPoolExecutor執行任務時會遵循如下規則
- 如果執行緒池中的執行緒數量未達到核心執行緒的數量,那麼會直接啟動一個核心執行緒來執行任務。
- 如果執行緒池中的執行緒數量已經達到或則超過核心執行緒的數量,那麼任務會被插入任務佇列中排隊等待執行。
- 如果在第2點無法將任務插入到任務佇列中,這往往是由於任務佇列已滿,這個時候如果線上程數量未達到執行緒池規定的最大值,那麼會立刻啟動一個非核心執行緒來執行任務。
- 如果第3點中執行緒數量已經達到執行緒池規定的最大值,那麼就拒絕執行此任務,ThreadPoolExecutor會呼叫RejectedExecutionHandler的rejectedExecution方法來通知呼叫者。
7、Handler、Thread和HandlerThread的差別?
- 參考回答:
- Handler:在android中負責傳送和處理訊息,通過它可以實現其他支線執行緒與主執行緒之間的訊息通訊。
- Thread:Java程式中執行運算的最小單位,亦即執行處理機排程的基本單位。某一程式中一路單獨執行的程式。
- HandlerThread:一個繼承自Thread的類HandlerThread,Android中沒有對Java中的Thread進行任何封裝,而是提供了一個繼承自Thread的類HandlerThread類,這個類對Java的Thread做了很多便利的封裝。HandlerThread繼承於Thread,所以它本質就是個Thread。與普通Thread的差別就在於,它在內部直接實現了Looper的實現,這是Handler訊息機制必不可少的。有了自己的looper,可以讓我們在自己的執行緒中分發和處理訊息。如果不用HandlerThread的話,需要手動去呼叫Looper.prepare()和Looper.loop()這些方法。
8、ThreadLocal的原理
- 參考回答:
- ThreadLocal是一個關於建立執行緒區域性變數的類。使用場景如下所示:
- 實現單個執行緒單例以及單個執行緒上下文資訊儲存,比如交易id等。
- 實現執行緒安全,非執行緒安全的物件使用ThreadLocal之後就會變得執行緒安全,因為每個執行緒都會有一個對應的例項。 承載一些執行緒相關的資料,避免在方法中來回傳遞引數。
- 當需要使用多執行緒時,有個變數恰巧不需要共享,此時就不必使用synchronized這麼麻煩的關鍵字來鎖住,每個執行緒都相當於在堆記憶體中開闢一個空間,執行緒中帶有對共享變數的緩衝區,通過緩衝區將堆記憶體中的共享變數進行讀取和操作,ThreadLocal相當於執行緒內的記憶體,一個區域性變數。每次可以對執行緒自身的資料讀取和操作,並不需要通過緩衝區與 主記憶體中的變數進行互動。並不會像synchronized那樣修改主記憶體的資料,再將主記憶體的資料複製到執行緒內的工作記憶體。ThreadLocal可以讓執行緒獨佔資源,儲存於執行緒內部,避免執行緒堵塞造成CPU吞吐下降。
- 在每個Thread中包含一個ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的物件,value是獨享資料。
9、多執行緒是否一定會高效(優缺點)
- 參考回答:
- 多執行緒的優點:
- 方便高效的記憶體共享 - 多程式下記憶體共享比較不便,且會抵消掉多程式程式設計的好處
- 較輕的上下文切換開銷 - 不用切換地址空間,不用更改CR3暫存器,不用清空TLB
- 執行緒上的任務執行完後自動銷燬
- 多執行緒的缺點:
- 開啟執行緒需要佔用一定的記憶體空間(預設情況下,每一個執行緒都佔512KB)
- 如果開啟大量的執行緒,會佔用大量的記憶體空間,降低程式的效能
- 執行緒越多,cpu在呼叫執行緒上的開銷就越大
- 程式設計更加複雜,比如執行緒間的通訊、多執行緒的資料共享
- 綜上得出,多執行緒不一定能提高效率,在記憶體空間緊張的情況下反而是一種負擔,因此在日常開發中,應儘量
- 不要頻繁建立,銷燬執行緒,使用執行緒池
- 減少執行緒間同步和通訊(最為關鍵)
- 避免需要頻繁共享寫的資料
- 合理安排共享資料結構,避免偽共享(false sharing)
- 使用非阻塞資料結構/演算法
- 避免可能產生可伸縮性問題的系統呼叫(比如mmap)
- 避免產生大量缺頁異常,儘量使用Huge Page
- 可以的話使用使用者態輕量級執行緒代替核心執行緒
10、多執行緒中,讓你做一個單例,你會怎麼做
- 參考回答:
- 多執行緒中建立單例模式考慮的因素有很多,比如執行緒安全 -延遲載入-程式碼安全:如防止序列化攻擊,防止反射攻擊(防止反射進行私有方法呼叫) -效能因素
- 實現方法有多種,餓漢,懶漢(執行緒安全,執行緒非安全),雙重檢查(DCL),內部類,以及列舉
- 推薦文章:單例模式的總結
11、除了notify還有什麼方式可以喚醒執行緒
- 參考回答:
- 當一個擁有Object鎖的執行緒呼叫 wait()方法時,就會使當前執行緒加入object.wait 等待佇列中,並且釋放當前佔用的Object鎖,這樣其他執行緒就有機會獲取這個Object鎖,獲得Object鎖的執行緒呼叫notify()方法,就能在Object.wait 等待佇列中隨機喚醒一個執行緒(該喚醒是隨機的與加入的順序無關,優先順序高的被喚醒概率會高)
- 如果呼叫notifyAll()方法就喚醒全部的執行緒。注意:呼叫notify()方法後並不會立即釋放object鎖,會等待該執行緒執行完畢後釋放Object鎖。
12、什麼是ANR ? 什麼情況會出現ANR ?如何避免 ? 在不看程式碼的情況下如何快速定位出現ANR問題所在 ?
- 參考回答:
- ANR(Application Not Responding,應用無響應):當操作在一段時間內系統無法處理時,會在系統層面會彈出ANR對話方塊
- 產生ANR可能是因為5s內無響應使用者輸入事件、10s內未結束BroadcastReceiver、20s內未結束Service
- 想要避免ANR就不要在主執行緒做耗時操作,而是通過開子執行緒,方法比如繼承Thread或實現Runnable介面、使用AsyncTask、IntentService、HandlerThread等
- 推薦文章:如何快速分析定位ANR
Bitmap
1、Bitmap使用需要注意哪些問題 ?
- 參考回答:
- 要選擇合適的圖片規格(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記憶體
- 降低取樣率:BitmapFactory.Options 引數inSampleSize的使用,先把options.inJustDecodeBounds設為true,只是去讀取圖片的大小,在拿到圖片的大小之後和要顯示的大小做比較通過calculateInSampleSize()函式計算inSampleSize的具體值,得到值之後。options.inJustDecodeBounds設為false讀圖片資源。
- 複用記憶體:即通過軟引用(記憶體不夠的時候才會回收掉),複用記憶體塊,不需要再重新給這個bitmap申請一塊新的記憶體,避免了一次記憶體的分配和回收,從而改善了執行效率。
- 使用recycle()方法及時回收記憶體。
- 壓縮圖片。
2、Bitmap.recycle()會立即回收麼?什麼時候會回收?如果沒有地方使用這個Bitmap,為什麼垃圾回收不會直接回收?
- 參考回答:
- 通過原始碼可以瞭解到,載入Bitmap到記憶體裡以後,是包含兩部分記憶體區域的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap物件是由Java部分分配的,不用的時候系統就會自動回收了
- 但是那個對應的C可用的記憶體區域,虛擬機器是不能直接回收的,這個只能呼叫底層的功能釋放。所以需要呼叫recycle()方法來釋放C部分的記憶體
- bitmap.recycle()方法用於回收該Bitmap所佔用的記憶體,接著將bitmap置空,最後使用System.gc()呼叫一下系統的垃圾回收器進行回收,呼叫System.gc()並不能保證立即開始進行回收過程,而只是為了加快回收的到來。
3、一張Bitmap所佔記憶體以及記憶體佔用的計算
- 參考回答:
- Bitamp 所佔記憶體大小 = 寬度畫素 x (inTargetDensity / inDensity) x 高度畫素 x (inTargetDensity / inDensity)x 一個畫素所佔的記憶體位元組大小
- 注:這裡inDensity表示目標圖片的dpi(放在哪個資原始檔夾下),inTargetDensity表示目標螢幕的dpi,所以你可以發現inDensity和inTargetDensity會對Bitmap的寬高進行拉伸,進而改變Bitmap佔用記憶體的大小。
- 在Bitmap裡有兩個獲取記憶體佔用大小的方法。
- getByteCount():API12 加入,代表儲存 Bitmap 的畫素需要的最少記憶體。
- getAllocationByteCount():API19 加入,代表在記憶體中為 Bitmap 分配的記憶體大小,代替了 getByteCount() 方法。
- 在不復用 Bitmap 時,getByteCount() 和 getAllocationByteCount 返回的結果是一樣的。在通過複用 Bitmap 來解碼圖片時,那麼 getByteCount() 表示新解碼圖片佔用記憶體的大 小,getAllocationByteCount() 表示被複用 Bitmap 真實佔用的記憶體大小
4、Android中快取更新策略 ?
- 參考回答:
- Android的快取更新策略沒有統一的標準,一般來說,快取策略主要包含快取的新增、獲取和刪除這三類操作,但不管是記憶體快取還是儲存裝置快取,它們的快取容量是有限制的,因此刪除一些舊快取並新增新快取,如何定義快取的新舊這就是一種策略,不同的策略就對應著不同的快取演算法
- 比如可以簡單地根據檔案的最後修改時間來定義快取的新舊,當快取滿時就將最後修改時間較早的快取移除,這就是一種快取演算法,但不算很完美
5、LRU的原理 ?
- 參考回答:
- 為減少流量消耗,可採用快取策略。常用的快取演算法是LRU(Least Recently Used):當快取滿時, 會優先淘汰那些近期最少使用的快取物件。主要是兩種方式:
- LruCache(記憶體快取):LruCache類是一個執行緒安全的泛型類:內部採用一個LinkedHashMap以強引用的方式儲存外界的快取物件,並提供get和put方法來完成快取的獲取和新增操作,當快取滿時會移除較早使用的快取物件,再新增新的快取物件。
- DiskLruCache(磁碟快取): 通過將快取物件寫入檔案系統從而實現快取效果
效能優化
1、圖片的三級快取中,圖片載入到記憶體中,如果記憶體快爆了,會發生什麼?怎麼處理?
- 參考回答:
- 首先我們要清楚圖片的三級快取是如何的 如果記憶體足夠時不回收。記憶體不夠時就回收軟引用物件
2、記憶體中如果載入一張500*500的png高清圖片.應該是佔用多少的記憶體?
- 參考回答:
- 不考慮螢幕比的話:佔用記憶體=500 * 500 * 4 = 1000000B ≈ 0.95MB
- 考慮螢幕比的的話:佔用記憶體= 寬度畫素 x (inTargetDensity / inDensity) x 高度畫素 x (inTargetDensity / inDensity)x 一個畫素所佔的記憶體位元組大小
- inDensity表示目標圖片的dpi(放在哪個資原始檔夾下),inTargetDensity表示目標螢幕的dpi
3、WebView的效能優化 ?
- 參考回答:
- 一個載入網頁的過程中,native、網路、後端處理、CPU都會參與,各自都有必要的工作和依賴關係;讓他們相互並行處理而不是相互阻塞才可以讓網頁載入更快:
- WebView初始化慢,可以在初始化同時先請求資料,讓後端和網路不要閒著。
- 常用 JS 本地化及延遲載入,使用第三方瀏覽核心
- 後端處理慢,可以讓伺服器分trunk輸出,在後端計算的同時前端也載入網路靜態資源。
- 指令碼執行慢,就讓指令碼在最後執行,不阻塞頁面解析。
- 同時,合理的預載入、預快取可以讓載入速度的瓶頸更小。
- WebView初始化慢,就隨時初始化好一個WebView待用。
- DNS和連結慢,想辦法複用客戶端使用的域名和連結。
- 推薦文章:WebView效能、體驗分析與優化
4、Bitmap如何處理大圖,如一張30M的大圖,如何預防OOM?
- 參考回答:避免OOM的問題就需要對大圖片的載入進行管理,主要通過縮放來減小圖片的記憶體佔用。
- BitmapFactory提供的載入圖片的四類方法(decodeFile、decodeResource、decodeStream、decodeByteArray)都支援BitmapFactory.Options引數,通過inSampleSize引數就可以很方便地對一個圖片進行取樣縮放
- 比如一張10241024的高清圖片來說。那麼它佔有的記憶體為102410244,即4MB,如果inSampleSize為2,那麼取樣後的圖片佔用記憶體只有512512*4,即1MB(注意:根據最新的官方文件指出,inSampleSize的取值應該總是為2的指數,即1、2、4、8等等,如果外界輸入不足為2的指數,系統也會預設選擇最接近2的指數代替,比如2)
- 綜合考慮。通過取樣率即可有效載入圖片,流程如下
- 將BitmapFactory.Options的inJustDecodeBounds引數設為true並載入圖片
- 從BitmapFactory.Options中取出圖片的原始寬高資訊,它們對應outWidth和outHeight引數
- 根據取樣率的規則並結合目標View的所需大小計算出取樣率inSampleSize
- 將BitmapFactory.Options的inJustDecodeBounds引數設為false,重新載入圖片
- 推薦文章:Android高效載入大圖、多圖解決方案,有效避免程式OOM
5、記憶體回收機制與GC演算法(各種演算法的優缺點以及應用場景);GC原理時機以及GC物件
- 參考回答:
- 記憶體判定物件可回收有兩種機制:
- 引用計數演算法:給物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的物件就是不可能再被使用的。然而在主流的Java虛擬機器裡未選用引用計數演算法來管理記憶體,主要原因是它難以解決物件之間相互迴圈引用的問題,所以出現了另一種物件存活判定演算法。
- 可達性分析法:通過一系列被稱為『GCRoots』的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的。其中可作為GC Roots的物件:虛擬機器棧中引用的物件,主要是指棧幀中的本地變數*、本地方法棧中Native方法引用的物件、方法區中類靜態屬性引用的物件、方法區中常量引用的物件
- GC回收演算法有以下四種:
- 分代收集演算法:是當前商業虛擬機器都採用的一種演算法,根據物件存活週期的不同,將Java堆劃分為新生代和老年代,並根據各個年代的特點採用最適當的收集演算法。
- 新生代:大批物件死去,只有少量存活。使用『複製演算法』,只需複製少量存活物件即可。
- 複製演算法:把可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用盡後,把還存活著的物件『複製』到另外一塊上面,再將這一塊記憶體空間一次清理掉。實現簡單,執行高效。在物件存活率較高時就要進行較多的複製操作,效率將會變低
- 老年代:物件存活率高。使用『標記—清理演算法』或者『標記—整理演算法』,只需標記較少的回收物件即可。
- 標記-清除演算法:首先『標記』出所有需要回收的物件,然後統一『清除』所有被標記的物件。標記和清除兩個過程的效率都不高,清除之後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。
- 標記-整理演算法:首先『標記』出所有需要回收的物件,然後進行『整理』,使得存活的物件都向一端移動,最後直接清理掉端邊界以外的記憶體。標記整理演算法會將所有的存活物件移動到一端,並對不存活物件進行處理,因此其不會產生記憶體碎片
- 推薦文章:圖解Java 垃圾回收機制
6、記憶體洩露和記憶體溢位的區別 ?AS有什麼工具可以檢測記憶體洩露
- 參考回答:
- 記憶體溢位(out of memory):是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是記憶體溢位。
- 記憶體洩露(memory leak):是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆積後果很嚴重,無論多少記憶體,遲早會被佔光。memory leak會最終會導致out of memory!
- 查詢記憶體洩漏可以使用Android Studio 自帶的AndroidProfiler工具或MAT
7、效能優化,怎麼保證應用啟動不卡頓? 黑白屏怎麼處理?
- 參考回答:
- 應用啟動速度,取決於你在application裡面時候做了什麼事情,比如你整合了很多sdk,並且sdk的init操作都需要在主執行緒裡實現所以會有卡頓的感覺。在非必要的情況下可以把載入延後或則開啟子執行緒處理
- 另外,影響介面卡頓的兩大因素,分別是介面繪製和資料處理。
- 佈局優化(使用include,merge標籤,複雜佈局推薦使用ConstraintLayout等)
- onCreate() 中不執行耗時操作 把頁面顯示的 View 細分一下,放在 AsyncTask 裡逐步顯示,用 Handler 更好。這樣使用者的看到的就是有層次有步驟的一個個的 View 的展示,不會是先看到一個黑屏,然後一下顯示所有 View。最好做成動畫,效果更自然。
- 利用多執行緒的目的就是儘可能的減少 onCreate() 和 onReume() 的時間,使得使用者能儘快看到頁面,操作頁面。
- 減少主執行緒阻塞時間。
- 提高 Adapter 和 AdapterView 的效率。
- 推薦文章:Android 效能優化之記憶體檢測、卡頓優化、耗電優化、APK瘦身
- 黑白屏產生原因:當我們在啟動一個應用時,系統會去檢查是否已經存在這樣一個程式,如果不存在,系統的服務會先檢查startActivity中的intent的資訊,然後在去建立程式,最後啟動Acitivy,即冷啟動。而啟動出現白黑屏的問題,就是在這段時間內產生的。系統在繪製頁面載入佈局之前,首先會初始化視窗(Window),而在進行這一步操作時,系統會根據我們設定的Theme來指定它的Theme 主題顏色,我們在Style中的設定就決定了顯示的是白屏還是黑屏。
- windowIsTranslucent和windowNoTitle,將這兩個屬性都設定成true (會有明顯的卡頓體驗,不推薦)
- 如果啟動頁只是是一張圖片,那麼為啟動頁專一設定一個新的主題,設定主題的android:windowBackground屬性為啟動頁背景圖即可
- 使用layer-list製作一張圖片launcher_layer.xml,將其設定為啟動頁專一主題的背景,並將其設定為啟動頁佈局的背景。
- 推薦文章:Android啟動頁解決攻略
8、強引用置為null,會不會被回收?
- 參考回答:
- 不會立即釋放物件佔用的記憶體。 如果物件的引用被置為null,只是斷開了當前執行緒棧幀中對該物件的引用關係,而 垃圾收集器是執行在後臺的執行緒,只有當使用者執行緒執行到安全點(safe point)或者安全區域才會掃描物件引用關係,掃描到物件沒有被引用則會標記物件,這時候仍然不會立即釋放該物件記憶體,因為有些物件是可恢復的(在 finalize方法中恢復引用 )。只有確定了物件無法恢復引用的時候才會清除物件記憶體。
9、ListView跟RecyclerView的區別
- 參考回答:
- 動畫區別:
- 在RecyclerView中,內建有許多動畫API,例如:notifyItemChanged(), notifyDataInserted(), notifyItemMoved()等等;如果需要自定義動畫效果,可以通過實現(RecyclerView.ItemAnimator類)完成自定義動畫效果,然後呼叫RecyclerView.setItemAnimator();
- 但是ListView並沒有實現動畫效果,但我們可以在Adapter自己實現item的動畫效果;
- 重新整理區別:
- ListView中通常重新整理資料是用全域性重新整理notifyDataSetChanged(),這樣一來就會非常消耗資源;本身無法實現區域性重新整理,但是如果要在ListView實現區域性重新整理,依然是可以實現的,當一個item資料重新整理時,我們可以在Adapter中,實現一個onItemChanged()方法,在方法裡面獲取到這個item的position(可以通過getFirstVisiblePosition()),然後呼叫getView()方法來重新整理這個item的資料;
- RecyclerView中可以實現區域性重新整理,例如:notifyItemChanged();
- 快取區別:
- RecyclerView比ListView多兩級快取,支援多個離ItemView快取,支援開發者自定義快取處理邏輯,支援所有RecyclerView共用同一個RecyclerViewPool(快取池)。
- ListView和RecyclerView快取機制基本一致,但快取使用不同
- 推薦文章:
10、ListView的adapter是什麼adapter
- 參考回答:
- BaseAdapter:抽象類,實際開發中我們會繼承這個類並且重寫相關方法,用得最多的一個介面卡!
- ArrayAdapter:支援泛型操作,最簡單的一個介面卡,只能展現一行文字〜
- SimpleAdapter:同樣具有良好擴充套件性的一個介面卡,可以自定義多種效果!
- SimpleCursorAdapter:用於顯示簡單文字型別的listView,一般在資料庫那裡會用到,不過有點過時,不推薦使用!
11、LinearLayout、FrameLayout、RelativeLayout效能對比,為什麼?
- 參考回答:
- RelativeLayout會讓子View呼叫2次onMeasure,LinearLayout 在有weight時,也會呼叫子 View 2次onMeasure
- RelativeLayout的子View如果高度和RelativeLayout不同,則會引發效率問題,當子View很複雜時,這個問題會更加嚴重。如果可以,儘量使用padding代替margin。
- 在不影響層級深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。
JNI
1、對JNI是否瞭解
- 參考回答:
- Java的優點是跨平臺,但也因為其跨平臺的的特性導致其本地互動的能力不夠強大,一些和作業系統相關的的特性Java無法完成,於是Java提供JNI專門用於和原生程式碼互動,通過JNI,使用者可以呼叫C、C++編寫的原生程式碼
- 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來實現過什麼功能 ? 怎麼實現的 ?(加密處理、影音方面、圖形影像處理)
- 參考回答:
設計模式
1、你所知道的設計模式有哪些?
- 參考回答:
- 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
- 結構型模式,共七種:介面卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
- 行為型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄 模式、狀態模式、訪問者模式、中介者模式、直譯器模式。
2、談談MVC、MVP和MVVM,好在哪裡,不好在哪裡 ?
- 參考回答:
- MVC:
- 檢視層(View) 對應於xml佈局檔案和java程式碼動態view部分
- 控制層(Controller) MVC中Android的控制層是由Activity來承擔的,Activity本來主要是作為初始化頁面,展示資料的操作,但是因為XML檢視功能太弱,所以Activity既要負責檢視的顯示又要加入控制邏輯,承擔的功能過多。
- 模型層(Model) 針對業務模型,建立資料結構和相關的類,它主要負責網路請求,資料庫處理,I/O的操作。
- 總結
- 具有一定的分層,model徹底解耦,controller和view並沒有解耦層與層之間的互動儘量使用回撥或者去使用訊息機制去完成,儘量避免直接持有 controller和view在android中無法做到徹底分離,但在程式碼邏輯層面一定要分清業務邏輯被放置在model層,能夠更好的複用和修改增加業務。
- MVP
- 通過引入介面BaseView,讓相應的檢視元件如Activity,Fragment去實現BaseView,實現了檢視層的獨立,通過中間層Preseter實現了Model和View的完全解耦。MVP徹底解決了MVC中View和Controller傻傻分不清楚的問題,但是隨著業務邏輯的增加,一個頁面可能會非常複雜,UI的改變是非常多,會有非常多的case,這樣就會造成View的介面會很龐大。
- MVVM
- MVP中我們說過隨著業務邏輯的增加,UI的改變多的情況下,會有非常多的跟UI相關的case,這樣就會造成View的介面會很龐大。而MVVM就解決了這個問題,通過雙向繫結的機制,實現資料和UI內容,只要想改其中一方,另一方都能夠及時更新的一種設計理念,這樣就省去了很多在View層中寫很多case的情況,只需要改變資料就行。
- 三者如何選擇?
- 如果專案簡單,沒什麼複雜性,未來改動也不大的話,那就不要用設計模式或者架構方法,只需要將每個模組封裝好,方便呼叫即可,不要為了使用設計模式或架構方法而使用。
- 對於偏向展示型的app,絕大多數業務邏輯都在後端,app主要功能就是展示資料,互動等,建議使用mvvm。
- 對於工具類或者需要寫很多業務邏輯app,使用mvp或者mvvm都可。
- 推薦文章:MVC、MVP、MVVM,我到底該怎麼選?
3、封裝p層之後.如果p層資料過大,如何解決?
- 參考回答:
- 對於MVP模式來說,P層如果資料邏輯過於臃腫,建議引入RxJava或則Dagger,越是複雜的邏輯,越能體現RxJava的優越性
- 推薦文章:(仿有道精品課)RxJava+OkHttp+Retrofit+Dagger2+MVP框架(kotlin版本)
4、是否能從Android中舉幾個例子說說用到了什麼設計模式 ?
- 參考回答:
- AlertDialog、Notification原始碼中使用了Builder(建造者)模式完成引數的初始化
- Okhttp內部使用了責任鏈模式來完成每個Interceptor攔截器的呼叫
- RxJava的觀察者模式;單例模式;GridView的介面卡模式;Intent的原型模式
- 日常開發的BaseActivity抽象工廠模式
5、裝飾模式和代理模式有哪些區別 ?
- 參考回答:
- 裝飾器模式與代理模式的區別就在於
- 兩者都是對類的方法進行擴充套件,但裝飾器模式強調的是增強自身,在被裝飾之後你能夠在被增強的類上使用增強後的功能。
- 而代理模式則強調要讓別人幫你去做一些本身與你業務沒有太多關係的職責(記錄日誌、設定快取)代理模式是為了實現物件的控制,因為被代理的物件往往難以直接獲得或者是其內部不想暴露出來。
6、實現單例模式有幾種方法 ?懶漢式中雙層鎖的目的是什麼 ?兩次判空的目的又是什麼 ?
- 參考回答:
- 單例模式實現方法有多種:餓漢,懶漢(執行緒安全,執行緒非安全),雙重檢查(DCL),內部類,以及列舉
- 所謂雙層檢驗鎖(在加鎖前後對例項物件進行兩次判空的檢驗):加鎖是為了第一次物件例項化的執行緒同步,而鎖內還要有第二層判空是因為可能會有多個執行緒進入第一層if判斷內部,而在加鎖程式碼塊外排隊等候,如果鎖內不進行第二次檢驗,仍然會出現例項化多個物件的情況。
- 推薦文章:單例模式的總結
7、用到的一些開源框架,介紹一個看過原始碼的,內部實現過程。
- 參考回答:
- 面試常客:Okhttp,Retrofit,Glide,RxJava,GreenDao,Dagger等
- 推薦文章:
8、Fragment如果在Adapter中使用應該如何解耦?
- 參考回答:
- 介面回撥
- 廣播
你當前所處:Android篇:2019初中級Android開發社招面試解答(中)