Android 開發者,你真的懂 Context 嗎?

前言

Context 相信所有的 Android 開發人員基本上每天都在接觸,因爲它太常見了。但是這並不代表每位 Android 開發者真正搞懂 Context!

下列面試題,你真的都懂嗎?
(後文有回答)

  1. 面試官:Android 中有哪些類型的 Context,它們有什麼區別?
  2. 面試官:一個APP應用裏有幾個 Context 呢?
  3. 面試官:Android 開發過程中,Context 有什麼用?
  4. 面試官:ContextImpl 實例是什麼時候生成的,另外在 Activity 的 onCreate 裏能拿到這個實例嗎?
  5. 面試官:ContextImpl 、ContextWrapper、ContextThemeWrapper 有什麼區別?
  6. 面試官:Activity Context、Service Context、Application Context、Base Context 有什麼區別?
  7. 面試官:爲什麼不推薦使用 BaseContext?
  8. 面試官:ContentProvider 裏的 Context 是什麼時候初始化的呢?
  9. 面試官:BroadcastReceiver 裏的 Context是哪來的?

一、Context是什麼

Context 是 Android 中用的非常多的一種概念,常被翻譯成上下文,這種概念在其他的技術中也有所使用。Android 官方對它的解釋,可以理解爲應用程序環境中全局信息的接口,它整合了許多系統級的服務,可以用來獲取應用中的類、資源,以及可以進行應用程序級的調起操作,比如啓動 Activity、Service等等,而且 Context 這個類是 abstract 的,不包含具體的函數實現。

二、Context結構

Context 是維持 Android 程序中各組件能夠正常工作的一個核心功能類。

Context 本身是一個抽象類,其主要實現類爲 ContextImpl,另外有直系子類兩個:ContextWrapperContextThemeWrapper。這兩個子類都是 Context 的代理類,它們繼承關係如下:

在這裏插入圖片描述

1、ContextImpl類介紹

ContextImpl 是 Context API 的常見實現,它爲 Activity 和其他應用程序組件提供基本上下文對象,說的通俗一點就是 ContextImpl 實現了抽象類的方法,我們在使用 Context 的時候的方法就是它實現的。

2、ContextWrapper類介紹

ContextWrapper 類代理 Context 的實現,將其所有調用簡單地委託給另一個 Context 對象(ContextImpl),可以被分類爲修飾行爲而不更改原始 Context 的類,其實就 Context 類的修飾類。真正的實現類是 ContextImpl,ContextWrapper 裏面的方法調用也是調用 ContextImpl 裏面的方法。

3、ContextThemeWrapper

就是一個帶有主題的封裝類,比 ContextWrapper 多了主題,它的一個直接子類就是 Activity。
通過 Context 的繼承關係圖結合我們幾個開發中比較熟悉的類,Activity、Service、Application,所以我們可以認爲 Context 一共有三種類型,分別是 Application、Activity 和Service,他們分別承擔不同的作用,但是都屬於 Context,而他們具有 Context 的功能則是由ContextImpl 類實現的。

三、Context的數量

其實根據上面的 Context 類型我們就已經可以得出答案了。Context 一共有 Application、Activity 和 Service 三種類型,因此一個應用程序中 Context 數量的計算公式就可以這樣寫:

Context數量 = Activity數量 + Service數量 + 1

上面的1代表着 Application 的數量,因爲一個應用程序中可以有多個 Activity 和多個 Service,但是只能有一個 Application。

四、Context注意事項

Context 如果使用不恰當很容易引起內存泄露問題。最簡單的例子比如說引用了 Context 的錯誤的單例模式:

public class Singleton {
    private static Singleton instance;
    private Context mContext;

    private Singleton(Context context) {
        this.mContext = context;
    }

    public static Synchronized Singleton getInstance(Context context) {
        if (instance == null) {
            instance = new Singleton(context);
        }
        return instance;
    }
}

上述代碼中,我們使得了一個靜態對象持有 Context 對象,而靜態數據的生命一般是長於普通數據的,因此當 Context 被銷燬(例如假設這裏持有的是 Activity 的上下文對象,當 Activity 被銷燬的時候),因爲 instance 仍然持有 Context 的引用,導致 Context 雖然被銷燬了但是卻無法被GC機制回收,因爲造成內存泄露問題。

而一般因爲Context所造成的內存泄漏,基本上都是 Context 已經被銷燬後,卻因爲被引用導致GC回收失敗。但是 Application 的 Context 對象卻會隨着當前進程而一直存在,所以使用 Context 是應該注意:

  • 當 Application 的 Context 能搞定的情況下,並且生命週期長的對象,優先使用 Application 的 Context。

  • 不要讓生命週期長於 Activity 的對象持有到 Activity 的引用。

  • 儘量不要在 Activity 中使用非靜態內部類,因爲非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作爲弱引用持有。

五、如何正確回覆以上面試題?

1.面試官:Android 中有哪些類型的 Context,它們有什麼區別?

應用有 Activity 、Service、Application 這些 Context 。

共同點:它們都是 ContextWrapper 的子類,而 ContextWrapper 的成員變量 mBase 可以用來存放系統實現的 ContextImpl,這樣我們在調用如 Activity 的 Context 方法時,都是通過靜態代理的方式最終調用到 ContextImpl 的方法。我們調用 ContextWrapper 的 getBaseContext 方法就能拿到 ContextImpl 的實例。

不同點:它們有各自不同的生命週期;在功能上,只有 Activity 顯示界面,正因爲如此,Activity 繼承的是 ContextThemeWrapper 提供一些關於主題,界面顯示的能力,間接繼承了 ContextWrapper ;而 Applicaiton 、Service 都是直接繼承 ContextWrapper ,所以我們要記住一點,凡是跟 UI 有關的,都應該用 Activity 作爲 Context 來處理,否則要麼會報錯,要麼 UI 會使用系統默認的主題。

2.面試官:一個APP應用裏有幾個 Context 呢?

Context 一共有 Application 、Activity 和 Service 三種類型,因此一個應用程序中 Context 數量的計算公式就可以這樣寫:

Context 數量 = Activity 數量 + Service 數量 + 1
上面的1代表着 Application 的數量,因爲一個應用程序中可以有多個Activity和多個 Service,但是只能有一個 Application。

3.面試官:Android 開發過程中,Context 有什麼用?

Context 就相當於 Application 的大管家,主要負責:

  • 四大組件的交互,包括啓動 Activity、Broadcast、Service,獲取 ContentResolver 等。

  • 獲取系統/應用資源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等。

  • 文件,包括獲取緩存文件夾、刪除文件、SharedPreference 相關等。

  • 數據庫(SQLite)相關,包括打開數據庫、刪除數據庫、獲取數據庫路徑等。

  • 其它輔助功能,比如設置 ComponentCallbacks,即監聽配置信息改變、內存不足等事件的發生

4.面試官:ContextImpl 實例是什麼時候生成的,在 Activity 的 onCreate 裏能拿到這個實例嗎?

可以。我們開發的時候,經常會在 onCreate 裏拿到 Application,如果用 getApplicationContext 取,最終調用的就是 ContextImpl 的 getApplicationContext 方法,如果調用的是 getApplication 方法,雖然沒調用到 ContextImpl ,但是返回 Activity 的成員變量 mApplication 和 ContextImpl 的初始化時機是一樣的。
再說下它的原理,Activity 真正開始啓動是從 ActivityThread.performLaunchActivity 開始的,這個方法做了這些事:

  • 通過 ClassLoader 去加載目標 Activity 的類,從而創建 對象。

  • 從 packageInfo 裏獲取 Application 對象。

  • 調用 createBaseContextForActivity 方法去創建 ContextImpl。

  • 調用 activity.attach ( contextImpl , application) 這個方法就把 Activity 和 Application 以及 ContextImpl 關聯起來了,就是上面結論裏說的時機一樣。

  • 最後調用 activity.onCreate 生命週期回調。

通過以上的分析,我們知道了 Activity 是先創建類,再初始化 Context ,最後調用 onCreate , 從而得出問題的答案。不僅 Activity 是這樣, Application 、Service 裏的 Context 初始化也都是這樣的。

5.面試官:ContextImpl 、ContextWrapper、ContextThemeWrapper 有什麼區別?

ContextWrapper、ContextThemeWrapper 都是 Context 的代理類,二者的區別在於 ContextThemeWrapper 有自己的 Theme 以及 Resource,並且 Resource 可以傳入自己的配置初始化。

  • ContextImpl 是 Context 的主要實現類,Activity、Service 和 Application 的 Base Context 都是由它創建的,即 ContextWrapper 代理的就是 ContextImpl 對象本身。

  • ContextImpl 和 ContextThemeWrapper 的主要區別是, ContextThemeWrapper 有 Configuration 對象,Resource 可以根據這個對象來初始化。

  • Service 和 Application 使用同一個 Recource,和 Activity 使用的 Resource 不同。

6.面試官:Activity Context、Service Context、Application Context、Base Context 有什麼區別?

Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 創建的,且創建的都是 ContextImpl 對象,即它們都是 ContextImpl 的代理類 。

  • Service 和 Application 使用同一個 Recource,和 Activity 使用的 Resource 不同。

  • getApplicationContext 返回的就是 Application 對象本身,一般情況下它對應的是應用本身的 Application 對象,但也可能是系統的某個 Application。

7.面試官:爲什麼不推薦使用 BaseContext?

  • 對於 Service 和 Application 而言,不推薦使用 Base Context,是擔心用戶修改了 Base Context 而導致錯誤的發生。

  • 對於 Activity 而言,除了擔心用戶的修改之外,Base Context 和 Activity 本身對於 Reource 以及 Theme 的相關行爲是不同的(如果應用了 Configuration 的話),使用 Base Context 可能出現無法預期的現象。

8.面試官:ContentProvider 裏的 Context 是什麼時候初始化的呢?

ContentProvider 本身不是 Context ,但是它有一個成員變量 mContext ,是通過構造函數傳入的。那麼這個問題就變成了,ContentProvider 什麼時候創建。應用創建 Application 是通過調用 ActivityThread.handleBindApplication 方法,這個方法的相關流程有:

  1. 創建 Application

  2. 初始化 Application 的 Context

  3. 調用 installContentProviders 並傳入剛創建好的 Application 來創建 ContentProvider

  4. 調用 Application.onCreate

得出結論,ContentProvider 的 Context 是在 Applicaiton 創建之後,但是 onCreate 方法調用之前初始化的。

9.面試官:BroadcastReceiver 裏的 Context是哪來的?

廣播接收器,分動態註冊和靜態註冊。

  • 動態註冊很簡單,在調用 Context.registerReceiver 動態註冊 BroadcastReceiver 時,會生成一個 ReceiverDispatcher 會持有這個 Context ,這樣當有廣播分發到它時,調用 onReceiver 方法就可以把 Context 傳遞過去了。當然,這也是爲什麼不用的時候要 unregisterReceiver 取消註冊,不然這個 Context 就泄漏了哦。
  • 靜態註冊時,在分發的時候最終調用的是 ActivityThread.handleReceiver ,這個方法直接通過 ClassLoader 去創建一個 BroadcastReceiver 的對象,而傳遞給 onReceiver 方法的 Context 則是通過 context.getReceiverRestrictedContext() 生成的一個以 Application 爲 mBase 的 ContextWrapper。注意這邊的 Context 不是 Application 。

在這裏插入圖片描述

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