Android 高級面試-5:四大組件、系統源碼等

一、四大組件

1.1 Activity

  • Q:在兩個 Activity 之間傳遞對象還須要注意什麼呢?

對象的大小。Intent 中的 Bundle 是使用 Binder 機制進行數據傳送的。能使用的 Binder 的緩衝區是有大小限制的(有些手機是 2 M),而一個進程默認有 16 個 Binder 線程,因此一個線程能佔用的緩衝區就更小了(有人之前作過測試,大約一個線程能夠佔用 128 KB)。可使用 EventBus 來傳遞數據,而不是直接使用 Intent 進行傳遞。java

  • Q:onSaveInstanceState() 和 onRestoreInstanceState()

當 Activity 被銷燬的時候回調用 onSaveInstanceState() 方法來存儲當前的狀態。這樣當 Activity 被重建的時候,能夠在 onCreate()onRestoreInstanceState() 中恢復狀態。android

對於 targetAPI 爲 28 及之後的應用,該方法會在 onStop() 方法以後調用,對於以前的設備,這方法會在 onStop() 以前調用,可是沒法肯定是在 onPause() 以前仍是以後調用。c++

onRestoreInstanceState() 方法用來恢復以前存儲的狀態,它會在 onStart()onPostCreate() 之間被調用。此外,你也能夠直接在 onCreate() 方法中進行恢復,可是基於這個方法調用的時機,若是有特別需求,能夠在這個方法中進行處理。git

  • Q:SingleTask 啓動模式
  • Q:Activity 啓動模式
  1. standard:默認,每次啓動的時候會建立一個新的實例,而且被建立的實例所在的棧與啓動它的 Activity 是同一個棧。好比,A 啓動了 B,那麼 B 將會與 A 處在同一個棧。假如,咱們使用 Application 的 Context 啓動一個 Activity 的時候會拋出異常,這是由於新啓動的 Activity 不知道本身將會處於哪一個棧。能夠在啓動 Activity 的時候使用 FLAG_ACTIVITY_NEW_TASK。這樣新啓動的 Acitivyt 將會建立一個新的棧。
  2. singleTop:棧頂複用,若是將要啓動的 Activity 已經位於棧頂,那麼將會複用棧頂的 Activity,而且會調用它的 onNewIntent()。常見的應用場景是從通知打開 Activity 時。
  3. singleTask:單例,若是啓動它的任務棧中存在該 Activity,那麼將會複用該 Activity,而且會將棧內的、它之上的全部的 Activity 清理出去,以使得該 Activity 位於棧頂。常見的應用場景是啓動頁面、購物界面、確認訂單界面和付款界面等。
  4. singleInstance:這種啓動模式會在啓動的時候爲其指定一個單獨的棧來執行。若是用一樣的intent 再次啓動這個 Activity,那麼這個 Activity 會被調到前臺,而且會調用其 onNewIntent() 方法。
  • Q:下拉狀態欄是否是影響 Activity 的生命週期,若是在 onStop() 的時候作了網絡請求,onResume() 的時候怎麼恢復
  • Q:前臺切換到後臺,而後再回到前臺,Activity 生命週期回調方法。彈出 Dialog,生命值週期回調方法。
  • Q:Activity 生命週期
  • Q:Activity 上有 Dialog 的時候按 Home 鍵時的生命週期
  • Q:橫豎屏切換的時候,Activity 各類狀況下的生命週期

Android 下拉通知欄不會影響 Activity 的生命週期方法。github

彈出 Dialog,生命週期:實際上是否彈出 Dialog,並不影響 Activity 的生命週期,因此這時和正常啓動時 Activity 的生命回調方法一致: onCreate() -> onStart() -> onResume()面試

Activity 的生命週期

這裏咱們總結一下在實際的使用過程當中可能會遇到的一些 Acitivity 的生命週期過程:算法

  1. 當用戶打開新的 Activity 或者切換回桌面:會通過的生命週期爲 onPause()->onStop()。由於此時 Activity 已經變成不可見了,固然,若是新打開的 Activity 用了透明主題,那麼 onStop() 不會被調用,所以原來的 Activity 只是不能交互,可是仍然可見。
  2. 重新的 Activity 回到以前的 Activity 或者從桌面回到以前的 Activity:會通過的生命週期爲 onRestart()->onStart()-onResume()。此時是從 onStop() 經 onRestart() 回到 onResume() 狀態。
  3. 若是在上述 1 的狀況下,進入後臺的 Activity 由於內存不足被銷燬了,那麼當再次回到該 Activity 的時候,生命週期方法將會從 onCreate() 開始執行到 onResume()。
  4. 當用戶按下 Back 鍵時:若是當前 Activity 被銷燬,那麼通過的生命週期將會是 onPause()->onStop()->onDestroy()

具體地,當存在兩個 Activity,分別是 A 和 B 的時候,在各類狀況下,它們的生命週期將會通過:數據庫

  1. Back 鍵 Home 鍵
    1. 當用戶點擊 A 中按鈕來到 B 時,假設 B 所有遮擋住了 A,將依次執行:A.onPause()->B.onCreate()->B.onStart()->B.onResume->A.onStop()
    2. 接1,此時若是點擊 Back 鍵,將依次執行:B.onPause()->A.onRestart()->A.onStart()->A.onResume()->B.onStop()->B.onDestroy()
    3. 接2,此時若是按下 Back 鍵,系統返回到桌面,並依次執行:A.onPause()->A.onStop()->A.onDestroy()
    4. 接2,此時若是按下 Home 鍵(非長按),系統返回到桌面,並依次執行A.onPause()->A.onStop()。因而可知,Back 鍵和 Home 鍵主要區別在因而否會執行 onDestroy()。
    5. 接2,此時若是長按 Home 鍵,不一樣手機可能彈出不一樣內容,Activity 生命週期未發生變化。
  2. 橫豎屏切換時 Activity 的生命週期
    1. 不設置 Activity 的 android:configChanges 時,切屏會從新調用各個生命週期,切橫屏時會執行一次,切豎屏時會執行兩次。
    2. 設置 Activity 的 android:configChanges=「orientation」 時,切屏仍是會從新調用各個生命週期,切橫、豎屏時只會執行一次。
    3. 設置 Activity 的 android:configChanges=「orientation|keyboardHidden」 時,切屏不會從新調用各個生命週期,只會執行 onConfiguration() 方法。
  • Q:Activity 之間的通訊方式
  1. Intent + onActivityResult() + setResult()
  2. 靜態變量(跨進程不行)
  3. 全局通訊,廣播或者 EventBus
  • Q:AlertDialog, PopupWindow, Activity 區別

AlertDialog 是 Dialog 的子類,因此它包含了 Dialog 類的不少屬性和方法。是彈出對話框的主要方式,對話框分紅支持包的和非支持包的,UI 效果上略有區別。設計模式

AlertDialog 與 PopupWindow 之間最本質的差別在於緩存

  1. AlertDialog 是非阻塞式對話框;而PopupWindow 是阻塞式對話框。AlertDialog 彈出時,後臺還能夠作事情;PopupWindow 彈出時,程序會等待,在PopupWindow 退出前,程序一直等待,只有當咱們調用了 dismiss() 方法的後,PopupWindow 退出,程序纔會向下執行。咱們在寫程序的過程當中能夠根據本身的須要選擇使用 Popupwindow 或者是 Dialog.
  2. 二者最根本的區別在於有沒有新建一個 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 才行:

  1. Binder 用於跨進程的通訊方式,AIDL 能夠用來進行與遠程通訊,綁定服務的時候能夠拿到遠程的 Binder,而後調用它的方法就能夠從遠程拿數據。那麼若是但願對遠程的服務進行監聽呢?可使用 AIDL 中的 oneway 來定義回調的接口,而後在方法中傳入回調便可。也可使用 Messenger,向遠程發送信息的時候,附帶本地的 Messenger,而後遠程獲取本地的 Messenger 而後向其發送信息便可,詳見 IPC 相關一文:《Android 高級面試-2:IPC 相關》
  2. 廣播:使用廣播實現跨進程通訊
  3. 啓動服務的時候傳入值,使用 startService() 的方式

關於 Activity 相關的內容能夠參考筆者的文章:《Android 基礎回顧:Activity 基礎》

1.2 Service

  • Q:怎麼啓動 Service
  • Q:Service 的開啓方式
  • Q:Service 生命週期

Service的生命週期圖
其餘,

  1. Service 有綁定模式和非綁定模式,以及這兩種模式的混合使用方式。不一樣的使用方法生命週期方法也不一樣。
    1. 非綁定模式:當第一次調用 startService() 的時候執行的方法依次爲 onCreate()->onStartCommand();當 Service 關閉的時候調用 onDestory()
    2. 綁定模式:第一次 bindService() 的時候,執行的方法爲 onCreate()->onBind();解除綁定的時候會執行 onUnbind()->onDestory()
  2. 咱們在開發的過程當中還必須注意 Service 實例只會有一個,也就是說若是當前要啓動的 Service 已經存在了那麼就不會再次建立該 Service 固然也不會調用 onCreate() 方法。因此,
    1. 當第一次執行 startService(intent) 的時候,會調用該 Service 中的 onCreate()onStartCommand() 方法。
    2. 當第二次執行 startService(intent) 的時候,只會調用該 Service 中的 onStartCommand() 方法。(所以已經建立了服務,因此不須要再次調用 onCreate() 方法了)。
  3. bindService() 方法的第三個參數是一個標誌位,這裏傳入 BIND_AUTO_CREATE 表示在Activity 和 Service 創建關聯後自動建立 Service,這會使得 MyService 中的 onCreate() 方法獲得執行,但 onStartCommand() 方法不會執行。因此,在上面的程序中當調用了bindService() 方法的時候,會執行的方法有,Service 的 onCreate() 方法,以及 ServiceConnection 的 onServiceConnected() 方法。
  4. 在 3 中,若是想要中止 Service,須要調用 unbindService() 才行。
  5. 若是咱們既調用了 startService(),又調用 bindService() 會怎麼樣呢?這時無論你是單獨調用 stopService() 仍是 unbindService(),Service 都不會被銷燬,必需要將兩個方法都調用 Service 纔會被銷燬。也就是說,stopService() 只會讓 Service 中止,unbindService() 只會讓 Service 和 Activity 解除關聯,一個 Service 必需要在既沒有和任何 Activity 關聯又處理中止狀態的時候纔會被銷燬。
  • 進程保活
  • App 中喚醒其餘進程的實現方式

1.3 Broadcast

  • Q:BroadcastReceiver,LocalBroadcastReceiver 區別
  • Q:廣播的使用場景
  • Q:廣播的使用方式,場景
  • Q:廣播的分類?
  • Q:廣播(動態註冊和靜態註冊區別,有序廣播和標準廣播)

分類

  1. 按照註冊方式:靜態註冊和動態註冊兩種:
    1. 靜態廣播直接在 manifest 中註冊。限制:
      1. 在 Android 8.0 的平臺上,應用不能對大部分的廣播進行靜態註冊,也就是說,不能在 AndroidManifest 文件對有些廣播進行靜態註冊;
      2. 當程序運行在後臺的時候,靜態廣播中不能啓動服務。
    2. 動態廣播與靜態廣播類似,可是不須要在 Manifest 中進行註冊。注意當頁面被銷燬的時候須要取消註冊廣播!
  2. 按照做用範圍:本地廣播和普通廣播兩種,
    1. 普通廣播是全局的,全部應用程序均可以接收到,容易會引發安全問題。
    2. 本地廣播只可以在應用內傳遞,廣播接收器也只能接收應用內發出的廣播。本地廣播的核心類是 LocalBroadcastManager,使用它的靜態方法 getInstance() 獲取一個單例以後就可使用該單例的 registerReceiver()unregisterReceiver()sendBroadcast() 等方法來進行操做了。
  3. 按照是否有序:有序廣播和無序廣播兩種,無序廣播各接收器接收的順序沒法肯定,而且在廣播發出以後接收器只能接收,不能攔截和進行其餘處理,二者的區別主要體如今發送時調用的方法上。優先級高的會先接收到,優先級相等的話則順序不肯定。而且前面的廣播能夠在方法中向 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 之間傳遞數據的方式?
  1. 同一 Activity 的 Fragment 之間可使用 ViewModel 來交換數據;
  2. 使用 EventBus,廣播,靜態的;
  3. 經過 Activity 獲取到另外一個 Fragment,強轉以後使用它對外提供的 public 方法進行通訊;
  4. 經過 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 均可以,少數狀況下只能使用特定的 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 中。

二、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。剩下的事件傳遞就只在 ViewGroupView 之間進行。

事件分發機制本質上是一個深度優先的遍歷算法。事件分發機制的核心代碼:

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 的區別,性能
  1. 裝飾;
  2. 手勢滑動、拖拽;
  3. 頂部懸浮效果;
  • Q:Listview 圖片加載錯亂的原理和解決方案

2.4 其餘控件

  • Q:LinearLayout、RelativeLayout、FrameLayout 的特性、使用場景
  • Q:ViewPager 使用細節,如何設置成每次只初始化當前的 Fragment,其餘的不初始化

2.5 數據存儲

  • Q:Android 中數據存儲方式

SP,SQLite,ContentProvider,File,Server

三、架構相關

  • Q:模塊化實現(好處,緣由)
  • Q:項目組件化的理解
  • Q:模式 MVP、MVC 介紹
  • Q:MVP 模式

MVC (Model-View-Controller, 模型-視圖-控制器),標準的MVC是這個樣子的:

  1. 模型層 (Model):業務邏輯對應的數據模型,無 View 無關,而與業務相關;
  2. 視圖層 (View):通常使用 XML 或者 Java 對界面進行描述;
  3. 控制層 (Controllor):在 Android 中一般指 Activity 和Fragment,或者由其控制的業務類。

在 Android 開發中,就是指直接使用 Activity 並在其中寫業務邏輯的開發方式。顯然,一方面 Activity 自己就是一個視圖,另外一方面又要負責處理業務邏輯,所以邏輯會比較混亂。這種開發方式不太適合 Android 開發。

MVP (Model-View-Presenter)

  1. 模型層 (Model):主要提供數據存取功能。
  2. 視圖層 (View):處理用戶事件和視圖。在 Android 中,多是指 Activity、Fragment 或者 View。
  3. 展現層 (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 和業務邏輯分開。

  1. 模型層 (Model):負責從各類數據源中獲取數據;
  2. 視圖層 (View):在 Android 中對應於 Activity 和 Fragment,用於展現給用戶和處理用戶交互,會驅動 ViewModel 從 Model 中獲取數據;
  3. ViewModel 層:用於將 Model 和 View 進行關聯,咱們能夠在 View 中經過 ViewModel 從 Model 中獲取數據;當獲取到了數據以後,會經過自動綁定,好比 DataBinding,來將結果自動刷新到界面上。

優勢:

  1. 低耦合:視圖(View)能夠獨立於Model變化和修改,一個 ViewModel 能夠綁定到不一樣的 View 上,當 View 變化的時候 Model 能夠不變,當 Model 變化的時候 View 也能夠不變。
  2. 可重用性:你能夠把一些視圖邏輯放在一個 ViewModel 裏面,讓不少 view 重用這段視圖邏輯。
  3. 獨立開發:開發人員能夠專一於業務邏輯和數據的開發(ViewModel),設計人員能夠專一於頁面設計。
  4. 可測試:界面素來是比較難於測試的,而如今測試能夠針對 ViewModel 來寫。

關於移動應用架構部份內容能夠參考筆者的文章:《Android 架構設計:MVC、MVP、MVVM和組件化》,另外關於 MVVM 架構設計中的 ViewModel 和 LiveData 的機制能夠參考:《淺談 ViewModel 的生命週期控制》《淺談 LiveData 的通知機制》

四、系統源碼

  • 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上應用安裝能夠分爲如下幾種方式:

  1. 系統安裝:開機的時候,沒有安裝界面
  2. adb 命令安裝:經過abd命令行安裝,沒有安裝界面
  3. 應用市場安裝,這個要視應用的權限,有系統的權限無安裝界面(例如MUI的小米應用商店)
  4. 第三方安裝,有安裝界面,經過packageinstaller.apk來處理安裝及卸載的過程的界面

apk的大致流程以下:

  1. 第一步:拷貝文件到指定的目錄:在Android系統中,apk安裝文件是會被保存起來的,默認狀況下,用戶安裝的apk首先會被拷貝到/data/app目錄下,/data/app目錄是用戶有權限訪問的目錄,在安裝apk的時候會自動選擇該目錄存放用戶安裝的文件,而系統出場的apk文件則被放到了/system分區下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,該分區只有ROOT權限的用戶才能訪問,這也就是爲何在沒有Root手機以前,咱們無法刪除系統出場的app的緣由了。
  2. 第二步:解壓縮apk,寶貝文件,建立應用的數據目錄:爲了加快app的啓動速度,apk在安裝的時候,會首先將app的可執行文件dex拷貝到/data/dalvik-cache目錄,緩存起來。而後,在/data/data/目錄下建立應用程序的數據目錄(以應用的包名命名),存放在應用的相關數據,如數據庫、xml文件、cache、二進制的so動態庫等。
  3. 第三步:解析apk的AndroidManifest.xml文件:提取出這個apk的重要信息寫入到packages.xml文件中,這些信息包括:權限、應用包名、APK的安裝位置、版本、userID等等。
  4. 第四步:顯示快捷方式:Home應用程序,負責從PackageManagerService服務中把這些安裝好的應用程序取出來。在Android系統中,負責把系統中已經安裝的應用程序在桌面中展示出來的Home應用就是Launcher了。

普通安裝:

PackagInstaller是安卓上默認的應用程序,用它來安裝普通文件。PackageInstaller調用一個叫作InstallAppProgress的activity來獲取用戶發出的指令。InstallAppProgress會請求Package Manager服務,而後經過installed來安裝包文件。它提供了安裝的頁面和安裝進度相關頁面,咱們平時安裝應用時顯式的就是它。(源碼

最終的安卓過程則是交給 PMS 的 installPackageLI() 方法來完成,它也會先對 manifest 進行解析,而後將解析的結果添加到 PMS 的緩存中,並註冊四大組件。

若是是第一次安裝的時候就會調用 scanPackageLI() 方法來進行安裝。

安裝大體流程圖

  1. PMS 在啓動 SystemSever 時啓動,調用構造方法,對目錄進行掃描,包括系統、供應商等的目錄,複製 APK,解析 APK,緩存 APK 信息;
  2. 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() 函數中主要作了三件事情:

  1. 調用 registerZygoteSocket() 函數建立了一個 socket 接口,用來和 AMS 通信;(Android 應用程序進程啓動過程當中,AMS 是經過 Process.start() 函數來建立一個新的進程的,而 Process.start() 函數會首先經過 Socket 鏈接到 Zygote 進程中,最終由 Zygote 進程來完成建立新的應用程序進程,而 Process 類是經過 openZygoteSocketIfNeeded() 函數來鏈接到 Zygote 進程中的 Socket.)
  2. 調用 startSystemServer() 函數來啓動 SystemServer 組件,Zygote 進程經過 Zygote.forkSystemServer() 函數來建立一個新的進程來啓動 SystemServer 組件;(SystemServer 的 main 方法將會被調用,並在這裏啓動 Binder 中的 ServiceManager 和各類系統運行所需的服務,PMS 和 AMS 等)
  3. 調用 runSelectLoopMode() 函數進入一個無限循環在前面建立的 socket 接口上等待 AMS 請求建立新的應用程序進程。

總結一下:

  1. 系統啓動時 init 進程會建立 Zygote 進程,Zygote 進程負責後續 Android 應用程序框架層的其它進程的建立和啓動工做。
  2. Zygote 進程會首先建立一個 SystemServer 進程,SystemServer 進程負責啓動系統的關鍵服務,如包管理服務 PMS 和應用程序組件管理服務 AMS。
  3. 當咱們須要啓動一個 Android 應用程序時,AMS 會經過 Socket 進程間通訊機制,通知 Zygote 進程爲這個應用程序建立一個新的進程。
  • Q:描述清點擊 Android Studio 的 build 按鈕後發生了什麼?

編譯打包的過程->adb->安裝過程 PMS->應用啓動過程 AMS


Android 高級面試系列文章,關注做者及時獲取更多面試資料

本系列以及其餘系列的文章均維護在 Github 上面:Github / Android-notes,歡迎 Star & Fork. 若是你喜歡這篇文章,願意支持做者的工做,請爲這篇文章點個贊👍!

相關文章
相關標籤/搜索