1、四大元件
1.1 Activity
- Q:在兩個 Activity 之間傳遞物件還需要注意什麼呢?
物件的大小。Intent 中的 Bundle 是使用 Binder 機制進行資料傳送的。能使用的 Binder 的緩衝區是有大小限制的(有些手機是 2 M),而一個程式預設有 16 個 Binder 執行緒,所以一個執行緒能佔用的緩衝區就更小了(有人以前做過測試,大約一個執行緒可以佔用 128 KB)。可以使用 EventBus 來傳遞資料,而不是直接使用 Intent 進行傳遞。
- Q:onSaveInstanceState() 和 onRestoreInstanceState()
當 Activity 被銷燬的時候回撥用 onSaveInstanceState()
方法來儲存當前的狀態。這樣當 Activity 被重建的時候,可以在 onCreate()
和 onRestoreInstanceState()
中恢復狀態。
對於 targetAPI 為 28 及以後的應用,該方法會在 onStop()
方法之後呼叫,對於之前的裝置,這方法會在 onStop()
之前呼叫,但是無法確定是在 onPause()
之前還是之後呼叫。
onRestoreInstanceState()
方法用來恢復之前儲存的狀態,它會在 onStart()
和 onPostCreate()
之間被呼叫。此外,你也可以直接在 onCreate()
方法中進行恢復,但是基於這個方法呼叫的時機,如果有特別需求,可以在這個方法中進行處理。
- Q:SingleTask 啟動模式
- Q:Activity 啟動模式
- standard:預設,每次啟動的時候會建立一個新的例項,並且被建立的例項所在的棧與啟動它的 Activity 是同一個棧。比如,A 啟動了 B,那麼 B 將會與 A 處在同一個棧。假如,我們使用 Application 的 Context 啟動一個 Activity 的時候會丟擲異常,這是因為新啟動的 Activity 不知道自己將會處於哪個棧。可以在啟動 Activity 的時候使用
FLAG_ACTIVITY_NEW_TASK
。這樣新啟動的 Acitivyt 將會建立一個新的棧。 - singleTop:棧頂複用,如果將要啟動的 Activity 已經位於棧頂,那麼將會複用棧頂的 Activity,並且會呼叫它的
onNewIntent()
。常見的應用場景是從通知開啟 Activity 時。 - singleTask:單例,如果啟動它的任務棧中存在該 Activity,那麼將會複用該 Activity,並且會將棧內的、它之上的所有的 Activity 清理出去,以使得該 Activity 位於棧頂。常見的應用場景是啟動頁面、購物介面、確認訂單介面和付款介面等。
- singleInstance:這種啟動模式會在啟動的時候為其指定一個單獨的棧來執行。如果用同樣的intent 再次啟動這個 Activity,那麼這個 Activity 會被調到前臺,並且會呼叫其
onNewIntent()
方法。
- Q:下拉狀態列是不是影響 Activity 的生命週期,如果在 onStop() 的時候做了網路請求,onResume() 的時候怎麼恢復
- Q:前臺切換到後臺,然後再回到前臺,Activity 生命週期回撥方法。彈出 Dialog,生命值週期回撥方法。
- Q:Activity 生命週期
- Q:Activity 上有 Dialog 的時候按 Home 鍵時的生命週期
- Q:橫豎屏切換的時候,Activity 各種情況下的生命週期
Android 下拉通知欄不會影響 Activity 的生命週期方法。
彈出 Dialog,生命週期:其實是否彈出 Dialog,並不影響 Activity 的生命週期,所以這時和正常啟動時 Activity 的生命回撥方法一致: onCreate() -> onStart() -> onResume()
。
這裡我們總結一下在實際的使用過程中可能會遇到的一些 Acitivity 的生命週期過程:
- 當使用者開啟新的 Activity 或者切換回桌面:會經過的生命週期為
onPause()->onStop()
。因為此時 Activity 已經變成不可見了,當然,如果新開啟的 Activity 用了透明主題,那麼 onStop() 不會被呼叫,因此原來的 Activity 只是不能互動,但是仍然可見。 - 從新的 Activity 回到之前的 Activity 或者從桌面回到之前的 Activity:會經過的生命週期為
onRestart()->onStart()-onResume()
。此時是從 onStop() 經 onRestart() 回到 onResume() 狀態。 - 如果在上述 1 的情況下,進入後臺的 Activity 因為記憶體不足被銷燬了,那麼當再次回到該 Activity 的時候,生命週期方法將會從 onCreate() 開始執行到 onResume()。
- 當使用者按下 Back 鍵時:如果當前 Activity 被銷燬,那麼經過的生命週期將會是
onPause()->onStop()->onDestroy()
。
具體地,當存在兩個 Activity,分別是 A 和 B 的時候,在各種情況下,它們的生命週期將會經過:
- Back 鍵 Home 鍵
- 當使用者點選 A 中按鈕來到 B 時,假設 B 全部遮擋住了 A,將依次執行:
A.onPause()->B.onCreate()->B.onStart()->B.onResume->A.onStop()
。 - 接1,此時如果點選 Back 鍵,將依次執行:
B.onPause()->A.onRestart()->A.onStart()->A.onResume()->B.onStop()->B.onDestroy()
。 - 接2,此時如果按下 Back 鍵,系統返回到桌面,並依次執行:
A.onPause()->A.onStop()->A.onDestroy()
。 - 接2,此時如果按下 Home 鍵(非長按),系統返回到桌面,並依次執行
A.onPause()->A.onStop()
。由此可見,Back 鍵和 Home 鍵主要區別在於是否會執行 onDestroy()。 - 接2,此時如果長按 Home 鍵,不同手機可能彈出不同內容,Activity 生命週期未發生變化。
- 當使用者點選 A 中按鈕來到 B 時,假設 B 全部遮擋住了 A,將依次執行:
- 橫豎屏切換時 Activity 的生命週期
- 不設定 Activity 的
android:configChanges
時,切屏會重新呼叫各個生命週期,切橫屏時會執行一次,切豎屏時會執行兩次。 - 設定 Activity 的
android:configChanges=“orientation”
時,切屏還是會重新呼叫各個生命週期,切橫、豎屏時只會執行一次。 - 設定 Activity 的
android:configChanges=“orientation|keyboardHidden”
時,切屏不會重新呼叫各個生命週期,只會執行 onConfiguration() 方法。
- 不設定 Activity 的
- Q:Activity 之間的通訊方式
- Intent +
onActivityResult()
+setResult()
- 靜態變數(跨程式不行)
- 全域性通訊,廣播或者 EventBus
- Q:AlertDialog, PopupWindow, Activity 區別
AlertDialog 是 Dialog 的子類,所以它包含了 Dialog 類的很多屬性和方法。是彈出對話方塊的主要方式,對話方塊分成支援包的和非支援包的,UI 效果上略有區別。
AlertDialog 與 PopupWindow 之間最本質的差異在於:
AlertDialog 是非阻塞式對話方塊;而PopupWindow 是阻塞式對話方塊
。AlertDialog 彈出時,後臺還可以做事情;PopupWindow 彈出時,程式會等待,在PopupWindow 退出前,程式一直等待,只有當我們呼叫了dismiss()
方法的後,PopupWindow 退出,程式才會向下執行。我們在寫程式的過程中可以根據自己的需要選擇使用 Popupwindow 或者是 Dialog.兩者最根本的區別在於有沒有新建一個 window
,PopupWindow 沒有新建,而是通過 WMS 將 View 加到 DecorView;Dialog 是新建了一個 window (PhoneWindow),相當於走了一遍 Activity 中建立 window 的流程。
Activity 與 Dialog 類似,都會使用 PhoneWindow 來作為 View 的容器。Activity 也可以通過設定主題為 Dialog 的來將其作為對話方塊來使用。Dialog 也可以通過設定 Theme 來表現得像一個 Activity 一樣作為整個頁面。但 Activity 具有生命週期,並且它的生命週期歸 AMS 管,而 Dialog 不具有生命週期,它歸 WMS 管。
- Q:Activity 與 Service 通訊的方式
前提是是否跨程式,如果不跨程式的話,EventBus 和 靜態變數都能傳遞資訊,否則需要 IPC 才行:
- Binder 用於跨程式的通訊方式,AIDL 可以用來進行與遠端通訊,繫結服務的時候可以拿到遠端的 Binder,然後呼叫它的方法就可以從遠端拿資料。那麼如果希望對遠端的服務進行監聽呢?可以使用 AIDL 中的
oneway
來定義回撥的介面,然後在方法中傳入回撥即可。也可以使用 Messenger,向遠端傳送資訊的時候,附帶本地的 Messenger,然後遠端獲取本地的 Messenger 然後向其傳送資訊即可,詳見 IPC 相關一文:《Android 高階面試-2:IPC 相關》 - 廣播:使用廣播實現跨程式通訊
- 啟動服務的時候傳入值,使用
startService()
的方式
關於 Activity 相關的內容可以參考筆者的文章:《Android 基礎回顧:Activity 基礎》
1.2 Service
- Q:怎麼啟動 Service
- Q:Service 的開啟方式
- Q:Service 生命週期
- Service 有繫結模式和非繫結模式,以及這兩種模式的混合使用方式。不同的使用方法生命週期方法也不同。
- 非繫結模式:當第一次呼叫
startService()
的時候執行的方法依次為onCreate()->onStartCommand()
;當 Service 關閉的時候呼叫onDestory()
。 - 繫結模式:第一次
bindService()
的時候,執行的方法為onCreate()->onBind()
;解除繫結的時候會執行onUnbind()->onDestory()
。
- 非繫結模式:當第一次呼叫
- 我們在開發的過程中還必須注意 Service 例項只會有一個,也就是說如果當前要啟動的 Service 已經存在了那麼就不會再次建立該 Service 當然也不會呼叫 onCreate() 方法。所以,
- 當第一次執行
startService(intent)
的時候,會呼叫該 Service 中的onCreate()
和onStartCommand()
方法。 - 當第二次執行
startService(intent)
的時候,只會呼叫該 Service 中的onStartCommand()
方法。(因此已經建立了服務,所以不需要再次呼叫onCreate()
方法了)。
- 當第一次執行
bindService()
方法的第三個引數是一個標誌位,這裡傳入BIND_AUTO_CREATE
表示在Activity 和 Service 建立關聯後自動建立 Service,這會使得 MyService 中的onCreate()
方法得到執行,但onStartCommand()
方法不會執行。所以,在上面的程式中當呼叫了bindService()
方法的時候,會執行的方法有,Service 的onCreate()
方法,以及 ServiceConnection 的onServiceConnected()
方法。- 在 3 中,如果想要停止 Service,需要呼叫
unbindService()
才行。 - 如果我們既呼叫了
startService()
,又呼叫bindService()
會怎麼樣呢?這時不管你是單獨呼叫stopService()
還是unbindService()
,Service 都不會被銷燬,必須要將兩個方法都呼叫 Service 才會被銷燬。也就是說,stopService()
只會讓 Service 停止,unbindService()
只會讓 Service 和 Activity 解除關聯,一個 Service 必須要在既沒有和任何 Activity 關聯又處理停止狀態的時候才會被銷燬。
- 程式保活
- App 中喚醒其他程式的實現方式
1.3 Broadcast
- Q:BroadcastReceiver,LocalBroadcastReceiver 區別
- Q:廣播的使用場景
- Q:廣播的使用方式,場景
- Q:廣播的分類?
- Q:廣播(動態註冊和靜態註冊區別,有序廣播和標準廣播)
分類
- 按照註冊方式:靜態註冊和動態註冊兩種:
- 靜態廣播直接在 manifest 中註冊。限制:
- 在 Android 8.0 的平臺上,應用不能對大部分的廣播進行靜態註冊,也就是說,不能在 AndroidManifest 檔案對有些廣播進行靜態註冊;
- 當程式執行在後臺的時候,靜態廣播中不能啟動服務。
- 動態廣播與靜態廣播相似,但是不需要在 Manifest 中進行註冊。注意當頁面被銷燬的時候需要取消註冊廣播!
- 靜態廣播直接在 manifest 中註冊。限制:
- 按照作用範圍:本地廣播和普通廣播兩種,
- 普通廣播是全域性的,所有應用程式都可以接收到,容易會引起安全問題。
- 本地廣播只能夠在應用內傳遞,廣播接收器也只能接收應用內發出的廣播。本地廣播的核心類是 LocalBroadcastManager,使用它的靜態方法
getInstance()
獲取一個單例之後就可以使用該單例的registerReceiver()
、unregisterReceiver()
和sendBroadcast()
等方法來進行操作了。
- 按照是否有序:有序廣播和無序廣播兩種,無序廣播各接收器接收的順序無法確定,並且在廣播發出之後接收器只能接收,不能攔截和進行其他處理,兩者的區別主要體現在傳送時呼叫的方法上。優先順序高的會先接收到,優先順序相等的話則順序不確定。並且前面的廣播可以在方法中向 Intent 寫入資料,後面的廣播可以接收到寫入的值。
1.4 ContentProvider
- Q:Android 系統為什麼會設計 ContentProvider,程式共享和執行緒安全問題
ContentProvider 在 Android 中的作用是對外共享資料,提供了資料訪問介面,用於在不同應用程式之間共享資料,同時還能保證被訪問資料的安全性。它通常用來提供一些公共的資料,比如用來查詢檔案資訊,製作音樂播放器的時候用來讀取系統中的音樂檔案的資訊。
與 SQLiteDatabase 不同,ContentProvider 中的 CRUD 不接收表名引數,而是 Uri 引數。內容 URI 是內容提供器中資料的唯一識別符號,包括許可權和路徑。
併發訪問時,不論是不同的程式還是同一程式的不同執行緒,當使用 AMS 獲取 Provider 的時候返回的都是同一例項。我們使用 Provider 來從遠端訪問資料,當 query()
方法執行在不同的執行緒,實際上是執行在 Provider 方的程式的 Binder 執行緒池中。通過 Binder 的執行緒池來實現多程式和多執行緒訪問的安全性。
參考:Android ContentProvider的執行緒安全(一)
1.5 Fragment
- Q:Fragment 各種情況下的生命週期
- Q:Activity 與 Fragment 之間生命週期比較
- Q:Fragment 之間傳遞資料的方式?
- 同一 Activity 的 Fragment 之間可以使用 ViewModel 來交換資料;
- 使用 EventBus,廣播,靜態的;
- 通過 Activity 獲取到另一個 Fragment,強轉之後使用它對外提供的 public 方法進行通訊;
- 通過 Activity 獲取到另一個 Fragment,該 Fragment 實現某個介面,然後轉成介面之後進行通訊(也適用於 Activity 與 Fragment 之間),強轉之後使用它對外提供的 public 方法進行通訊;
- Q:如何實現 Fragment 的滑動
1.6 Context
- Q:Application 和 Activity 的 Context 物件的區別
Context 的繼承關係如下所示,所以,Android 中的 Context 數量 = Activity 的數量 + Service 的數量 + 1 (Application)
Context 的用途比較廣,比如用來獲取圖片、字串,顯式對話方塊等,大部分情況下,使用哪個 Context 都可以,少數情況下只能使用特定的 Context. 比如啟動 Activity 的時候,要求傳入 Activity 的 Context,因為 AMS 需要直到啟動指定 Activity 的 Activity 的棧。一般情況下,能使用 Application 的 Context 儘量使用它的,因為它的生命週期更長。
Context 之間使用的是裝飾者設計模式,其中 Context 是一個抽象的類。ContextWrapper 內部實際使用 ContextImpl 實現的,因此所有的邏輯基本是在 ContextImpl 中實現的。然後對於 ContextThemeWrapper,它在 ContextWrapper 的基礎之上又進行了一層裝飾,就是與主題相關的東西。
新版的 Activity 啟動中將 Activity 的各個回撥執行的邏輯放在了各個 ClientTransactionItem 中,比如 LaunchActivityItem 表示用來啟動 Activity。 最終執行邏輯的時候是呼叫它們的 execute() 方法並使用傳入的 ClientTransactionHandler 真正執行任務。而這裡的 ClientTransactionHandler 實際上就是 ActivityThread,所以它將呼叫到 Activity 的 handleLaunchActivity()
啟動 Activity. 然後程式進入到 performLaunchActivity()
中。這個方法中會建立上面的 Application 和 Activity 對應的 Context:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ...
// 建立 Activity 的 Context
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 建立新的 Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// ...
} catch (Exception e) {
// ... handle exception
}
try {
// 建立應用的 Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
// Activity 的配置
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
// 建立視窗
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
// 關聯 Activity 和 Context
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// ...
// 設定 Activity 的主題
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
// 回撥 Activity 的生命週期方法
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
r.activity = activity;
}
r.setState(ON_CREATE);
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
// ... handle exception
}
return activity;
}
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
// ...
try {
// 建立 Application 的 Context
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
// 關聯 Application 和 Context
appContext.setOuterContext(app);
} catch (Exception e) {
// ... handle exception
}
// ...
return app;
}
複製程式碼
1.7 其他
- Q:AndroidManifest 的作用與理解
宣告瞭四大元件、應用版本、許可權等的配置資訊,會在解析 APK 的時候由 PMS 進行解析,然後解析的結果會被快取到 PMS 中。
2、Android API
2.1 AsyncTask
- Q:AsyncTask 機制,如何取消 AsyncTask
- Q:多執行緒(關於 AsyncTask 缺陷引發的思考)
- Q:Asynctask 有什麼優缺點
- Q:AsyncTask 機制、原理及不足?
AsyncTask 是 Android 提供的用來執行非同步操作的 API,我們可以通過它來執行非同步操作,並在得到結果之後將結果放在主執行緒當中進行後續處理。
AsyncTask 的缺點是在使用多個非同步操作和並需要進行 Ui 變更時,就變得複雜起來(會導致多個 AsyncTask 進行巢狀)。如果有多個地方需要用到 AsyncTask,可能需要定義多個 AsyncTask 的實現。
如果 AsyncTask 以一個非靜態的內部類的形式宣告在 Activity 中,那麼它會持有 Activity 的匿名引用,如果銷燬 Activity 時 AsyncTask 還在執行非同步任務的話,Activity 就不能銷燬,會造成記憶體洩漏。解決方式是,要麼將 AsyncTask 定義成靜態內部類,要麼在 Activity 銷燬的時候呼叫 cancel()
方法取消 AsyncTask.在螢幕旋轉或 Activity 意外結束時,Activity 被建立,而 AsyncTask 會擁有之前 Activity 的引用,會導致結果丟失。
AsyncTask 在 1.6 之前是序列的,1.6 之後是並行的,3.0 之後又改成了序列的。不過我們可以通過呼叫 executeOnExecutor()
方法並傳入一個執行緒池,來讓 AsyncTask 在某個執行緒池中並行執行任務。
AsyncTask 的原始碼就是將一個任務封裝成 Runnable 之後放進執行緒池當中執行,執行完畢之後呼叫主執行緒的 Handler 傳送訊息到主執行緒當中進行處理。任務在預設執行緒池當中執行的時候,會被加入到一個雙端佇列中執行,執行完一個之後再執行下一個,以此來實現任務的序列執行。
瞭解 AsyncTask 的原始碼,可以參考筆者的這篇文章:《Android AsyncTask 原始碼分析》
- Q:介紹下 SurfaceView
SurfaceView 以及 TextureView 均繼承於 android.view.View
,它們都在獨立的執行緒
中繪製和渲染,常被用在對繪製的速率要求比較高的應用場景中,用來解決普通 View 因為繪製的時間延遲而帶來的掉幀
的問題,比如用作相機預覽、視訊播放的媒介等。
SurfaceView 提供了嵌入在檢視層次結構內的專用繪圖圖層 (Surface)。圖層 (Surface) 處於 Z 軸
,位於持有 SurfaceView 的視窗之後。SurfaceView 在視窗上開了一個透明的 “洞” 以展示圖面。Surface 的排版顯示受到檢視層級關係的影響,它的兄弟檢視結點會在頂端顯示。注意,如果 Surface 上面有透明控制元件,那麼每次 Surface 變化都會引起框架重新計算它和頂層控制元件的透明效果,這會影響效能。SurfaceView 使用雙緩衝,SurfaceView 自帶一個 Surface,這個 Surface 在 WMS 中有自己對應的WindowState,在 SurfaceFlinger 中也會有自己的 Layer。這樣的好處是對這個Surface的渲染可以放到單獨執行緒去做。因為這個 Surface 不在 View hierachy 中,它的顯示也不受 View 的屬性控制,所以不能進行平移,縮放等變換,也不能放在其它 ViewGroup 中,一些 View 中的特性也無法使用。
TextureView 在 Andriod 4.0 之後的 API 中才能使用,並且必須在硬體加速的視窗中。和 SurfaceView 不同,它不會在 WMS 中單獨建立視窗,而是作為 View hierachy 中的一個普通 View,因此可以和其它普通 View 一樣進行移動,旋轉,縮放,動畫等變化。它佔用記憶體比 SurfaceView 高,在 5.0 以前在主執行緒渲染,5.0 以後有單獨的渲染執行緒。
更多內容請參考:Android:解析 SurfaceView & TextureView
2.2 View 體系
事件分發機制等
- Q:Android 事件分發機制
- Q:事件傳遞機制的介紹
- Q:View 事件傳遞
- Q:觸控事件的分發?
Activity 的層級:Activity->PhoneWindow->DecorView
當觸控事件發生的時候,首先會被 Activity 接收到,然後該 Activity 會通過其內部的 dispatchTouchEvent(MotionEvent)
將事件傳遞給內部的 PhoneWindow
;接著 PhoneWindow
會把事件交給 DecorView
,再由 DecorView
交給根 ViewGroup
。剩下的事件傳遞就只在 ViewGroup
和 View
之間進行。
事件分發機制本質上是一個深度優先
的遍歷演算法。事件分發機制的核心程式碼:
boolean dispatchTouchEvent(MotionEvent e) {
boolean result;
if (onInterceptTouchEvent(e)) { // 父控制元件可以覆寫並返回 true 以攔截
result = super.dispatchTouchEvent(e); // 呼叫 View 中該方法的實現
} else {
for (child in children) {
result = child.dispatchTouchEvent(e); // 這裡的 child 分成 View 和 ViewGroup 兩者情形
if (result) break; // 被子控制元件消費,停止分發
}
}
return result;
}
複製程式碼
對於 dispatchTouchEvent()
方法,在 View 的預設實現中,會先交給 onTouchEvent()
進行處理,若它返回了 true 就消費了,否則根據觸控的型別,決定是交給 OnClickListener
還是 OnLongClickListener
繼續處理。
事件分發機制和 View 的體系請參考筆者文章:《View 體系詳解:座標系、滑動、手勢和事件分發機制》,整體上事件分發機制應該分成三個階段來進行說明:1).從 Activity 到 DecorView 的過程;2).ViewGroup 中的分發的過程;3).交給 View 之後的實現過程。
- Q:封裝 View 的時候怎麼知道 View 的大小
- Q:點選事件被攔截,但是想傳到下面的 View,如何操作?
- Q:計算一個 view 的巢狀層級
按照廣度優先演算法進行遍歷
2.3 列表控制元件
- Q:ListView 的優化?
- Q:ListView 重用的是什麼?
ListView 預設快取一頁的 View,也就是你當前 Listview 介面上有幾個 Item 可以顯示,,Lstview 就快取幾個。當現實第一頁的時候,由於沒有一個 Item 被建立,所以第一頁的 Item 的 getView()
方法中的第二個引數都是為 null 的。
ViewHolder 同樣也是為了提高效能。就是用來在快取使用 findViewById()
方法得到的控制元件,下次的時候可以直接使用它而不用再進行 find
了。
關於 ListView 的 ViewHolder 等的使用,可以參考這篇文章:ListView 複用和優化詳解
- Q:RecycleView 的使用,原理,RecycleView 優化
- Q:Recycleview Listview 的區別,效能
- 裝飾;
- 手勢滑動、拖拽;
- 頂部懸浮效果;
- Q:Listview 圖片載入錯亂的原理和解決方案
2.4 其他控制元件
- Q:LinearLayout、RelativeLayout、FrameLayout 的特性、使用場景
- Q:ViewPager 使用細節,如何設定成每次只初始化當前的 Fragment,其他的不初始化
2.5 資料儲存
- Q:Android 中資料儲存方式
SP,SQLite,ContentProvider,File,Server
3、架構相關
- Q:模組化實現(好處,原因)
- Q:專案元件化的理解
- Q:模式 MVP、MVC 介紹
- Q:MVP 模式
MVC (Model-View-Controller, 模型-檢視-控制器),標準的MVC是這個樣子的:
- 模型層 (Model):業務邏輯對應的資料模型,無 View 無關,而與業務相關;
- 檢視層 (View):一般使用 XML 或者 Java 對介面進行描述;
- 控制層 (Controllor):在 Android 中通常指 Activity 和Fragment,或者由其控制的業務類。
在 Android 開發中,就是指直接使用 Activity 並在其中寫業務邏輯的開發方式。顯然,一方面 Activity 本身就是一個檢視,另一方面又要負責處理業務邏輯,因此邏輯會比較混亂。這種開發方式不太適合 Android 開發。
MVP (Model-View-Presenter)
- 模型層 (Model):主要提供資料存取功能。
- 檢視層 (View):處理使用者事件和檢視。在 Android 中,可能是指 Activity、Fragment 或者 View。
- 展示層 (Presenter):負責通過 Model 存取書資料,連線 View 和 Model,從 Model 中取出資料交給 View。
實際開發中會像下面這樣定義一個契約介面
public interface HomeContract {
interface IView extends BaseView {
void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists);
void setNextPage(List<HomeBean.IssueList.ItemList> itemLists);
void onError(String msg);
}
interface IPresenter extends BasePresenter {
void requestFirstPage();
void requestNextPage();
}
}
複製程式碼
然後讓 Fragment 或者 Activity 等繼承 IView,例項化一個 IPresenter,並在構造方法中將自己引入到 IPresenter 中。這樣 View 和 Presenter 就相互持有了對方的引用。當要發起一個網路請求的時候,View 中呼叫 Presenter 的方法,Presenter 拿到了結果之後回撥 View 的方法。這樣就使得 View 和 Presenter 只需要關注自身的責任即可。
MVP 缺點:1). Presenter 中除了應用邏輯以外,還有大量的 View->Model,Model->View 的手動同步邏輯,造成 Presenter 比較笨重,維護起來會比較困難;2). 由於對檢視的渲染放在了 Presenter 中,所以檢視和 Presenter 的互動會過於頻繁;3). 如果 Presenter 過多地渲染了檢視,往往會使得它與特定的檢視的聯絡過於緊密,一旦檢視需要變更,那麼 Presenter 也需要變更了。
MVVM 是 Model-View-ViewModel 的簡寫。它本質上就是 MVC 的改進版。MVVM 就是將其中的 View 的狀態和行為抽象化,讓我們將檢視 UI 和業務邏輯分開。
- 模型層 (Model):負責從各種資料來源中獲取資料;
- 檢視層 (View):在 Android 中對應於 Activity 和 Fragment,用於展示給使用者和處理使用者互動,會驅動 ViewModel 從 Model 中獲取資料;
- ViewModel 層:用於將 Model 和 View 進行關聯,我們可以在 View 中通過 ViewModel 從 Model 中獲取資料;當獲取到了資料之後,會通過自動繫結,比如 DataBinding,來將結果自動重新整理到介面上。
優點:
- 低耦合:檢視(View)可以獨立於Model變化和修改,一個 ViewModel 可以繫結到不同的 View 上,當 View 變化的時候 Model 可以不變,當 Model 變化的時候 View 也可以不變。
- 可重用性:你可以把一些檢視邏輯放在一個 ViewModel 裡面,讓很多 view 重用這段檢視邏輯。
- 獨立開發:開發人員可以專注於業務邏輯和資料的開發(ViewModel),設計人員可以專注於頁面設計。
- 可測試:介面素來是比較難於測試的,而現在測試可以針對 ViewModel 來寫。
關於移動應用架構部分內容可以參考筆者的文章:《Android 架構設計:MVC、MVP、MVVM和元件化》,另外關於 MVVM 架構設計中的 ViewModel 和 LiveData 的機制可以參考:《淺談 ViewModel 的生命週期控制》 和 《淺談 LiveData 的通知機制》
4、系統原始碼
- Q:畫出 Android 的大體架構圖
- Q:App 啟動流程,從點選桌面開始
- Q:Activity 棧
- Q:簡述 Activity 啟動全部過程?
- Q:ActicityThread 相關?
startActivity()
-> Process.start()
-> Socket -> (SystemServer) -> Zygote.fork() -> VM,Binder 執行緒池 -> ActivityThread.main()
Instrumentation.execStartActivity()
-> IApplicationThread+AMS+H
-> 校驗使用者資訊等 -> 解析 Intent -> 回撥 IApplicationThread.scheduleTransaction()
- Q:App 是如何沙箱化,為什麼要這麼做?
Android是一個許可權分離的系統,這是利用 Linux 已有的許可權管理機制,通過為每一個 Application 分配不同的 uid 和 gid,從而使得不同的 Application 之間的私有資料和訪問(native以及java層通過這種 sandbox 機制,都可以)達到隔離的目的 。與此同時,Android 還在此基礎上進行擴充套件,提供了 permission 機制,它主要是用來對 Application 可以執行的某些具體操作進行許可權細分和訪問控制,同時提供了 per-URI permission 機制,用來提供對某些特定的資料塊進行 ad-hoc 方式的訪問。
-
Q:許可權管理系統(底層的許可權是如何進行 grant 的)
-
Q:動態許可權適配方案,許可權組的概念
-
Q:大體說清一個應用程式安裝到手機上時發生了什麼?
-
Q:應用安裝過程
應用安裝的時候涉及幾個類,分別時 PackageManager, ApplicationPackageManager 和 PMS. 它們之間的關係是,PackageManager 是一個抽象類,它的具體實現是 ApplicationPackageManager,而後者的所有實現都是靠 PMS 實現的。PMS 是一種遠端的服務,與 AMS 相似,在同一方法中啟動。另外,還有 Installer 它是安裝應用程式的輔助類,它也是一種系統服務,與 PMS 在同一方法中啟動。它會通過 Socket 與遠端的 Installd 建立聯絡。這是因為許可權的問題,PMS 只有 system 許可權。installd 卻是具有 root 許可權。(Installd 的作用好像就是建立一個目錄)
installd是由Android系統init程式(pid=1),在解析init.rc檔案的程式碼時,通過fork建立使用者空間的守護程式intalld。啟動時,進入監聽socket,當客戶端傳送過來請求時,接收客戶端的請求,並讀取客戶端傳送過來的命令資料,並根據讀取客戶端命令來執行命令操作。
Android上應用安裝可以分為以下幾種方式:
- 系統安裝:開機的時候,沒有安裝介面
- adb 命令安裝:通過abd命令列安裝,沒有安裝介面
- 應用市場安裝,這個要視應用的許可權,有系統的許可權無安裝介面(例如MUI的小米應用商店)
- 第三方安裝,有安裝介面,通過packageinstaller.apk來處理安裝及解除安裝的過程的介面
apk的大體流程如下:
- 第一步:拷貝檔案到指定的目錄:在Android系統中,apk安裝檔案是會被儲存起來的,預設情況下,使用者安裝的apk首先會被拷貝到/data/app目錄下,/data/app目錄是使用者有許可權訪問的目錄,在安裝apk的時候會自動選擇該目錄存放使用者安裝的檔案,而系統出場的apk檔案則被放到了/system分割槽下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,該分割槽只有ROOT許可權的使用者才能訪問,這也就是為什麼在沒有Root手機之前,我們沒法刪除系統出場的app的原因了。
- 第二步:解壓縮apk,寶貝檔案,建立應用的資料目錄:為了加快app的啟動速度,apk在安裝的時候,會首先將app的可執行檔案dex拷貝到/data/dalvik-cache目錄,快取起來。然後,在/data/data/目錄下建立應用程式的資料目錄(以應用的包名命名),存放在應用的相關資料,如資料庫、xml檔案、cache、二進位制的so動態庫等。
- 第三步:解析apk的AndroidManifest.xml檔案:提取出這個apk的重要資訊寫入到packages.xml檔案中,這些資訊包括:許可權、應用包名、APK的安裝位置、版本、userID等等。
- 第四步:顯示快捷方式:Home應用程式,負責從PackageManagerService服務中把這些安裝好的應用程式取出來。在Android系統中,負責把系統中已經安裝的應用程式在桌面中展現出來的Home應用就是Launcher了。
普通安裝:
PackagInstaller是安卓上預設的應用程式,用它來安裝普通檔案。PackageInstaller呼叫一個叫做InstallAppProgress的activity來獲取使用者發出的指令。InstallAppProgress會請求Package Manager服務,然後通過installed來安裝包檔案。它提供了安裝的頁面和安裝進度相關頁面,我們平時安裝應用時顯式的就是它。(原始碼)
最終的安卓過程則是交給 PMS 的 installPackageLI()
方法來完成,它也會先對 manifest 進行解析,然後將解析的結果新增到 PMS 的快取中,並註冊四大元件。
如果是第一次安裝的時候就會呼叫 scanPackageLI()
方法來進行安裝。
- PMS 在啟動 SystemSever 時啟動,呼叫構造方法,對目錄進行掃描,包括系統、供應商等的目錄,複製 APK,解析 APK,快取 APK 資訊;
- ADB -> pm.jar -> PMS -> Installd() -> Installer(系統服務)
- Q:系統啟動流程 Zygote 程式 –> SystemServer 程式 –> 各種系統服務 –> 應用程式?
按下電源之後,首先載入載入程式 BootLoader 到 RAM;然後,執行載入程式 BootLoader 以把系統 OS 拉起來;接著,啟動 Linux 核心;核心中啟動的第一個使用者程式是 init 程式,init 程式會通過解析 init.rc 來啟動 zygote 服務;Zygote 又會進一步的啟動 SystemServer;在 SystemServer 中,Android 會啟動一系列的系統服務供使用者呼叫。
Init 程式會啟動之後會解析 init.rc
檔案,該檔案由固定格式的指令組成,在 AOSP 中有說明它的規則。其中的每個指令對應系統中的類。在解析檔案的時候會將其轉換成對應的類的例項來儲存,比如 service 開頭的指令會被解析成 Service 物件。在解析 init.rc
檔案之前會根據啟動時的屬性設定載入指定的 rc 檔案到系統中。當解析完畢之後系統會觸發初始化操作。它會通過呼叫 Service 的 start()
方法來啟動屬性服務,然後進入 app_main.cpp
的 main() 方法。這個方法中會根據 service 指令的引數來決定呼叫 Java 層的 ZygoteInit 還是 RuntimeInit. 這裡會呼叫後者的 ZygoteInit 初始化 Zygote 程式。
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
app_usage();
}
複製程式碼
它呼叫的方式是通過 runtime 的 start()
方法,而這個 runtime 就是 AndroidRuntime. 但是在執行 Java 類之前得先有虛擬機器,所以它會先啟動虛擬機器例項,然後再呼叫 ZygoteInit 的方法。所以,AndroidRuntime 的 start()
方法中主要做了三件事情:1).呼叫函式 startVM()
啟動虛擬機器;2).呼叫函式 startReg()
註冊 JNI 方法;3).呼叫 com.android.internal.os.ZygoteInit
類的 main()
函式。
ZygoteInit 用來初始化 Zygote 程式的,它的 main()
函式中主要做了三件事情:
- 呼叫
registerZygoteSocket()
函式建立了一個 socket 介面,用來和 AMS 通訊;(Android 應用程式程式啟動過程中,AMS 是通過Process.start()
函式來建立一個新的程式的,而Process.start()
函式會首先通過 Socket 連線到 Zygote 程式中,最終由 Zygote 程式來完成建立新的應用程式程式,而 Process 類是通過openZygoteSocketIfNeeded()
函式來連線到 Zygote 程式中的 Socket.) - 呼叫
startSystemServer()
函式來啟動 SystemServer 元件,Zygote 程式通過Zygote.forkSystemServer()
函式來建立一個新的程式來啟動 SystemServer 元件;(SystemServer 的 main 方法將會被呼叫,並在這裡啟動 Binder 中的 ServiceManager 和各種系統執行所需的服務,PMS 和 AMS 等) - 呼叫
runSelectLoopMode()
函式進入一個無限迴圈在前面建立的 socket 介面上等待 AMS 請求建立新的應用程式程式。
總結一下:
- 系統啟動時 init 程式會建立 Zygote 程式,Zygote 程式負責後續 Android 應用程式框架層的其它程式的建立和啟動工作。
- Zygote 程式會首先建立一個 SystemServer 程式,SystemServer 程式負責啟動系統的關鍵服務,如包管理服務 PMS 和應用程式元件管理服務 AMS。
- 當我們需要啟動一個 Android 應用程式時,AMS 會通過 Socket 程式間通訊機制,通知 Zygote 程式為這個應用程式建立一個新的程式。
- Q:描述清點選 Android Studio 的 build 按鈕後發生了什麼?
編譯打包的過程->adb->安裝過程 PMS->應用啟動過程 AMS
Android 高階面試系列文章,關注作者及時獲取更多面試資料,
- Android 高階面試-1:Handler 相關
- Android 高階面試-2:IPC 相關
- Android 高階面試-3:語言相關
- Android 高階面試-4:虛擬機器相關
- Android 高階面試-5:四大元件、系統原始碼等
本系列以及其他系列的文章均維護在 Github 上面:Github / Android-notes,歡迎 Star & Fork. 如果你喜歡這篇文章,願意支援作者的工作,請為這篇文章點個贊?!