史上最全!押題率90%的 Android 中高階工程師面試複習大綱及真題答案整理(中篇)
緣起
轉眼間2020就接近尾聲了,年後有跳槽想法的小夥伴們心裡應該也有自己的決定了。金三銀四青銅五,總不能到跳槽的黃金期再開始複習吧。沒辦法,都是兄弟,寵著!2020年度Android中高階面試複習大全奉上。
篇幅過長,預計分三篇文章講解,好兄弟們記得點個關注或者點贊Mark插個眼,後續不容錯過哦
上一篇Java基礎,計算機網路相關面試題點這裡:
史上最全!押題率90%的 Android 中高階工程師面試複習大綱及真題答案整理(上篇)
2、Android 基礎
1、什麼是ANR 如何避免它?
答:在Android上,如果你的應用程式有一段時間響應不夠靈敏,系統會向用戶顯示一個對話方塊,這個對話方塊稱作應 用程式無響應(ANR:Application NotResponding)對話方塊。 使用者可以選擇讓程式繼續執行,但是,他們在使用你的 應用程式時,並不希望每次都要處理這個對話方塊。因此 ,在程式裡對響應效能的設計很重要這樣,這樣系統就不會顯 示ANR給使用者。
不同的元件發生ANR的時間不一樣,Activity是5秒,BroadCastReceiver是10秒,Service是20秒(均為前臺)。
如果開發機器上出現問題,我們可以通過檢視/data/anr/traces.txt即可,最新的ANR資訊在最開始部分。
- 主執行緒被IO操作(從4.0之後網路IO不允許在主執行緒中)阻塞。
- 主執行緒中存在耗時的計算
- 主執行緒中錯誤的操作,比如Thread.wait或者Thread.sleep等 Android系統會監控程式的響應狀況,一旦出現下面兩種情況,則彈出ANR對話方塊
- 應用在5秒內未響應使用者的輸入事件(如按鍵或者觸控)
- BroadcastReceiver未在10秒內完成相關的處理
- Service在特定的時間內無法處理完成 20秒
修正:
1、使用AsyncTask處理耗時IO操作。
2、使用Thread或者HandlerThread時,呼叫Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)設定優先順序,否則仍然會降低程式響應,因為預設Thread的優先順序和主執行緒相同。
3、使用Handler處理工作執行緒結果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主執行緒。
4、Activity的onCreate和onResume回撥中儘量避免耗時的程式碼。 BroadcastReceiver中onReceive程式碼也要儘量減少耗時,建議使用IntentService處理。
解決方案:
將所有耗時操作,比如訪問網路,Socket通訊,查詢大 量SQL 語句,複雜邏輯計算等都放在子執行緒中去,然 後通過handler.sendMessage、runonUIThread、AsyncTask、RxJava等方式更新UI。無論如何都要確保使用者介面的流暢 度。如果耗時操作需要讓使用者等待,那麼可以在介面上顯示度條。
2、Activity和Fragment生命週期有哪些?
3、橫豎屏切換時候Activity的生命週期
- 1)、Android 3.2 (API 13) 之前:
- 不設定 Activity 的 android:configChanges 時,切屏會重新呼叫生命週期,切橫屏會呼叫一次,切豎屏會呼叫兩次。
- 設定 Activity 的 android:configChanges="orientation" 時,切屏會重新呼叫生命週期,且橫豎屏都是呼叫一次生命週期。
- 設定 Activity 的 android:configChanges="orientation|keyboardHidden" 時,切屏不會重新呼叫 Activity 的生命週期,但是會呼叫 onConfigurationChanges() 方法。
- 2)、從Android 3.2 (API 13) 開始
- 不設定 Activity 的 android:configChanges 時、設定 Activity 的 android:configChanges="orientation"
- 設定 Activity 的 android:configChanges="orientaion|keyboardHidden"時切換橫屏和豎屏都會重新呼叫一次生命週期。
- 設定 Activity 的 android:configChanges="orientation|screenSize"時不會重新呼叫 Activity 的生命週期,但是會呼叫 onConfigurationChanges() 方法。
4、AsyncTask的缺陷和問題,說說他的原理。
AsyncTask是什麼?
AsyncTask是一種輕量級的非同步任務類,它可以線上程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主執行緒並在主執行緒中更新UI。
AsyncTask是一個抽象的泛型類,它提供了Params、Progress和Result這三個泛型引數,其中Params表示引數的型別,Progress表示後臺任務的執行進度和型別,而Result則表示後臺任務的返回結果的型別,如果AsyncTask不需要傳遞具體的引數,那麼這三個泛型引數可以用Void來代替。
關於執行緒池:
AsyncTask對應的執行緒池ThreadPoolExecutor都是程序範圍內共享的,且都是static的,所以是Asynctask控制著程序範圍內所有的子類例項。由於這個限制的存在,當使用預設執行緒池時,如果執行緒數超過執行緒池的最大容量,執行緒池就會爆掉(3.0後預設序列執行,不會出現個問題)。針對這種情況,可以嘗試自定義執行緒池,配合Asynctask使用。
關於預設執行緒池:
AsyncTask裡面執行緒池是一個核心執行緒數為CPU + 1,最大執行緒數為CPU * 2 + 1,工作佇列長度為128的執行緒池,執行緒等待佇列的最大等待數為28,但是可以自定義執行緒池。執行緒池是由AsyncTask來處理的,執行緒池允許tasks並行執行,需要注意的是併發情況下資料的一致性問題,新資料可能會被老資料覆蓋掉。所以希望tasks能夠序列執行的話,使用SERIAL_EXECUTOR。
AsyncTask在不同的SDK版本中的區別:
呼叫AsyncTask的execute方法不能立即執行程式的原因及改善方案通過查閱官方文件發現,AsyncTask首次引入時,非同步任務是在一個獨立的執行緒中順序的執行,也就是說一次只執行一個任務,不能並行的執行,從1.6開始,AsyncTask引入了執行緒池,支援同時執行5個非同步任務,也就是說只能有5個執行緒執行,超過的執行緒只能等待,等待前的執行緒直到某個執行完了才被排程和執行。換句話說,如果程序中的AsyncTask例項個數超過5個,那麼假如前5都執行很長時間的話,那麼第6個只能等待機會了。這是AsyncTask的一個限制,而且對於2.3以前的版本無法解決。如果你的應用需要大量的後臺執行緒去執行任務,那麼只能放棄使用AsyncTask,自己建立執行緒池來管理Thread。不得不說,雖然AsyncTask較Thread使用起來方便,但是它最多隻能同時執行5個執行緒,這也大大侷限了它的作用,你必須要小心設計你的應用,錯開使用AsyncTask時間,盡力做到分時,或者保證數量不會大於5個,否就會遇到上面提到的問題。可能是Google意識到了AsynTask的侷限性了,從Android 3.0開始對AsyncTask的API做出了一些調整:每次只啟動一個執行緒執行一個任務,完了之後再執行第二個任務,也就是相當於只有一個後臺執行緒在執行所提交的任務。
一些問題:
1.生命週期
很多開發者會認為一個在Activity中建立的AsyncTask會隨著Activity的銷燬而銷燬。然而事實並非如此。AsynTask會一直執行,直到doInBackground()方法執行完畢,然後,如果cancel(boolean)被呼叫,那麼onCancelled(Result result)方法會被執行;否則,執行onPostExecute(Result result)方法。如果我們的Activity銷燬之前,沒有取消AsyncTask,這有可能讓我們的應用崩潰(crash)。因為它想要處理的view已經不存在了。所以,我們是必須確保在銷燬活動之前取消任務。總之,我們使用AsyncTask需要確保AsyncTask正確的取消。
2.記憶體洩漏
如果AsyncTask被宣告為Activity的非靜態內部類,那麼AsyncTask會保留一個對Activity的引用。如果Activity已經被銷燬,AsyncTask的後臺執行緒還在執行,它將繼續在記憶體裡保留這個引用,導致Activity無法被回收,引起記憶體洩漏。
3.結果丟失
螢幕旋轉或Activity在後臺被系統殺掉等情況會導致Activity的重新建立,之前執行的AsyncTask會持有一個之前Activity的引用,這個引用已經無效,這時呼叫onPostExecute()再去更新介面將不再生效。
4.並行還是序列
在Android1.6之前的版本,AsyncTask是序列的,在1.6之後的版本,採用執行緒池處理並行任務,但是從Android 3.0開始,為了避免AsyncTask所帶來的併發錯誤,又採用一個執行緒來序列執行任務。可以使用executeOnExecutor()方法來並行地執行任務。
AsyncTask原理
- AsyncTask中有兩個執行緒池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler),其中執行緒池SerialExecutor用於任務的排隊,而執行緒池THREAD_POOL_EXECUTOR用於真正地執行任務,InternalHandler用於將執行環境從執行緒池切換到主執行緒。
- InternalHandler是一個靜態的Handler物件,為了能夠將執行環境切換到主執行緒,這就要求sHandler這個物件必須在主執行緒建立。由於靜態成員會在載入類的時候進行初始化,因此這就變相要求AsyncTask的類必須在主執行緒中載入,否則同一個程序中的AsyncTask都將無法正常工作。
5、onSaveInstanceState() 與 onRestoreIntanceState()
Activity的 onSaveInstanceState() 和 onRestoreInstanceState()並不是生命週期方法,它們不同於 onCreate()、onPause()等生命週期方法,它們並不一定會被觸發。當應用遇到意外情況(如:記憶體不足、使用者直接按Home鍵)由系統銷燬一個Activity時,onSaveInstanceState() 會被呼叫。但是當用戶主動去銷燬一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被呼叫。因為在這種情況下,使用者的行為決定了不需要儲存Activity的狀態。通常onSaveInstanceState()只適合用於儲存一些臨時性的狀態,而onPause()適合用於資料的持久化儲存。 在activity被殺掉之前呼叫儲存每個例項的狀態,以保證該狀態可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (傳入的Bundle引數是由onSaveInstanceState封裝好的)中恢復。這個方法在一個activity被殺死前呼叫,當該activity在將來某個時刻回來時可以恢復其先前狀態。 例如,如果activity B啟用後位於activity A的前端,在某個時刻activity A因為系統回收資源的問題要被殺掉,A通過onSaveInstanceState將有機會儲存其使用者介面狀態,使得將來使用者返回到activity A時能通過onCreate(Bundle)或者onRestoreInstanceState(Bundle)恢復介面的狀態
6、android中程序的優先順序?
1. 前臺程序:
即與使用者正在互動的Activity或者Activity用到的Service等,如果系統記憶體不足時前臺程序是最晚被殺死的
2. 可見程序:
可以是處於暫停狀態(onPause)的Activity或者繫結在其上的Service,即被使用者可見,但由於失了焦點而不能與使用者互動
3. 服務程序:
其中執行著使用startService方法啟動的Service,雖然不被使用者可見,但是卻是使用者關心的,例如使用者正在非音樂介面聽的音樂或者正在非下載頁面下載的檔案等;當系統要空間執行,前兩者程序才會被終止
4. 後臺程序:
其中執行著執行onStop方法而停止的程式,但是卻不是使用者當前關心的,例如後臺掛著的QQ,這時的程序系統一旦沒了有記憶體就首先被殺死
5. 空程序:
不包含任何應用程式的程序,這樣的程序系統是一般不會讓他存在的
7、Bunder傳遞物件為什麼需要序列化?Serialzable和Parcelable的區別?
因為bundle傳遞資料時只支援基本資料型別,所以在傳遞物件時需要序列化轉換成可儲存或可傳輸的本質狀態(位元組流)。序列化後的物件可以在網路、IPC(比如啟動另一個程序的Activity、Service和Reciver)之間進行傳輸,也可以儲存到本地。
Serializable(Java自帶):
Serializable 是序列化的意思,表示將一個物件轉換成儲存或可傳輸的狀態。序列化後的物件可以在網路上進傳輸,也可以儲存到本地。
Parcelable(android專用):
除了Serializable之外,使用Parcelable也可以實現相同的效果,不過不同於將物件進行序列化,Parcelable方式的實現原理是將一個完整的物件進行分解,而分解後的每一部分都是Intent所支援的資料型別,這也就實現傳遞物件的功能了。
區別總結如下圖所示:
8、動畫
- tween 補間動畫。通過指定View的初末狀態和變化方式,對View的內容完成一系列的圖形變換來實現動畫效果。 Alpha, Scale ,Translate, Rotate。
- frame 幀動畫。AnimationDrawable控制animation-list.xml佈局
- PropertyAnimation 屬性動畫3.0引入,屬性動畫核心思想是對值的變化。
Property Animation 動畫有兩個步聚:
1.計算屬性值
2.為目標物件的屬性設定屬性值,即應用和重新整理動畫
計算屬性分為3個過程:
過程一:
計算已完成動畫分數 elapsed fraction。為了執行一個動畫,你需要建立一個ValueAnimator,並且指定目標物件屬性的開始、結束和持續時間。在呼叫 start 後的整個動畫過程中,ValueAnimator 會根據已經完成的動畫時間計算得到一個0 到 1 之間的分數,代表該動畫的已完成動畫百分比。0表示 0%,1 表示 100%。
過程二:
計算插值(動畫變化率)interpolated fraction 。當 ValueAnimator計算完已完成的動畫分數後,它會呼叫當前設定的TimeInterpolator,去計算得到一個interpolated(插值)分數,在計算過程中,已完成動畫百分比會被加入到新的插值計算中。
過程三:
計算屬性值當插值分數計算完成後,ValueAnimator會根據插值分數呼叫合適的 TypeEvaluator去計算運動中的屬性值。 以上分析引入了兩個概念:已完成動畫分數(elapsed fraction)、插值分數( interpolated fraction )。
原理及特點:
1.屬性動畫:
插值器:作用是根據時間流逝的百分比來計算屬性變化的百分比
估值器:在1的基礎上由這個東西來計算出屬性到底變化了多少數值的類
其實就是利用插值器和估值器,來計出各個時刻View的屬性,然後通過改變View的屬性來實現View的動畫效果。
2.View動畫:
只是影像變化,view的實際位置還在原來地方。
3.幀動畫:
是在xml中定義好一系列圖片之後,使用AnimatonDrawable來播放的動畫。
它們的區別:
屬性動畫才是真正的實現了 view 的移動,補間動畫對view 的移動更像是在不同地方繪製了一個影子,實際物件還是處於原來的地方。 當動畫的 repeatCount 設定為無限迴圈時,如果在Activity退出時沒有及時將動畫停止,屬性動畫會導致Activity無法釋放而導致記憶體洩漏,而補間動畫卻沒問題。 xml 檔案實現的補間動畫,複用率極高。在 Activity切換,視窗彈出時等情景中有著很好的效果。 使用幀動畫時需要注意,不要使用過多特別大的圖,容導致記憶體不足。
為什麼屬性動畫移動後仍可點選?
播放補間動畫的時候,我們所看到的變化,都只是臨時的。而屬性動畫呢,它所改變的東西,卻會更新到這個View所對應的矩陣中,所以當ViewGroup分派事件的時候,會正確的將當前觸控座標,轉換成矩陣變化後的座標,這就是為什麼播放補間動畫不會改變觸控區域的原因了。
9、Context相關
-
1、Activity和Service以及Application的Context是不一樣的,Activity繼承自ContextThemeWraper.其他的繼承自ContextWrapper。
-
2、每一個Activity和Service以及Application的Context是一個新的ContextImpl物件。
-
3、getApplication()用來獲取Application例項的,但是這個方法只有在Activity和Service中才能呼叫的到。那也許在絕大多數情況下我們都是在Activity或者Servic中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的例項,這時就可以藉助getApplicationContext()方法,getApplicationContext()比getApplication()方法的作用域會更廣一些,任何一個Context的例項,只要呼叫getApplicationContext()方法都可以拿到我們的Application物件。
-
4、建立對話方塊時不可以用Application的context,只能用Activity的context。
-
5、Context的數量等於Activity的個數 + Service的個數 +1,這個1為Application。
10、Android各版本新特性
Android5.0新特性
- MaterialDesign設計風格
- 支援64位ART虛擬機器(5.0推出的ART虛擬機器,在5.0之前都是Dalvik。他們的區別是:
Dalvik,每次執行,位元組碼都需要通過即時編譯器轉換成機器碼(JIT)。 ART,第一次安裝應用的時候,位元組碼就會預先編譯成機器碼(AOT))
- 通知詳情可以使用者自己設計
Android6.0新特性
-
動態許可權管理
-
支援快速充電的切換
-
支援資料夾拖拽應用
-
相機新增專業模式
Android7.0新特性
-
多視窗支援
-
V2簽名
-
增強的Java8語言模式
-
夜間模式
Android8.0(O)新特性
-
優化通知
通知渠道 (Notification Channel) 通知標誌 休眠 通知超時 通知設定 通知清除
-
畫中畫模式:清單中Activity設定android:supportsPictureInPicture
-
後臺限制
-
自動填充框架
-
系統優化
-
等等優化很多
Android9.0(P)新特性
-
室內WIFI定位
-
“劉海”螢幕支援
-
安全增強
-
等等優化很多
Android10.0(Q)目前曝光的新特性
- 夜間模式:包括手機上的所有應用都可以為其設定暗黑模式。
- 桌面模式:提供類似於PC的體驗,但是遠遠不能代替PC。
- 螢幕錄製:通過長按“電源”選單中的"螢幕快照"來開啟。
11、Json
JSON的全稱是JavaScript Object Notation,也就是JavaScript 物件表示法 JSON是儲存和交換文字資訊的語法,類似XML,但是比XML更小、更快,更易解析 JSON是輕量級的文字資料交換格式,獨立於語言,具有可描述性,更易理解,物件可以包含多個名稱/值對,比如:
{"name":"zhangsan" , "age":25}
使用谷歌的GSON包進行解析,在 Android Studio 裡引入依賴:
compile 'com.google.code.gson:gson:2.7'
值得注意的是實體類中變數名稱必須和json中的值名字相同。
使用示例:
1、解析成實體類:
Gson gson = new Gson();
Student student = gson.fromJson(json1, Student.class);
2、解析成int陣列:
Gson gson = new Gson();
int[] ages = gson.fromJson(json2, int[].class);
3、直接解析成List.
Gson gson = new Gson();
List<Integer> ages = gson.fromJson(json2, newTypeToken<List<Integer>>(){}.getType);
Gson gson = new Gson();
List<Student> students = gson.fromJson(json3, newTypeToke<List<Student>>(){}.getType);
優點:
- 輕量級的資料交換格式
- 讀寫更加容易
- 易於機器的解析和生成
缺點:
- 語義性較差,不如 xml 直觀
12、android中有哪幾種解析xml的類,官方推薦哪種?以及它們的原理和區別?
DOM解析
優點:
1.XML樹在記憶體中完整儲存,因此可以直接修改其資料結構.
2.可以通過該解析器隨時訪問XML樹中的任何一個節點.
3.DOM解析器的API在使用上也相對比較簡單.
缺點:
如果XML文件體積比較大時,將文件讀入記憶體是非消耗系統資源的.
使用場景:
- DOM 是與平臺和語言無關的方式表示 XML文件的官方 W3C 標準.
- DOM 是以層次結構組織的節點的集合.這個層次結構允許開人員在樹中尋找特定資訊.分析該結構通常需要載入整個文件和構造層次結構,然後才能進行任何工作.
- DOM 是基於物件層次結構的.
SAX解析
優點:
SAX 對記憶體的要求比較低,因為它讓開發人員自己來決定所要處理的標籤.特別是當開發人員只需要處理文件中包含的部分資料時,SAX 這種擴充套件能力得到了更好的體現.
缺點:
用SAX方式進行XML解析時,需要順序執行,所以很難訪問同一文件中的不同資料.此外,在基於該方式的解析編碼程式也相對複雜.
使用場景:
對於含有資料量十分巨大,而又不用對文件的所有資料行遍歷或者分析的時候,使用該方法十分有效.該方法不將整個文件讀入記憶體,而只需讀取到程式所需的文件標記處即可.
Xmlpull解析
android SDK提供了xmlpullapi,xmlpull和sax類似,是基於流(stream)操作檔案,後者根據節點事件回撥開發者編寫的處理程式.因為是基於流的處理,因此xmlpull和sax都比較節約記憶體資源,不會像dom那樣要把所有節點以物件樹的形式展現在記憶體中.xmpull比sax更簡明,而且不需要掃描完整個流.
13、Jar和Aar的區別
Jar包裡面只有程式碼,aar裡面不光有程式碼還包括資原始檔,比如 drawable 檔案,xml資原始檔。對於一些不常變動的 Android Library,我們可以直接引用 aar,加快編譯速度。
14、Android為每個應用程式分配的記憶體大小是多少
android程式記憶體一般限制在16M,也有的是24M。近幾年手機發展較快,一般都會分配兩百兆左右,和具體機型有關。
15、更新UI方式
- Activity.runOnUiThread(Runnable)
- View.post(Runnable),View.postDelay(Runnable, long)(可以理解為在當前操作檢視UI執行緒新增佇列)
- Handler
- AsyncTask
- Rxjava
- LiveData
16、ContentProvider使用方法。
進行跨程序通訊,實現程序間的資料互動和共享。通過Context 中 getContentResolver() 獲得例項,通過 Uri匹配進行資料的增刪改查。ContentProvider使用表的形式來組織資料,無論資料的來源是什麼,ConentProvider 都會認為是一種表,然後把資料組織成表格。
17、Thread、AsyncTask、IntentService的使用場景與特點。
-
Thread執行緒,獨立執行與於 Activity 的,當Activity 被 finish 後,如果沒有主動停止 Thread或者 run 方法沒有執行完,其會一直執行下去。
-
AsyncTask 封裝了兩個執行緒池和一個Handler(SerialExecutor用於排隊,THREAD_POOL_EXECUTOR為真正的執行任務,Handler將工作執行緒切換到主執行緒),其必須在 UI執行緒中建立,execute 方法必須在 UI執行緒中執行,一個任務例項只允許執行一次,執行多次丟擲異常,用於網路請求或者簡單資料處理。
-
IntentService:處理非同步請求,實現多執行緒,在onHandleIntent中處理耗時操作,多個耗時任務會依次執行,執行完畢自動結束。
18、Merge、ViewStub 的作用。
Merge: 減少檢視層級,可以刪除多餘的層級。
ViewStub: 按需載入,減少記憶體使用量、加快渲染速度、不支援 merge 標籤。
19、activity的startActivity和context的startActivity區別?
(1)、從Activity中啟動新的Activity時可以直接mContext.startActivity(intent)就好
(2)、如果從其他Context中啟動Activity則必須給intent設定Flag:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ;
mContext.startActivity(intent);
20、怎麼在Service中建立Dialog對話方塊?
1.在我們取得Dialog物件後,需給它設定型別,即:
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
2.在Manifest中加上許可權:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINOW" />
21、Asset目錄與res目錄的區別?
assets:不會在 R 檔案中生成相應標記,存放到這裡的資源在打包時會打包到程式安裝包中。(通過 AssetManager 類訪問這些檔案)
res:會在 R 檔案中生成 id 標記,資源在打包時如果使用到則打包到安裝包中,未用到不會打入安裝包中。
res/anim:存放動畫資源。
res/raw:和 asset 下檔案一樣,打包時直接打入程式安裝包中(會對映到 R 檔案中)。
22、Android怎麼加速啟動Activity?
- onCreate() 中不執行耗時操作
把頁面顯示的 View 細分一下,放在 AsyncTask 裡逐步顯示,用 Handler 更好。這樣使用者的看到的就是有層次有步驟的一個個的 View 的展示,不會是先看到一個黑屏,然後一下顯示所有 View。最好做成動畫,效果更自然。
-
利用多執行緒的目的就是儘可能的減少 onCreate() 和 onReume() 的時間,使得使用者能儘快看到頁面,操作頁面。
-
減少主執行緒阻塞時間。
-
提高 Adapter 和 AdapterView 的效率。
-
優化佈局檔案。
23、Handler機制
Android訊息迴圈流程圖如下所示:
主要涉及的角色如下所示:
- message:訊息。
- MessageQueue:訊息佇列,負責訊息的儲存與管理,負責管理由 Handler 傳送過來的 Message。讀取會自動刪除訊息,單鏈表維護,插入和刪除上有優勢。在其next()方法中會無限迴圈,不斷判斷是否有訊息,有就返回這條訊息並移除。
- Looper:訊息迴圈器,負責關聯執行緒以及訊息的分發,在該執行緒下從 MessageQueue獲取 Message,分發給Handler,Looper建立的時候會建立一個
MessageQueue,呼叫loop()方法的時候訊息迴圈開始,其中會不斷呼叫messageQueue的next()方法,當有訊息就處理,否則阻塞在messageQueue的next()方法中。當Looper的quit()被呼叫的時候會呼叫messageQueue的quit(),此時next()會返回null,然後loop()方法也就跟著退出。
- Handler:訊息處理器,負責傳送並處理訊息,面向開發者,提供 API,並隱藏背後實現的細節。
整個訊息的迴圈流程還是比較清晰的,具體說來:
- 1、Handler通過sendMessage()傳送訊息Message到訊息佇列MessageQueue。
- 2、Looper通過loop()不斷提取觸發條件的Message,並將Message交給對應的target handler來處理。
- 3、target handler呼叫自身的handleMessage()方法來處理Message。
事實上,在整個訊息迴圈的流程中,並不只有Java層參與,很多重要的工作都是在C++層來完成的。我們來看下這些類的呼叫關係。
注:虛線表示關聯關係,實線表示呼叫關係。
在這些類中MessageQueue是Java層與C++層維繫的橋樑,MessageQueue與Looper相關功能都通過MessageQueue的Native方法來完成,而其他虛線連線的類只有關聯關係,並沒有直接呼叫的關係,它們發生關聯的橋樑是MessageQueue。
總結
- Handler 傳送的訊息由 MessageQueue 儲存管理,並由 Looper 負責回撥訊息到 handleMessage()。
- 執行緒的轉換由 Looper 完成,handleMessage() 所線上程由 Looper.loop() 呼叫者所線上程決定。
Handler 引起的記憶體洩露原因以及最佳解決方案
Handler 允許我們傳送延時訊息,如果在延時期間使用者關閉了 Activity,那麼該 Activity 會洩露。 這個洩露是因為 Message 會持有 Handler,而又因為 Java 的特性,內部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導致 Activity 洩露。
解決:將 Handler 定義成靜態的內部類,在內部持有 Activity 的弱引用,並在Acitivity的onDestroy()中呼叫handler.removeCallbacksAndMessages(null)及時移除所有訊息。
為什麼我們能在主執行緒直接使用 Handler,而不需要建立 Looper ?
通常我們認為 ActivityThread 就是主執行緒。事實上它並不是一個執行緒,而是主執行緒操作的管理者。在 ActivityThread.main() 方法中呼叫了 Looper.prepareMainLooper() 方法建立了 主執行緒的 Looper ,並且呼叫了 loop() 方法,所以我們就可以直接使用 Handler 了。
因此我們可以利用 Callback 這個攔截機制來攔截 Handler 的訊息。如大部分外掛化框架中Hook ActivityThread.mH 的處理。
主執行緒的 Looper 不允許退出
主執行緒不允許退出,退出就意味 APP 要掛。
Handler 裡藏著的 Callback 能幹什麼?
Handler.Callback 有優先處理訊息的權利,當一條訊息被 Callback 處理並攔截(返回 true),那麼 Handler 的 handleMessage(msg)方法就不會被呼叫了;如果 Callback處理了訊息,但是並沒有攔截,那麼就意味著一個訊息可以同時被 Callback 以及 Handler 處理。
建立 Message 例項的最佳方式
為了節省開銷,Android 給 Message 設計了回收機制,所以我們在使用的時候儘量複用 Message ,減少記憶體消耗:
- 通過 Message 的靜態方法 Message.obtain();
- 通過 Handler 的公有方法 handler.obtainMessage()。
子執行緒裡彈 Toast 的正確姿勢
本質上是因為 Toast 的實現依賴於 Handler,按子執行緒使用 Handler 的要求修改即可,同理的還有 Dialog。
妙用 Looper 機制
- 將 Runnable post 到主執行緒執行;
- 利用 Looper 判斷當前執行緒是否是主執行緒。
主執行緒的死迴圈一直執行是不是特別消耗CPU資源呢?
並不是,這裡就涉及到Linux pipe/epoll機制,簡單說就是在主執行緒的MessageQueue沒有訊息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裡,此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生,通過往pipe管道寫端寫入資料來喚醒主執行緒工作。這裡採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質是同步I/O,即讀寫是阻塞的。所以說,主執行緒大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。
handler postDelay這個延遲是怎麼實現的?
handler.postDelay並不是先等待一定的時間再放入到MessageQueue中,而是直接進入MessageQueue,以MessageQueue的時間順序排列和喚醒的方式結合實現的。
如何保證在msg.postDelay情況下保證訊息次序?
24、程式A能否接收到程式B的廣播?
能,使用全域性的BroadCastRecevier能進行跨程序通訊,但是注意它只能被動接收廣播。此外,LocalBroadCastRecevier只限於本程序的廣播間通訊。
25、資料載入更多涉及到分頁,你是怎麼實現的?
分頁載入就是一頁一頁載入資料,當滑動到底部、沒有更多資料載入的時候,我們可以手動呼叫介面,重新重新整理RecyclerView。
26、通過google提供的Gson解析json時,定義JavaBean的規則是什麼?
1). 實現序列化 Serializable
2). 屬性私有化,並提供get,set方法
3). 提供無參構造
4). 屬性名必須與json串中屬性名保持一致 (因為Gson解析json串底層用到了Java的反射原理)
27、json解析方式的兩種區別?
1,SDK提供JSONArray,JSONObject
2,google提供的 Gson 通過fromJson()實現物件的反序列化(即將json串轉換為物件型別) 通過toJson()實現物件的序列化 (即將物件型別轉換為json串)
28、執行緒池的相關知識。
Android中的執行緒池都是直接或間接通過配置ThreadPoolExecutor來實現不同特性的執行緒池.Android中最常見的類具有不同特性的執行緒池分別為FixThreadPool、CachedhreadPool、SingleThreadPool、ScheduleThreadExecutr.
1).FixThreadPool
只有核心執行緒,並且數量固定的,也不會被回收,所有執行緒都活動時,因為佇列沒有限制大小,新任務會等待執行.
優點:更快的響應外界請求.
2).SingleThreadPool
只有一個核心執行緒,確保所有的任務都在同一執行緒中按序完成.因此不需要處理執行緒同步的問題.
3).CachedThreadPool
只有非核心執行緒,最大執行緒數非常大,所有執行緒都活動時會為新任務建立新執行緒,否則會利用空閒執行緒(60s空閒時間,過了就會被回收,所以執行緒池中有0個執行緒的可能)處理任務.
優點:任何任務都會被立即執行(任務佇列SynchronousQuue相當於一個空集合);比較適合執行大量的耗時較少的任務.
4).ScheduledThreadPool
核心執行緒數固定,非核心執行緒(閒著沒活幹會被立即回收數)沒有限制.
優點:執行定時任務以及有固定週期的重複任務
29、記憶體洩露,怎樣查詢,怎麼產生的記憶體洩露?
1.資源物件沒關閉造成的記憶體洩漏
描述: 資源性物件比如(Cursor,File檔案等)往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收記憶體。它們的緩衝不僅存在於 java虛擬機器內,還存在於java虛擬機器外。如果我們僅僅是把它的引用設定為null,而不關閉它們,往往會造成記憶體洩漏。因為有些資源性物件,比如SQLiteCursor(在解構函式finalize(),如果我們沒有關閉它,它自己會調close()關閉),如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。因此對於資源性物件在不使用的時候,應該呼叫它的close()函式,將其關閉掉,然後才置為null.在我們的程式退出時一定要確保我們的資源性物件已經關閉。
程式中經常會進行查詢資料庫的操作,但是經常會有使用完畢Cursor後沒有關閉的情況。如果我們的查詢結果集比較小,對記憶體的消耗不容易被發現,只有在常時間大量操作的情況下才會復現記憶體問題,這樣就會給以後的測試和問題排查帶來困難和風險。
2.構造Adapter時,沒有使用快取的convertView
描述: 以構造ListView的BaseAdapter為例,在BaseAdapter中提供了方法: public View getView(int position, ViewconvertView, ViewGroup parent) 來向ListView提供每一個item所需要的view物件。初始時ListView會從BaseAdapter中根據當前的屏幕布局例項化一定數量的 view物件,同時ListView會將這些view物件快取起來。當向上滾動ListView時,原先位於最上面的list item的view物件會被回收,然後被用來構造新出現的最下面的list item。這個構造過程就是由getView()方法完成的,getView()的第二個形參View convertView就是被快取起來的list item的view物件(初始化時快取中沒有view物件則convertView是null)。由此可以看出,如果我們不去使用 convertView,而是每次都在getView()中重新例項化一個View物件的話,即浪費資源也浪費時間,也會使得記憶體佔用越來越大。 ListView回收list item的view物件的過程可以檢視: android.widget.AbsListView.java --> voidaddScrapView(View scrap) 方法。 示例程式碼:
public View getView(int position, ViewconvertView, ViewGroup parent) {
View view = new Xxx(...);
... ...
return view;
}
修正示例程式碼:
public View getView(int position, ViewconvertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
populate(view, getItem(position));
...
} else {
view = new Xxx(...);
...
}
return view;
}
3.Bitmap物件不在使用時呼叫recycle()釋放記憶體
描述: 有時我們會手工的操作Bitmap物件,如果一個Bitmap物件比較佔記憶體,當它不在被使用的時候,可以呼叫Bitmap.recycle()方法回收此物件的畫素所佔用的記憶體,但這不是必須的,視情況而定。可以看一下程式碼中的註釋:
/* •Free up the memory associated with thisbitmap's pixels, and mark the •bitmap as "dead", meaning itwill throw an exception if getPixels() or •setPixels() is called, and will drawnothing. This operation cannot be •reversed, so it should only be called ifyou are sure there are no •further uses for the bitmap. This is anadvanced call, and normally need •not be called, since the normal GCprocess will free up this memory when •there are no more references to thisbitmap. /
4.試著使用關於application的context來替代和activity相關的context
這是一個很隱晦的記憶體洩漏的情況。有一種簡單的方法來避免context相關的記憶體洩漏。最顯著地一個是避免context逃出他自己的範圍之外。使用Application context。這個context的生存週期和你的應用的生存週期一樣長,而不是取決於activity的生存週期。如果你想保持一個長期生存的物件,並且這個物件需要一個context,記得使用application物件。你可以通過呼叫 Context.getApplicationContext() or Activity.getApplication()來獲得。更多的請看這篇文章如何避免 Android記憶體洩漏。
5.註冊沒取消造成的記憶體洩漏
一些Android程式可能引用我們的Anroid程式的物件(比如註冊機制)。即使我們的Android程式已經結束了,但是別的引用程式仍然還有對我們的Android程式的某個物件的引用,洩漏的記憶體依然不能被垃圾回收。呼叫registerReceiver後未呼叫unregisterReceiver。 比如:假設我們希望在鎖屏介面(LockScreen)中,監聽系統中的電話服務以獲取一些資訊(如訊號強度等),則可以在LockScreen中定義一個 PhoneStateListener的物件,同時將它註冊到TelephonyManager服務中。對於LockScreen物件,當需要顯示鎖屏介面的時候就會建立一個LockScreen物件,而當鎖屏介面消失的時候LockScreen物件就會被釋放掉。 但是如果在釋放 LockScreen物件的時候忘記取消我們之前註冊的PhoneStateListener物件,則會導致LockScreen無法被垃圾回收。如果不斷的使鎖屏介面顯示和消失,則最終會由於大量的LockScreen物件沒有辦法被回收而引起OutOfMemory,使得system_process 程序掛掉。 雖然有些系統程式,它本身好像是可以自動取消註冊的(當然不及時),但是我們還是應該在我們的程式中明確的取消註冊,程式結束時應該把所有的註冊都取消掉。
6.集合中物件沒清理造成的記憶體洩漏
我們通常把一些物件的引用加入到了集合中,當我們不需要該物件時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
查詢記憶體洩漏可以使用Android Studio 自帶的AndroidProfiler工具或MAT,也可以使用Square產品的LeakCanary.
1、使用AndroidProfiler的MEMORY工具:
執行程式,對每一個頁面進行記憶體分析檢查。首先,反覆開啟關閉頁面5次,然後收到GC(點選Profile MEMORY左上角的垃圾桶圖示),如果此時total記憶體還沒有恢復到之前的數值,則可能發生了記憶體洩露。此時,再點選Profile MEMORY左上角的垃圾桶圖示旁的heap dump按鈕檢視當前的記憶體堆疊情況,選擇按包名查詢,找到當前測試的Activity,如果引用了多個例項,則表明發生了記憶體洩露。
2、使用MAT:
1、執行程式,所有功能跑一遍,確保沒有改出問題,完全退出程式,手動觸發GC,然後使用adb shell dumpsys meminfo packagename -d命令檢視退出介面後Objects下的Views和Activities數目是否為0,如果不是則通過Leakcanary檢查可能存在記憶體洩露的地方,最後通過MAT分析,如此反覆,改善滿意為止。
1、在使用MAT之前,先使用as的Profile中的Memory去獲取要分析的堆記憶體快照檔案.hprof,如果要測試某個頁面是否產生記憶體洩漏,可以先dump出沒進入該頁面的記憶體快照檔案.hprof,然後,通常執行5次進入/退出該頁面,然後再dump出此刻的記憶體快照檔案.hprof,最後,將兩者比較,如果記憶體相除明顯,則可能發生記憶體洩露。(注意:MAT需要標準的.hprof檔案,因此在as的Profiler中GC後dump出的記憶體快照檔案.hprof必須手動使用android sdk platform-tools下的hprof-conv程式進行轉換才能被MAT開啟)
2、然後,使用MAT開啟前面儲存的2份.hprof檔案,開啟Overview介面,在Overview介面下面有4中action,其中最常用的就是Histogram和Dominator Tree。
Dominator Tree:支配樹,按物件大小降序列出物件和其所引用的物件,注重引用關係分析。選擇Group by package,找到當前要檢測的類(或者使用頂部的Regex直接搜尋),檢視它的Object數目是否正確,如果多了,則判斷髮生了記憶體洩露。然後,右擊該類,選擇Merge Shortest Paths to GC Root中的exclude all phantom/weak/soft etc.references選項來檢視該類的GC強引用鏈。最後,通過引用鏈即可看到最終強引用該類的物件。
Histogram:直方圖注重量的分析。使用方式與Dominator Tree類似。
3、對比hprof檔案,檢測出複雜情況下的記憶體洩露:
通用對比方式:在Navigation History下面選擇想要對比的dominator_tree/histogram,右擊選擇Add to Compare Basket,然後在Compare Basket一欄中點選紅色感嘆號(Compare the results)生成對比表格(Compared Tables),在頂部Regex輸入要檢測的類,檢視引用關係或物件數量去進行分析即可。
針對於Historam的快速對比方式:直接選擇Histogram上方的Compare to another Heap Dump選擇要比較的hprof檔案的Historam即可。
30、類的初始化順序依次是?
(靜態變數、靜態程式碼塊)>(變數、程式碼塊)>構造方法
31、JSON的結構?
json是一種輕量級的資料交換格式, json簡單說就是物件和陣列,所以這兩種結構就是物件和陣列兩種結構,通過這兩種結構可以表示各種複雜的結構
1、物件:物件表示為“{}”擴起來的內容,資料結構為 {key:value,key:value,...}的鍵值對的結構,在面向物件的語言中,key為物件的屬性,value為對應的屬性值,所以很容易理解,取值方法為 物件.key 獲取屬性值,這個屬性值的型別可以是 數字、字串、陣列、物件幾種。
2、陣列:陣列在json中是中括號“[]”擴起來的內容,資料結構為 ["java","javascript","vb",...],取值方式和所有語言中一樣,使用索引獲取,欄位值的型別可以是 數字、字串、陣列、物件幾種。 經過物件、陣列2種結構就可以組合成複雜的資料結構了。
32、ViewPager使用細節,如何設定成每次只初始化當前的Fragment,其他的不初始化(提示:Fragment懶載入)?
自定義一個 LazyLoadFragment 基類,利用 setUserVisibleHint 和 生命週期方法,通過對 Fragment 狀態判斷,進行資料載入,並將資料載入的介面提供開放出去,供子類使用。然後在子類 Fragment 中實現 requestData 方法即可。這裡添加了一個 isDataLoaded 變數,目的是避免重複載入資料。考慮到有時候需要重新整理資料的問題,便提供了一個用於強制重新整理的引數判斷。
public abstract class LazyLoadFragment extends BaseFragment {
protected boolean isViewInitiated;
protected boolean isDataLoaded;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isViewInitiated = true;
prepareRequestData();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
prepareRequestData();
}
public abstract void requestData();
public boolean prepareRequestData() {
return prepareRequestData(false);
}
public boolean prepareRequestData(boolean forceUpdate) {
if (getUserVisibleHint() && isViewInitiated && (!isDataLoaded || forceUpdate)) {
requestData();
isDataLoaded = true;
return true;
}
return false;
}
}
35、Android為什麼引入Parcelable?
可以肯定的是,兩者都是支援序列化和反序列化的操作。
兩者最大的區別在於 儲存媒介的不同,Serializable 使用 I/O 讀寫儲存在硬碟上,而 Parcelable 是直接 在記憶體中讀寫。很明顯,記憶體的讀寫速度通常大於 IO 讀寫,所以在 Android 中傳遞資料優先選擇 Parcelable。
Serializable 會使用反射,序列化和反序列化過程需要大量 I/O 操作, Parcelable 自已實現封送和解封(marshalled &unmarshalled)操作不需要用反射,資料也存放在 Native 記憶體中,效率要快很多。
36、有沒有嘗試簡化Parcelable的使用?
使用Parcelable外掛(Android Parcelable code generator)進行實體類的序列化的實現。
37、Bitmap 使用時候注意什麼?
1、要選擇合適的圖片規格(bitmap型別):
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、壓縮圖片。
38、Oom 是否可以try catch ?
只有在一種情況下,這樣做是可行的:
在try語句中聲明瞭很大的物件,導致OOM,並且可以確認OOM是由try語句中的物件宣告導致的,那麼在catch語句中,可以釋放掉這些物件,解決OOM的問題,繼續執行剩餘語句。
但是這通常不是合適的做法。
Java中管理記憶體除了顯式地catch OOM之外還有更多有效的方法:比如SoftReference, WeakReference, 硬碟快取等。 在JVM用光記憶體之前,會多次觸發GC,這些GC會降低程式執行的效率。 如果OOM的原因不是try語句中的物件(比如記憶體洩漏),那麼在catch語句中會繼續丟擲OOM。
39、多程序場景遇見過麼?
1、在新的程序中,啟動前臺Service,播放音樂。 2、一個成熟的應用一定是多模組化的。首先多程序開發能為應用解決了OOM問題,因為Android對記憶體的限制是針對於程序的,所以,當我們需要載入大圖之類的操作,可以在新的程序中去執行,避免主程序OOM。而且假如圖片瀏覽程序打開了一個過大的圖片,java heap 申請記憶體失敗,該程序崩潰並不影響我主程序的使用。
40、Canvas.save()跟Canvas.restore()的呼叫時機
save:用來儲存Canvas的狀態。save之後,可以呼叫Canvas的平移、放縮、旋轉、錯切、裁剪等操作。
restore:用來恢復Canvas之前儲存的狀態。防止save後對Canvas執行的操作對後續的繪製有影響。
save和restore要配對使用(restore可以比save少,但不能多),如果restore呼叫次數比save多,會引發Error。save和restore操作執行的時機不同,就能造成繪製的圖形不同。
41、資料庫升級增加表和刪除表都不涉及資料遷移,但是修改表涉及到對原有資料進行遷移。升級的方法如下所示:
將現有表命名為臨時表。 建立新表。 將臨時表的資料匯入新表。 刪除臨時表。
如果是跨版本資料庫升級,可以有兩種方式,如下所示:
逐級升級,確定相鄰版本與現在版本的差別,V1升級到V2,V2升級到V3,依次類推。 跨級升級,確定每個版本與現在資料庫的差別,為每個case編寫專門升級大程式碼。
public class DBservice extends SQLiteOpenHelper{
private String CREATE_BOOK = "create table book(bookId integer primarykey,bookName text);";
private String CREATE_TEMP_BOOK = "alter table book rename to _temp_book";
private String INSERT_DATA = "insert into book select *,'' from _temp_book";
private String DROP_BOOK = "drop table _temp_book";
public DBservice(Context context, String name, CursorFactory factory,int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (newVersion) {
case 2:
db.beginTransaction();
db.execSQL(CREATE_TEMP_BOOK);
db.execSQL(CREATE_BOOK);
db.execSQL(INSERT_DATA);
db.execSQL(DROP_BOOK);
db.setTransactionSuccessful();
db.endTransaction();
break;
}
}
42、編譯期註解跟執行時註解
執行期註解(RunTime)利用反射去獲取資訊還是比較損耗效能的,對應@Retention(RetentionPolicy.RUNTIME)。
編譯期(Compile time)註解,以及處理編譯期註解的手段APT和Javapoet,對應@Retention(RetentionPolicy.CLASS)。 其中apt+javaPoet目前也是應用比較廣泛,在一些大的開源庫,如EventBus3.0+,頁面路由 ARout、Dagger、Retrofit等均有使用的身影,註解不僅僅是通過反射一種方式來使用,也可以使用APT在編譯期處理
43、bitmap recycler 相關
在Android中,Bitmap的儲存分為兩部分,一部分是Bitmap的資料,一部分是Bitmap的引用。 在Android2.3時代,Bitmap的引用是放在堆中的,而Bitmap的資料部分是放在棧中的,需要使用者呼叫recycle方法手動進行記憶體回收,而在Android2.3之後,整個Bitmap,包括資料和引用,都放在了堆中,這樣,整個Bitmap的回收就全部交給GC了,這個recycle方法就再也不需要使用了。
bitmap recycler引發的問題:當影象的旋轉角度小余兩個畫素點之間的夾角時,影象即使旋轉也無法顯示,因此,系統完全可以認為影象沒有發生變化。這時系統就直接引用同一個物件來進行操作,避免記憶體浪費。
44、強引用置為null,會不會被回收?
不會立即釋放物件佔用的記憶體。 如果物件的引用被置為null,只是斷開了當前執行緒棧幀中對該物件的引用關係,而 垃圾收集器是執行在後臺的執行緒,只有當用戶執行緒執行到安全點(safe point)或者安全區域才會掃描物件引用關係,掃描到物件沒有被引用則會標記物件,這時候仍然不會立即釋放該物件記憶體,因為有些物件是可恢復的(在 finalize方法中恢復引用 )。只有確定了物件無法恢復引用的時候才會清除物件記憶體。
45、Bundle傳遞資料為什麼需要序列化?
序列化,表示將一個物件轉換成可儲存或可傳輸的狀態。序列化的原因基本三種情況:
1.永久性儲存物件,儲存物件的位元組序列到本地檔案中;
2.物件在網路中傳遞;
3.物件在IPC間傳遞。
46、廣播傳輸的資料是否有限制,是多少,為什麼要限制?
Intent在傳遞資料時是有大小限制的,大約限制在1MB之內,你用Intent傳遞資料,實際上走的是跨程序通訊(IPC),跨程序通訊需要把資料從核心copy到程序中,每一個程序有一個接收核心資料的緩衝區,預設是1M;如果一次傳遞的資料超過限制,就會出現異常。
不同廠商表現不一樣有可能是廠商修改了此限制的大小,也可能同樣的物件在不同的機器上大小不一樣。
傳遞大資料,不應該用Intent;考慮使用ContentProvider或者直接匿名共享記憶體。簡單情況下可以考慮分段傳輸。
47、是否瞭解硬體加速?
硬體加速就是運用GPU優秀的運算能力來加快渲染的速度,而通常的基於軟體的繪製渲染模式是完全利用CPU來完成渲染。
1.硬體加速是從API 11引入,API 14之後才預設開啟。對於標準的繪製操作和控制元件都是支援的,但是對於自定義View的時候或者一些特殊的繪製函式就需要考慮是否需要關閉硬體加速。
2.我們面對不支援硬體加速的情況,就需要限制硬體加速,這個相容性的問題是因為硬體加速是把View的繪製函式轉化為使用OpenGL的函式來進完成實際的繪製的,那麼必然會存在OpenGL中不支援原始回執函式的情況,對於這些繪製函式,就會失效。
3.硬體加速的消耗問題,因為是使用OpenGL,需要把系統中OpenGL載入到記憶體中,OpenGL API呼叫就會佔用8MB,而實際上會佔用更多記憶體,並且使用了硬體必然增加耗電量了。
4.硬體加速的優勢還有display list的設計,使用這個我們不需要每次重繪都執行大量的程式碼,基於軟體的繪製模式會重繪髒區域內的所有控制元件,而display只會更新列表,然後繪製列表內的控制元件。
- CPU更擅長複雜邏輯控制,而GPU得益於大量ALU和並行結構設計,更擅長數學運算。
48、ContentProvider的許可權管理(讀寫分離,許可權控制-精確到表級,URL控制)。
對於ContentProvider暴露出來的資料,應該是儲存在自己應用記憶體中的資料,對於一些儲存在外部儲存器上的資料,並不能限制訪問許可權,使用ContentProvider就沒有意義了。對於ContentProvider而言,有很多許可權控制,可以在AndroidManifest.xml檔案中對節點的屬性進行配置,一般使用如下一些屬性設定:
- android:grantUriPermssions:臨時許可標誌。
- android:permission:Provider讀寫許可權。
- android:readPermission:Provider的讀許可權。
- android:writePermission:Provider的寫許可權。
- android:enabled:標記允許系統啟動Provider。
- android:exported:標記允許其他應用程式使用這個Provider。
- android:multiProcess:標記允許系統啟動Provider相同的程序中呼叫客戶端。
49、Fragment狀態儲存
Fragment狀態儲存入口:
1、Activity的狀態儲存, 在Activity的onSaveInstanceState()裡, 呼叫了FragmentManger的saveAllState()方法, 其中會對mActive中各個Fragment的例項狀態和View狀態分別進行儲存.
2、FragmentManager還提供了public方法: saveFragmentInstanceState(), 可以對單個Fragment進行狀態儲存, 這是提供給我們用的。
3、FragmentManager的moveToState()方法中, 當狀態回退到ACTIVITY_CREATED, 會呼叫saveFragmentViewState()方法, 儲存View的狀態.
50、直接在Activity中建立一個thread跟在service中建立一個thread之間的區別?
在Activity中被建立:該Thread的就是為這個Activity服務的,完成這個特定的Activity交代的任務,主動通知該Activity一些訊息和事件,Activity銷燬後,該Thread也沒有存活的意義了。
在Service中被建立:這是保證最長生命週期的Thread的唯一方式,只要整個Service不退出,Thread就可以一直在後臺執行,一般在Service的onCreate()中建立,在onDestroy()中銷燬。所以,在Service中建立的Thread,適合長期執行一些獨立於APP的後臺任務,比較常見的就是:在Service中保持與伺服器端的長連線。
51、如何計算一個Bitmap佔用記憶體的大小,怎麼保證載入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 真實佔用的記憶體大小(即 mBuffer 的長度)。
為了保證在載入Bitmap的時候不產生記憶體溢位,可以使用BitmapFactory進行圖片壓縮,主要有以下幾個引數:
BitmapFactory.Options.inPreferredConfig:將ARGB_8888改為RGB_565,改變編碼方式,節約記憶體。 BitmapFactory.Options.inSampleSize:縮放比例,可以參考Luban那個庫,根據圖片寬高計算出合適的縮放比例。 BitmapFactory.Options.inPurgeable:讓系統可以記憶體不足時回收記憶體。
52、對於應用更新這塊是如何做的?(灰度,強制更新,分割槽域更新)
1、通過介面獲取線上版本號,versionCode 2、比較線上的versionCode 和本地的versionCode,彈出更新視窗 3、下載APK檔案(檔案下載) 4、安裝APK
灰度: (1)找單一渠道投放特別版本。 (2)做升級平臺的改造,允許針對部分使用者推送升級通知甚至版本強制升級。 (3)開放單獨的下載入口。 (4)是兩個版本的程式碼都打到app包裡,然後在app端植入測試框架,用來控制顯示哪個版本。測試框架負責與伺服器端api通訊,由伺服器端控制app上A/B版本的分佈,可以實現指定的一組使用者看到A版本,其它使用者看到B版本。服務端會有相應的報表來顯示A/B版本的數量和效果對比。最後可以由服務端的後臺來控制,全部使用者線上切換到A或者B版本~
無論哪種方法都需要做好版本管理工作,分配特別的版本號以示區別。 當然,既然是做灰度,資料監控(常規資料、新特性資料、主要業務資料)還是要做到位,該打的資料樁要打。 還有,灰度版最好有收回的能力,一般就是強制升級下一個正式版。
強制更新:一般的處理就是進入應用就彈窗通知使用者有版本更新,彈窗可以沒有取消按鈕並不能取消。這樣使用者就只能選擇更新或者關閉應用了,當然也可以新增取消按鈕,但是如果使用者選擇取消則直接退出應用。
增量更新:bsdiff:二進位制差分工具bsdiff是相應的補丁合成工具,根據兩個不同版本的二進位制檔案,生成補丁檔案.patch檔案。通過bspatch使舊的apk檔案與不定檔案合成新的apk。 注意通過apk檔案的md5值進行區分版本。
53、請解釋安卓為啥要加簽名機制。
1、傳送者的身份認證 由於開發商可能通過使用相同的 Package Name 來混淆替換已經安裝的程式,以此保證簽名不同的包不被替換。
2、保證資訊傳輸的完整性 簽名對於包中的每個檔案進行處理,以此確保包中內容不被替換。
3、防止交易中的抵賴發生, Market 對軟體的要求。
54、為什麼bindService可以跟Activity生命週期聯動?
1、bindService 方法執行時,LoadedApk 會記錄 ServiceConnection 資訊。
2、Activity 執行 finish 方法時,會通過 LoadedApk 檢查 Activity 是否存在未登出/解綁的 BroadcastReceiver 和 ServiceConnection,如果有,那麼會通知 AMS 登出/解綁對應的 BroadcastReceiver 和 Service,並列印異常資訊,告訴使用者應該主動執行登出/解綁的操作。
55、如何通過Gradle配置多渠道包?
用於生成不同渠道的包
android {
productFlavors {
xiaomi {}
baidu {}
wandoujia {}
_360 {} // 或“"360"{}”,數字需下劃線開頭或加上雙引號
}
}
執行./gradlew assembleRelease ,將會打出所有渠道的release包;
執行./gradlew assembleWandoujia,將會打出豌豆莢渠道的release和debug版的包;
執行./gradlew assembleWandoujiaRelease將生成豌豆莢的release包。
因此,可以結合buildType和productFlavor生成不同的Build Variants,即型別與渠道不同的組合。
56、activty和Fragmengt之間怎麼通訊,Fragmengt和Fragmengt怎麼通訊?
(一)Handler
(二)廣播
(三)事件匯流排:EventBus、RxBus、Otto
(四)介面回撥
(五)Bundle和setArguments(bundle)
57、自定義view效率高於xml定義嗎?說明理由。
自定義view效率高於xml定義:
1、少了解析xml。
2.、自定義View 減少了ViewGroup與View之間的測量,包括父量子,子量自身,子在父中位置擺放,當子view變化時,父的某些屬性都會跟著變化。
58、廣播註冊一般有幾種,各有什麼優缺點?
第一種是常駐型(靜態註冊):當應用程式關閉後如果有資訊廣播來,程式也會被系統呼叫,自己執行。
第二種不常駐(動態註冊):廣播會跟隨程式的生命週期。
動態註冊
優點: 在android的廣播機制中,動態註冊優先順序高於靜態註冊優先順序,因此在必要情況下,是需要動態註冊廣播接收者的。
缺點: 當用來註冊的 Activity 關掉後,廣播也就失效了。
靜態註冊
優點: 無需擔憂廣播接收器是否被關閉,只要裝置是開啟狀態,廣播接收器就是開啟著的。
59、服務啟動一般有幾種,服務和activty之間怎麼通訊,服務和服務之間怎麼通訊
方式:
1、startService:
onCreate()--->onStartCommand() ---> onDestory()
如果服務已經開啟,不會重複的執行onCreate(), 而是會呼叫onStartCommand()。一旦服務開啟跟呼叫者(開啟者)就沒有任何關係了。 開啟者退出了,開啟者掛了,服務還在後臺長期的執行。 開啟者不能呼叫服務裡面的方法。
2、bindService:
onCreate() --->onBind()--->onunbind()--->onDestory()
bind的方式開啟服務,繫結服務,呼叫者掛了,服務也會跟著掛掉。 繫結者可以呼叫服務裡面的方法。
通訊:
1、通過Binder物件。
2、通過broadcast(廣播)。
60、ddms 和 traceView 的區別?
ddms 原意是:davik debug monitor service。簡單的說 ddms 是一個程式執行檢視器,在裡面可以看見執行緒和堆疊等資訊,traceView 是程式效能分析器。traceview 是 ddms 中的一部分內容。
Traceview 是 Android 平臺特有的資料採集和分析工具,它主要用於分析 Android 中應用程式的 hotspot(瓶頸)。Traceview 本身只是一個數據分析工具,而資料的採集則需要使用 Android SDK 中的 Debug 類或者利用DDMS 工具。二者的用法如下:開發者在一些關鍵程式碼段開始前呼叫 Android SDK 中 Debug 類的 startMethodTracing 函式,並在關鍵程式碼段結束前呼叫 stopMethodTracing 函式。這兩個函式執行過程中將採集執行時間內該應用所有執行緒(注意,只能是 Java執行緒) 的函式執行情況, 並將採集資料儲存到/mnt/sdcard/下的一個檔案中。 開發者然後需要利用 SDK 中的 Traceview工具來分析這些資料。
61、ListView卡頓原因
Adapter的getView方法裡面convertView沒有使用setTag和getTag方式;
在getView方法裡面ViewHolder初始化後的賦值或者是多個控制元件的顯示狀態和背景的顯示沒有優化好,抑或是裡面含有複雜的計算和耗時操作;
在getView方法裡面 inflate的row 巢狀太深(佈局過於複雜)或者是佈局裡面有大圖片或者背景所致;
Adapter多餘或者不合理的notifySetDataChanged;
listview 被多層巢狀,多次的onMessure導致卡頓,如果多層巢狀無法避免,建議把listview的高和寬設定為match_parent. 如果是程式碼繼承的listview,那麼也請你別忘記為你的繼承類新增上LayoutPrams,注意高和寬都mactch_parent的;
62、AndroidManifest的作用與理解
AndroidManifest.xml檔案,也叫清單檔案,來獲知應用中是否包含該元件,如果有會直接啟動該元件。可以理解是一個應用的配置檔案。
作用:
- 為應用的 Java 軟體包命名。軟體包名稱充當應用的唯一識別符號。
- 描述應用的各個元件,包括構成應用的 Activity、服務、廣播接收器和內容提供程式。它還為實現每個元件的類命名併發布其功能,例如它們可以處理的 Intent - 訊息。這些宣告向 Android 系統告知有關元件以及可以啟動這些元件的條件的資訊。
- 確定託管應用元件的程序。
- 宣告應用必須具備哪些許可權才能訪問 API 中受保護的部分並與其他應用互動。還宣告其他應用與該應用元件互動所需具備的許可權
- 列出 Instrumentation類,這些類可在應用執行時提供分析和其他資訊。這些宣告只會在應用處於開發階段時出現在清單中,在應用釋出之前將移除。
- 宣告應用所需的最低 Android API 級別
- 列出應用必須連結到的庫
63、LaunchMode應用場景
standard,建立一個新的Activity。
singleTop,棧頂不是該型別的Activity,建立一個新的Activity。否則,onNewIntent。
singleTask,回退棧中沒有該型別的Activity,建立Activity,否則,onNewIntent+ClearTop。
注意:
設定了"singleTask"啟動模式的Activity,它在啟動的時候,會先在系統中查詢屬性值affinity等於它的屬性值taskAffinity的Task存在;如果存在這樣的Task,它就會在這個Task中啟動,否則就會在新的任務棧中啟動。因此, 如果我們想要設定了"singleTask"啟動模式的Activity在新的任務中啟動,就要為它設定一個獨立的taskAffinity屬性值。
如果設定了"singleTask"啟動模式的Activity不是在新的任務中啟動時,它會在已有的任務中檢視是否已經存在相應的Activity例項, 如果存在,就會把位於這個Activity例項上面的Activity全部結束掉,即最終這個Activity 例項會位於任務的Stack頂端中。
在一個任務棧中只有一個”singleTask”啟動模式的Activity存在。他的上面可以有其他的Activity。這點與singleInstance是有區別的。
singleInstance,回退棧中,只有這一個Activity,沒有其他Activity。
singleTop適合接收通知啟動的內容顯示頁面。
例如,某個新聞客戶端的新聞內容頁面,如果收到10個新聞推送,每次都開啟一個新聞內容頁面是很煩人的。
singleTask適合作為程式入口點。
例如瀏覽器的主介面。不管從多少個應用啟動瀏覽器,只會啟動主介面一次,其餘情況都會走onNewIntent,並且會清空主介面上面的其他頁面。
singleInstance應用場景:
鬧鈴的響鈴介面。 你以前設定了一個鬧鈴:上午6點。在上午5點58分,你啟動了鬧鈴設定介面,並按 Home 鍵回桌面;在上午5點59分時,你在微信和朋友聊天;在6點時,鬧鈴響了,並且彈出了一個對話方塊形式的 Activity(名為 AlarmAlertActivity) 提示你到6點了(這個 Activity 就是以 SingleInstance 載入模式開啟的),你按返回鍵,回到的是微信的聊天介面,這是因為 AlarmAlertActivity 所在的 Task 的棧只有他一個元素, 因此退出之後這個 Task 的棧空了。如果是以 SingleTask 開啟 AlarmAlertActivity,那麼當鬧鈴響了的時候,按返回鍵應該進入鬧鈴設定介面。
64、說說Activity、Intent、Service 是什麼關係
他們都是 Android 開發中使用頻率最高的類。其中 Activity 和 Service 都是 Android 四大元件之一。他倆都是 Context 類的子類 ContextWrapper 的子類,因此他倆可以算是兄弟關係吧。不過兄弟倆各有各自的本領,Activity 負責使用者介面的顯示和互動,Service 負責後臺任務的處理。Activity 和 Service 之間可以通過 Intent 傳遞資料,因此 可以把 Intent 看作是通訊使者。
65、ApplicationContext和ActivityContext的區別
這是兩種不同的context,也是最常見的兩種.第一種中context的生命週期與Application的生命週期相關的,context隨著Application的銷燬而銷燬,伴隨application的一生,與activity的生命週期無關.第二種中的context跟Activity的生命週期是相關的,但是對一個Application來說,Activity可以銷燬幾次,那麼屬於Activity的context就會銷燬多次.至於用哪種context,得看應用場景。還有就是,在使用context的時候,小心記憶體洩露,防止記憶體洩露,注意一下幾個方面:
- 不要讓生命週期長的物件引用activity context,即保證引用activity的物件要與activity本身生命週期是一樣的。
- 對於生命週期長的物件,可以使用application context。
- 避免非靜態的內部類,儘量使用靜態類,避免生命週期問題,注意內部類對外部物件引用導致的生命週期變化。
66、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()這些方法。
67、ThreadLocal的原理
ThreadLocal是一個關於建立執行緒區域性變數的類。使用場景如下所示:
-
實現單個執行緒單例以及單個執行緒上下文資訊儲存,比如交易id等。
-
實現執行緒安全,非執行緒安全的物件使用ThreadLocal之後就會變得執行緒安全,因為每個執行緒都會有一個對應的例項。
承載一些執行緒相關的資料,避免在方法中來回傳遞引數。
當需要使用多執行緒時,有個變數恰巧不需要共享,此時就不必使用synchronized這麼麻煩的關鍵字來鎖住,每個執行緒都相當於在堆記憶體中開闢一個空間,執行緒中帶有對共享變數的緩衝區,通過緩衝區將堆記憶體中的共享變數進行讀取和操作,ThreadLocal相當於執行緒內的記憶體,一個區域性變數。每次可以對執行緒自身的資料讀取和操作,並不需要通過緩衝區與 主記憶體中的變數進行互動。並不會像synchronized那樣修改主記憶體的資料,再將主記憶體的資料複製到執行緒內的工作記憶體。ThreadLocal可以讓執行緒獨佔資源,儲存於執行緒內部,避免執行緒堵塞造成CPU吞吐下降。
在每個Thread中包含一個ThreadLocalMap,ThreadLocalMap的key是ThreadLocal的物件,value是獨享資料。
68、計算一個view的巢狀層級
private int getParents(ViewParents view){
if(view.getParents() == null)
return 0;
} else {
return (1 + getParents(view.getParents));
}
}
69、MVP,MVVM,MVC解釋和實踐
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的情況,只需要改變資料就行。
MVVM與DataBinding的關係?
MVVM是一種思想,DataBinding是谷歌推出的方便實現MVVM的工具。
看起來MVVM很好的解決了MVC和MVP的不足,但是由於資料和檢視的雙向繫結,導致出現問題時不太好定位來源,有可能資料問題導致,也有可能業務邏輯中對檢視屬性的修改導致。如果專案中打算用MVVM的話可以考慮使用官方的架構元件ViewModel、LiveData、DataBinding去實現MVVM。
三者如何選擇?
- 如果專案簡單,沒什麼複雜性,未來改動也不大的話,那就不要用設計模式或者架構方法,只需要將每個模組封裝好,方便呼叫即可,不要為了使用設計模式或架構方法而使用。
- 對於偏向展示型的app,絕大多數業務邏輯都在後端,app主要功能就是展示資料,互動等,建議使用mvvm。
- 對於工具類或者需要寫很多業務邏輯app,使用mvp或者mvvm都可。
70、SharedPrefrences的apply和commit有什麼區別?
這兩個方法的區別在於:
-
apply沒有返回值而commit返回boolean表明修改是否提交成功。
-
apply是將修改資料原子提交到記憶體, 而後非同步真正提交到硬體磁碟, 而commit是同步的提交到硬體磁碟,因此,在多個併發的提交commit的時候,他們會等待正在處理的commit儲存到磁碟後在操作,從而降低了效率。而apply只是原子的提交到內容,後面有呼叫apply的函式的將會直接覆蓋前面的記憶體資料,這樣從一定程度上提高了很多效率。
-
apply方法不會提示任何失敗的提示。
由於在一個程序中,sharedPreference是單例項,一般不會出現併發衝突,如果對提交的結果不關心的話,建議使用apply,當然需要確保提交成功且有後續操作的話,還是需要用commit的。
71、Base64、MD5是加密方法麼?
Base64是什麼?
Base64是用文字表示二進位制的編碼方式,它使用4個位元組的文字來表示3個位元組的原始二進位制資料。 它將二進位制資料轉換成一個由64個可列印的字元組成的序列:A-Za-z0-9+/
MD5是什麼?
MD5是雜湊演算法的一種,可以將任意資料產生出一個128位(16位元組)的雜湊值,用於確保資訊傳輸完整一致。我們常在註冊登入模組使用MD5,使用者密碼可以使用MD5加密的方式進行儲存。如:md5(hello world,32) = 5eb63bbbe01eeed093cb22bb8f5acdc3
加密,指的是對資料進行轉換以後,資料變成了另一種格式,並且除了拿到解密方法的人,沒人能把資料轉換回來。 MD5是一種資訊摘要演算法,它是不可逆的,不可以解密。所以它只能算的上是一種單向加密演算法。 Base64也不是加密演算法,它是一種資料編碼方式,雖然是可逆的,但是它的編碼方式是公開的,無所謂加密。
72、HttpClient和HttpConnection的區別?
Http Client適用於web瀏覽器,擁有大量靈活的API,實現起來比較穩定,且其功能比較豐富,提供了很多工具,封裝了http的請求頭,引數,內容體,響應,還有一些高階功能,代理、COOKIE、鑑權、壓縮、連線池的處理。 但是,正因此,在不破壞相容性的前提下,其龐大的API也使人難以改進,因此Android團隊對於修改優化Apache Http Client並不積極。(並在Android 6.0中拋棄了Http Client,替換成OkHttp)
HttpURLConnection對於大部分功能都進行了包裝,Http Client的高階功能程式碼會較複雜,另外,HttpURLConnection在Android 2.3中增加了一些Https方面的改進(包括Http Client,兩者都支援https)。且在Android 4.0中增加了response cache。當快取被安裝後(呼叫HttpResponseCache的install()方法),所有的HTTP請求都會滿足以下三種情況:
- 所有的快取響應都由本地儲存來提供。因為沒有必要去發起任務的網路連線請求,所有的響應都可以立刻獲取到。
- 視情況而定的快取響應必須要有伺服器來進行更新檢查。比如說客戶端發起了一條類似於 “如果/foo.png這張圖片發生了改變,就將它傳送給我” 這樣的請求,伺服器需要將更新後的資料進行返回,或者返回一個304 Not Modified狀態。如果請求的內容沒有發生,客戶端就不會下載任何資料。
- 沒有快取的響應都是由伺服器直接提供的。這部分響應會在稍後儲存到響應快取中。
在Android 2.2版本之前,HttpClient擁有較少的bug,因此使用它是最好的選擇。 而在Android 2.3版本及以後,HttpURLConnection則是最佳的選擇。它的API簡單,體積較小,因而非常適用於Android專案。壓縮和快取機制可以有效地減少網路訪問的流量,在提升速度和省電方面也起到了較大的作用。對於新的應用程式應該更加偏向於使用HttpURLConnection,因為在以後的工作當中Android官方也會將更多的時間放在優化HttpURLConnection上面。
73、ActivityA跳轉ActivityB然後B按back返回A,各自的生命週期順序,A與B均不透明。
ActivityA跳轉到ActivityB:
Activity A:onPause
Activity B:onCreate
Activity B:onStart
Activity B:onResume
Activity A:onStop
ActivityB返回ActivityA:
Activity B:onPause
Activity A:onRestart
Activity A:onStart
Activity A:onResume
Activity B:onStop
Activity B:onDestroy
74、如何通過廣播攔截和abort一條簡訊?
可以監聽這條訊號,在傳遞給真正的接收程式時,我們將自定義的廣播接收程式的優先順序大於它,並且取消廣播的傳播,這樣就可以實現攔截簡訊的功能了。
75、BroadcastReceiver,LocalBroadcastReceiver 區別?
1、應用場景
1、BroadcastReceiver用於應用之間的傳遞訊息;
2、而LocalBroadcastManager用於應用內部傳遞訊息,比broadcastReceiver更加高效。
2、安全
1、BroadcastReceiver使用的Content API,所以本質上它是跨應用的,所以在使用它時必須要考慮到不要被別的應用濫用;
2、LocalBroadcastManager不需要考慮安全問題,因為它只在應用內部有效。
3、原理方面
(1) 與BroadcastReceiver是以 Binder 通訊方式為底層實現的機制不同,LocalBroadcastManager 的核心實現實際還是 Handler,只是利用到了 IntentFilter 的 match 功能,至於 BroadcastReceiver 換成其他介面也無所謂,順便利用了現成的類和概念而已。
(2) LocalBroadcastManager因為是 Handler 實現的應用內的通訊,自然安全性更好,效率更高。
76、如何選擇第三方,從那些方面考慮?
大方向:從軟體環境做判斷
效能是開源軟體第一解決的問題。
一個好的生態,是一個優秀的開源庫必備的,取決標準就是觀察它是否一直在持續更新迭代,是否能及時處理github上使用者提出來的問題。大家在社群針對這個開源庫是否有比較活躍的探討。
背景,該開源庫由誰推出,由哪個公司推出來的。
使用者數和有哪些知名的企業落地使用
小方向:從軟體開發者的角度做判斷
是否解決了我們現有問題或長期來看帶來的維護成本。
公司有多少人會。
學習成本。
77、簡單說下接入支付的流程,是否自己接入過支付功能?
Alipay支付功能:
1.首先登入支付寶開放平臺建立應用,並給應用新增App支付功能, 由於App支付功能需要簽約,因此需要上傳公司資訊和證件等資料進行簽約。
2.簽約成功後,需要配置祕鑰。使用支付寶提供的工具生成RSA公鑰和私鑰,公鑰需要設定到管理後臺。
3.android studio整合
(1)copy jar包;
(2)發起支付請求,處理支付請求。
78、單例實現執行緒的同步的要求:
1.單例類確保自己只有一個例項(建構函式私有:不被外部例項化,也不被繼承)。
2.單例類必須自己建立自己的例項。
3.單例類必須為其他物件提供唯一的例項。
79、如何保證Service不被殺死?
Android 程序不死從3個層面入手:
A.提供程序優先順序,降低程序被殺死的概率
方法一:監控手機鎖屏解鎖事件,在螢幕鎖屏時啟動1個畫素的 Activity,在使用者解鎖時將 Activity 銷燬掉。
方法二:啟動前臺service。
方法三:提升service優先順序:
在AndroidManifest.xml檔案中對於intent-filter可以通過android:priority = "1000"這個屬性設定最高優先順序,1000是最高值,如果數字越小則優先順序越低,同時適用於廣播。
B. 在程序被殺死後,進行拉活
方法一:註冊高頻率廣播接收器,喚起程序。如網路變化,解鎖螢幕,開機等
方法二:雙程序相互喚起。
方法三:依靠系統喚起。
方法四:onDestroy方法裡重啟service:service + broadcast 方式,就是當service走ondestory的時候,傳送一個自定義的廣播,當收到廣播的時候,重新啟動service;
C. 依靠第三方
根據終端不同,在小米手機(包括 MIUI)接入小米推送、華為手機接入華為推送;其他手機可以考慮接入騰訊信鴿或極光推送與小米推送做 A/B Test。
80、說說ContentProvider、ContentResolver、ContentObserver 之間的關係?
ContentProvider:管理資料,提供資料的增刪改查操作,資料來源可以是資料庫、檔案、XML、網路等,ContentProvider為這些資料的訪問提供了統一的介面,可以用來做程序間資料共享。
ContentResolver:ContentResolver可以為不同URI操作不同的ContentProvider中的資料,外部程序可以通過ContentResolver與ContentProvider進行互動。
ContentObserver:觀察ContentProvider中的資料變化,並將變化通知給外界。
81、如何匯入外部資料庫?
把原資料庫包括在專案原始碼的 res/raw。
android系統下資料庫應該存放在 /data/data/com.(package name)/ 目錄下,所以我們需要做的是把已有的資料庫傳入那個目錄下。操作方法是用FileInputStream讀取原資料庫,再用FileOutputStream把讀取到的東西寫入到那個目錄。
82、LinearLayout、FrameLayout、RelativeLayout效能對比,為什麼?
RelativeLayout會讓子View呼叫2次onMeasure,LinearLayout 在有weight時,也會呼叫子 View 2次onMeasure
RelativeLayout的子View如果高度和RelativeLayout不同,則會引發效率問題,當子View很複雜時,這個問題會更加嚴重。如果可以,儘量使用padding代替margin。
在不影響層級深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。
為什麼Google給開發者預設新建了個RelativeLayout,而自己卻在DecorView中用了個LinearLayout?
因為DecorView的層級深度是已知而且固定的,上面一個標題欄,下面一個內容欄。採用RelativeLayout並不會降低層級深度,所以此時在根節點上用LinearLayout是效率最高的。而之所以給開發者預設新建了個RelativeLayout是希望開發者能採用儘量少的View層級來表達佈局以實現效能最優,因為複雜的View巢狀對效能的影響會更大一些。
83、scheme跳轉協議
Android中的scheme是一種頁面內跳轉協議,通過定義自己的scheme協議,可以跳轉到app中的各個頁面
伺服器可以定製化告訴app跳轉哪個頁面
App可以通過跳轉到另一個App頁面
可以通過H5頁面跳轉頁面
84、HandlerThread
1、HandlerThread原理
當系統有多個耗時任務需要執行時,每個任務都會開啟個新執行緒去執行耗時任務,這樣會導致系統多次建立和銷燬執行緒,從而影響效能。為了解決這一問題,Google提出了HandlerThread,HandlerThread本質上是一個執行緒類,它繼承了Thread。HandlerThread有自己的內部Looper物件,可以進行loopr迴圈。通過獲取HandlerThread的looper物件傳遞給Handler物件,可以在handleMessage()方法中執行非同步任務。建立HandlerThread後必須先呼叫HandlerThread.start()方法,Thread會先呼叫run方法,建立Looper物件。當有耗時任務進入佇列時,則不需要開啟新執行緒,在原有的執行緒中執行耗時任務即可,否則執行緒阻塞。它在Android中的一個具體的使用場景是IntentService。由於HanlderThread的run()方法是一個無限迴圈,因此當明確不需要再使用HandlerThread時,可以通過它的quit或者quitSafely方法來終止執行緒的執行。
2、HanlderThread的優缺點
-
HandlerThread優點是非同步不會堵塞,減少對效能的消耗。
-
HandlerThread缺點是不能同時繼續進行多工處理,要等待進行處理,處理效率較低。
-
HandlerThread與執行緒池不同,HandlerThread是一個串佇列,背後只有一個執行緒。
85、IntentService
IntentService是一種特殊的Service,它繼承了Service並且它是一個抽象類,因此必須建立它的子類才能使用IntentService。
原理
在實現上,IntentService封裝了HandlerThread和Handler。當IntentService被第一次啟動時,它的onCreate()方法會被呼叫,onCreat()方法會建立一個HandlerThread,然後使用它的Looper來構造一個Handler物件mServiceHandler,這樣通過mServiceHandler傳送的訊息最終都會在HandlerThread中執行。
生成一個預設的且與主執行緒互相獨立的工作者執行緒來執行所有傳送至onStartCommand()方法的Intetnt。
生成一個工作佇列來傳送Intent物件給onHandleIntent()方法,同一時刻只傳送一個Intent物件,這樣一來,你就不必擔心多執行緒的問題。在所有的請求(Intent)都被執行完以後會自動停止服務,所以,你不需要自己去呼叫stopSelf()方法來停止。
該服務提供了一個onBind()方法的預設實現,它返回null。
提供了一個onStartCommand()方法的預設實現,它將Intent先傳送至工作佇列,然後從工作佇列中每次取出一個傳送至onHandleIntent()方法,在該方法中對Intent做相應的處理。
為什麼在mServiceHandler的handleMessage()回撥方法中執行完onHandlerIntent()方法後要使用帶引數的stopSelf()方法?
因為stopSel()方法會立即停止服務,而stopSelf(int startId)會等待所有的訊息都處理完畢後才終止服務,一般來說,stopSelf(int startId)在嘗試停止服務之前會判斷最近啟動服務的次數是否和startId相等,如果相等就立刻停止服務,不相等則不停止服務。
86、如何將一個Activity設定成視窗的樣式。
中配置:
android:theme="@android:style/Theme.Dialog"
另外
android:theme="@android:style/Theme.Translucnt"
是設定透明。
87、Android中跨程序通訊的幾種方式
1:訪問其他應用程式的Activity 如呼叫系統通話應用
Intent callIntent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:12345678");
startActivity(callIntent);
2:Content Provider 如訪問系統相簿
3:廣播(Broadcast) 如顯示系統時間
4:AIDL服務
88、顯示Intent與隱式Intent的區別
對明確指出了目標元件名稱的Intent,我們稱之為“顯式Intent”。
對於沒有明確指出目標元件名稱的Intent,則稱之為“隱式 Intent”。
對於隱式意圖,在定義Activity時,指定一個intent-filter,當一個隱式意圖物件被一個意圖過濾器進行匹配時,將有三個方面會被參考到:
動作(Action)
類別(Category ['kætɪg(ə)rɪ] )
資料(Data )
<activity android:name=".MainActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="com.wpc.test" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/gif"/>
</intent-filter>
</activity>
89、Android Holo主題與MD主題的理念,以及你的看法
Holo Theme
Holo Theme 是 Android Design 的最基礎的呈現方式。因為是最為基礎的 Android Design 呈現形式,每一臺 Android 4.X 的手機系統內部都有整合 Holo Theme 需要的控制元件,即開發者不需要自己設計控制元件,而是直接從系統裡呼叫相應的控制元件。在 UI 方面沒有任何的亮點,和 Android4.X 的設定/電話的視覺效果極度統一。由此帶來的好處顯而易見,這個應用作為 Android 應用的辨識度極高,且完全不可能與系統風格產生衝突。
Material Design
Material design其實是單純的一種設計語言,它包含了系統介面風格、互動、UI,更加專注擬真,更加大膽豐富的用色,更加豐富的互動形式,更加靈活的佈局形式
1.鮮明、形象的介面風格,
2.色彩搭配使得應用看起來非常的大膽、充滿色彩感,凸顯內容
3.Material design對於介面的排版非常的重視
4.Material design的互動設計上採用的是響應式互動,這樣的互動設計能把一個應用從簡單展現使用者所請求的資訊,提升至能與使用者產生更強烈、更具體化互動的工具。
90、如何讓程式自動啟動?
定義一個Braodcastreceiver,action為BOOT——COMPLETE,接受到廣播後啟動程式。
91、Fragment 在 ViewPager 裡面的生命週期,滑動 ViewPager 的頁面時 Fragment 的生命週期的變化。
92、如何檢視模擬器中的SP與SQList檔案。如何視覺化檢視佈局巢狀層數與載入時間。
93、各大平臺打包上線的流程與稽核時間,常見問題(主流的應用市場說出3-4個)
94、螢幕適配的處理技巧都有哪些?
一、為什麼要適配
為了保證使用者獲得一致的使用者體驗效果,使得某一元素在Android不同尺寸、不同解析度的、不同系統的手機上具備相同的顯示效果,能夠保持介面上的效果一致,我們需要對各種手機螢幕進行適配!
- Android系統碎片化:基於Google原生系統,小米定製的MIUI、魅族定製的flyme、華為定製的EMUI等等;
- Android機型螢幕尺寸碎片化:5寸、5.5寸、6寸等等;
- Android螢幕解析度碎片化:320x480、480x800、720x1280、1080x1920等。
二、基本概念
- 畫素(px):畫素就是手機螢幕的最小構成單元,px = 1畫素點 一般情況下UI設計師的設計圖會以px作為統一的計量單位。
- 解析度:手機在橫向、縱向上的畫素點數總和 一般描述成 寬*高 ,即橫向畫素點個數 * 縱向畫素點個數(如1080 x 1920),單位:px。
- 螢幕尺寸:手機對角線的物理尺寸。單位 英寸(inch),一英寸大約2.54cm 常見的尺寸有4.7寸、5寸、5.5寸、6寸。
- 螢幕畫素密度(dpi):每英寸的畫素點數,例如每英寸內有160個畫素點,則其畫素密度為160dpi,單位:dpi(dots per inch)。
- 標準螢幕畫素密度(mdpi): 每英寸長度上還有160個畫素點(160dpi),即稱為標準螢幕畫素密度(mdpi)。
- 密度無關畫素(dp):與終端上的實際物理畫素點無關,可以保證在不同螢幕畫素密度的裝置上顯示相同的效果,是安卓特有的長度單位,dp與px的轉換:1dp = (dpi / 160 ) * 1px。
- 獨立比例畫素(sp):字型大小專用單位 Android開發時用此單位設定文字大小,推薦使用12sp、14sp、18sp、22sp作為字型大小。
三、適配方案
適配的最多的3個解析度:1280720,19201080,800*480。
解決方案:
對於Android的螢幕適配,我認為可以從以下4個方面來做:
1、佈局元件適配
- 請務必使用密度無關畫素 dp 或獨立比例畫素 sp 單位指定尺寸。
- 使用相對佈局或線性佈局,不要使用絕對佈局
- 使用wrap_content、match_parent、權重
- 使用minWidth、minHeight、lines等屬性
dimens使用:
不同的螢幕尺寸可以定義不同的數值,或者是不同的語言顯示我們也可以定義不同的數值,因為翻譯後的長度一般都不會跟中文的一致。此外,也可以使用百分比佈局或者AndroidStudio2.2的新特性約束佈局。
2、佈局適配
使用限定符(螢幕密度限定符、尺寸限定符、最小寬度限定符、佈局別名、螢幕方向限定符)根據螢幕的配置來載入相應的UI佈局。
3、圖片資源適配
使用自動拉伸圖.9png圖片格式使圖片資源自適應螢幕尺寸。
普通圖片和圖示:
建議按照官方的密度型別進行切圖即可,但一般我們只需xxhdpi或xxxhdpi的切圖即可滿足我們的需求;
4、程式碼適配:
在程式碼中使用Google提供的API對裝置的螢幕寬度進行測量,然後按照需求進行設定。
5、介面配合:
本地載入圖片前判斷手機解析度或畫素密度,向伺服器請求對應級別圖片。
95、斷點續傳實現?
在本地下載過程中要使用資料庫實時儲存到底儲存到檔案的哪個位置了,這樣點選開始繼續傳遞時,才能通過HTTP的GET請求中的setRequestProperty("Range","bytes=startIndex-endIndex");方法可以告訴伺服器,資料從哪裡開始,到哪裡結束。同時在本地的檔案寫入時,RandomAccessFile的seek()方法也支援在檔案中的任意位置進行寫入操作。最後通過廣播或事件匯流排機制將子執行緒的進度告訴Activity的進度條。關於斷線續傳的HTTP狀態碼是206,即HttpStatus.SC_PARTIAL_CONTENT。
96、專案中遇到哪些難題,最終你是如何解決的?
97、Activity正常和異常情況下的生命週期
98、關於< include >< merge >< stub >三者的使用場景
99、Android對HashMap做了優化後推出的新的容器類是什麼?
面試資料PDF電子書
資料已經上傳在我的GitHub無償分享下載!
Java、計算機網路部分電子書(242頁)
Android部分電子書(1294頁)
資料已經上傳在我的GitHub免費下載!誠意滿滿!!!
聽說一鍵三連的粉絲都面試成功了?如果本篇部落格對你有幫助,請支援下小編哦
Android高階面試精選題、架構師進階實戰文件傳送門:我的GitHub
整理不易,覺得有幫助的朋友可以幫忙點贊分享支援一下小編~
你的支援,我的動力;祝各位前程似錦,offer不斷!!!
參考
https://juejin.cn/post/6844904079169159175
https://juejin.cn/post/6905226221357891592
https://juejin.cn/post/6888222422760488974