Android全面解析之Context機制

前言

很高興遇見你~ 歡迎閱讀我的文章。

在文章Android全面解析之由淺及深Handler消息機制中討論到,Handler可以:

避免我們自己去手動寫 死循環(huán)和輸入阻塞 來不斷獲取用戶的輸入以及避免線程直接結(jié)束,而是采用事務(wù)驅(qū)動型設(shè)計,使用Handler消息機制,讓AMS可以控制整個程序的運行邏輯。

這是關(guān)于android程序在設(shè)計上更加重要的一部分,不太了解的讀者可以前往閱讀了解一下。而當(dāng)我們知道android程序的程序是通過main方法跑起來的,然后通過handler機制來控制程序的運行,那么四大組件和普通的Java類到底有什么區(qū)別?為什么同樣是Java類,而ActivityThread、Activity等等這些類就顯得那么特殊呢?我們的代碼、寫的布局是通過什么路徑使用系統(tǒng)資源把界面展示在屏幕上的?這一切就涉及到我們今天的主角:Context。

什么是Context

回想一下最初學(xué)習(xí)Android開發(fā)的時候,第一用到context是什么時候?如果你跟我一樣是通過郭霖的《第一行代碼》來入門android,那么一般是Toast。Toast的常規(guī)用法是:

Toast.makeText(this, "我是toast", Toast.LENGTH_SHORT).show()

當(dāng)初也不知道什么是Context,只知道他需要一個context類型,把activity對象傳進去即可。從此context貫穿在我開發(fā)過程的方方面面,但我始終不知道這個context到底有什么用?為什么要這個對象?我們首先來看官方對于Context類的注釋:

/**
 * Interface to global information about an application environment.  This is
 * an abstract class whose implementation is provided by
 * the Android system.  It
 * allows access to application-specific resources and classes, as well as
 * up-calls for application-level operations such as launching activities,
 * broadcasting and receiving intents, etc.
 */
public abstract class Context {...}

關(guān)于應(yīng)用程序環(huán)境的全局信息的接口。 這是一個抽象類,它的實現(xiàn)是由Android系統(tǒng)提供。 它允許訪問特定應(yīng)用的資源和類,以及向上調(diào)用應(yīng)用程序級的操作,如啟動活動,廣播和接收Intent等。

可以看到Context最重要的作用就是獲取全局消息、訪問系統(tǒng)資源、調(diào)用應(yīng)用程序級的操作??赡軐τ谶@些作用沒什么印象,想一下,如果沒有context,我們?nèi)绾巫龅揭韵虏僮鳎?/p>

  • 彈出一個toast
  • 啟動一個activity
  • 獲取程序布局文件、drawable文件等
  • 訪問數(shù)據(jù)庫
    這些平時看似簡單的操作,一旦失去了context將無法執(zhí)行。這些行為都有一個共同點:需要與系統(tǒng)交匯。四大組件為什么配為組件,而我們的寫的就只能叫做一個普通的Java類,正是因為context的這些功能讓四大組件有了不一樣的能力。簡單來說,context是:

應(yīng)用程序和系統(tǒng)之間的橋梁,應(yīng)用程序訪問系統(tǒng)各種資源的接口。

我們一般使用context最多的是兩種情景:直接調(diào)用context的方法和調(diào)用接口時需要context參數(shù)。這些行為都意味著我們需要訪問系統(tǒng)相關(guān)的資源。

那context是從哪里來的?AMS!AMS是系統(tǒng)級進程,擁有訪問系統(tǒng)級操作的權(quán)利,應(yīng)用程序的啟動受AMS的調(diào)控,在程序啟動的過程中,AMS會把一個“憑證”通過跨進程通信給到我們的應(yīng)用程序,我們的程序會把這個“憑證”封裝成context,并提供一系列的接口,這樣我們的程序也就可以很方便地訪問系統(tǒng)資源了。這樣的好處是:

系統(tǒng)可以對應(yīng)用程序級的操作進行調(diào)控,限制各種情景下的權(quán)限,同時也可以防止惡意攻擊。

如Application類的context和Activity的context權(quán)利是不一樣的,生命周期也不一樣。對于想要操作系統(tǒng)攻擊用戶的程序也進行了阻止,沒有獲得允許的Java類沒有任何權(quán)利,而Activity開放給用戶也只有部分有限的權(quán)利。而我們開發(fā)者獲取context的路徑,也只有從activity、application等組件獲取。

因而,什么是Context?Context是應(yīng)用程序與系統(tǒng)之間溝通的橋梁,是應(yīng)用程序訪問系統(tǒng)資源的接口,同時也是系統(tǒng)給應(yīng)用程序的一張“權(quán)限憑證”。有了context,一個Java類才可以被稱之為組件。

Context家族

上一部分我們了解什么是context以及context的重要性,這一部分就來了解一下context在源碼中的子類繼承情況。先看一個圖:


Context家族

最頂層是Context抽象類,他定義了一系列與系統(tǒng)交匯的接口。ContextWrapper繼承自Context,但是并沒有真正實現(xiàn)Context中的接口,而是把接口的實現(xiàn)都托管給ContextImplContextImpl是Context接口的真正實現(xiàn)者,從AMS拿來的“憑證”也是封裝到了ContextImpl中,然后賦值給ContextWrapper,這里運用到了一種模式:裝飾者模式。ApplicationService都繼承自ContextWrapper,那么他們也就擁有Context的接口方法且本身即是context,方便開發(fā)者的使用。Activity比較特殊,因為它是有界面的,所以他需要一個主題:Theme,ContextThemeWrapper在ContextWrapper的基礎(chǔ)上增加與主題相關(guān)的操作。

這樣的設(shè)計有這樣的優(yōu)點:

  • Activity等可以更加方便地使用context,可以把自身當(dāng)成context來使用,遇到需要context的接口直接把自身傳進去即可。
  • 運用裝飾者模式,向外屏蔽ContextImpl的內(nèi)部邏輯,同時當(dāng)需要更改ContextImpl的邏輯實現(xiàn),ContextWrapper的邏輯幾乎不需要更改。
  • 更方便地擴展不同情景下的邏輯。如service和activity,情景不同,需要的接口方法也不同,但是與系統(tǒng)交互的接口是相同的,使用裝飾者模式可以拓展出很多的功能,同時只需要把ContextImpl對象賦值進去即可。

context的分類

前面講到Context的家族體系時,了解到他的最終實現(xiàn)類有:ApplicationActivity、Service,ContextImpl被前三者持有,是Context接口的真正實現(xiàn)。那么這里討論一下這三者有什么不同,和使用時需要注意的問題。

Application

Application是全局Context,整個應(yīng)用程序只有一個,他可以訪問到應(yīng)用程序的包信息等資源信息。獲取Application的方法一般有兩個:

context.getApplicationContext()
activity.getApplication()

通過context和activity都可以獲取到Application,那這兩個方法有什么區(qū)別?沒有區(qū)別。我們可以打印來看一下:

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    Log.d("一只修仙的猿", "application:$application")
    Log.d("一只修仙的猿", "applicationContext:$applicationContext")
}
log.png

可以看到確實是同個對象。但為什么要提供兩個一樣作用的方法?getApplication()方法更加直觀,但是只能在activity中調(diào)用。getApplicationContext()適用范圍更廣,任意一個context對象皆可以調(diào)用此方法。

Application類的Context的特點是生命周期長,在整個應(yīng)用程序運行的期間他都會存在。同時我們可以自定義Application,并在里面做一些全局的初始化操作,或者寫一個靜態(tài)的context供給全局獲取,不需要在方法中傳入context。如:

class MyApplication : Application(){
    // 全局context
    companion object{
        lateinit var context: Context
    }
    override fun onCreate() {
        super.onCreate()
        // 做全局初始化操作
        RetrofitManager.init(this)
        context = this
    }
}

這樣我們就可以在應(yīng)用啟動的時候?qū)σ恍┙M件進行初始化,同時可以通過MyApplication.context來獲取Application對象。

但是!??!請不要把Application當(dāng)成工具類使用。由于Application獲取的便利性,有開發(fā)者會在Application中編寫一些工具方法,全局獲取使用,這樣是不行的。自定義Application的目的是在程序啟動的時候做全局初始化工作,而不能拿來取代工具類,這嚴重違背谷歌設(shè)計Application的原則,也違背Java代碼規(guī)范的單一職責(zé)原則。

四大組件

Activity繼承自ContextThemeWrapper,是一個擁有主題的context對象。Activity常用于與UI有關(guān)的操作,如添加window等。常規(guī)使用可以直接用activity.this。

Service繼承自ContextWrapper,也可以和Activity一樣直接使用service.this來使用context。和activity不同的是,Service沒有界面,所以也不需要主題。

ContextProvider使用的是Application的context,Broadcast使用的是activity的context,這兩點在后面會進行源碼分析。

BaseContext

嗯?baseContext是什么?把這個拿出來單獨講,細心的讀者可能會發(fā)現(xiàn)activity中有一個方法:getBaseContext。這個是ContextWrapper中的mBase對象,也就是ContextImpl,也是context接口的真正邏輯實現(xiàn)。

context的使用問題

使用context最重要的問題之一是注意內(nèi)存泄露。不同的context的生命周期不同,Application是在應(yīng)用存在的期間會一直存在,而Activity是會隨著界面的銷毀而銷毀,如果當(dāng)我們的代碼長時間持有了activity的context,如靜態(tài)引用或者單例類,那么會導(dǎo)致activity無法被釋放。如下面的代碼:

object MyClass {
    lateinit var mContext : Context
    fun showToast(context : Context){
        mContext = context
    }
}

單例類在應(yīng)用持續(xù)的時間都會一直存在,這樣context也就會被一直被持有,activity無法被回收,導(dǎo)致內(nèi)存泄露。

那,我們就都換成Application不就可以了,如下:

object MyClass {
    lateinit var mContext : Context
    fun showToast(context : Context){
        mContext = context.applicationContext
    }
}

答案是:不可以。什么時候可以使用Application?不涉及UI以及啟動Activity操作。Activity的context是擁有主題屬性的,如果使用Application來操作UI,那么會丟失自定義的主題,采用系統(tǒng)默認的主題。同時,有些UI操作只有Activity可以執(zhí)行,如彈出dialog,這涉及到window的token問題,我在這篇文章token驗證進行了詳細的解答,有興趣的讀者可以去閱讀一下。這也是官方對于context不同權(quán)限的設(shè)計,沒有界面的context,就不應(yīng)該有操作界面的權(quán)利。使用Application啟動的Activity必須指定task以及標記為singleTask,因為Application是沒有任務(wù)棧的,需要重新開一個新的任務(wù)棧。因此,我們需要根據(jù)不同context的不同職責(zé)來執(zhí)行不同的任務(wù)。

Context的創(chuàng)建過程

經(jīng)過上面的討論,讀者對于context在心中有了一定的理解。但始終覺得少點什么:activity是什么時候被創(chuàng)建的,他的contextImpl是如何被賦值的?Application呢?為什么說ContextProvider的context是Application,Broadcast的context是Activity?contextImpl又是如何被創(chuàng)建的?解決這些疑惑,就必須閱讀源碼了。閱讀源碼的好處非常多,上面我的講述,都是基于我閱讀源碼之后的理解,而“一千個觀眾有一千個哈姆雷特”,閱讀源碼可以形成自己對整個機制自己的思考和理解,同時可以讓自己對context那些知識真正落實到代碼上,增強自己對知識的自信心。當(dāng)別人和你意見不同的時候,你可以拍拍胸脯說:我看過源碼,這個地方就是這樣。是不是非常自信且傲嬌?

然而閱讀源碼不是越多越好,而是把握整體的流程之后閱讀關(guān)鍵源碼,不要深入源碼堆中無法自拔。例如我覺得activity的contextImpl是在Activity創(chuàng)建的過程中被賦值的,那么我就會去找activity的啟動流程源碼,然后只看和context有關(guān)的部分。提高效率的同時,還可以切中我們學(xué)習(xí)的點。下面的源碼閱讀我們給出整體流程,然后重點理解關(guān)鍵代碼,其他的源碼讀者可自行下載源碼去跟蹤閱讀一下。

Application

Application應(yīng)用級別的context,是在應(yīng)用被創(chuàng)建的時候被創(chuàng)建的,是第一個被創(chuàng)建的context,也是最后一個被銷毀的context。因而追蹤Application的創(chuàng)建需要從應(yīng)用程序的啟動流程看起。應(yīng)用啟動的源碼流程如下(簡化版):


Application

應(yīng)用程序從ActivityThread的main方法開始執(zhí)行,從Handler消息機制中我們知道m(xù)ain方法主要是開啟線程的Looper以及handler,然后由AMS向主線程發(fā)送message控制應(yīng)用的啟動過程。因而我們可以把目標鎖定在圖中的最后一個方法:handleBindApplication,Application最有可能在這里被創(chuàng)建:

  • ActivityThread.class (api29)
private void handleBindApplication(AppBindData data) {
    ...
    // 創(chuàng)建LoadedApk對象
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    ...
    Application app;
    ...
    try {
        // 創(chuàng)建Application
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        ...
    }
    try {
        ...
        // 回調(diào)Application的onCreate方法
        mInstrumentation.callApplicationOnCreate(app);
    }
    ...
}

handleBindApplication的參數(shù)AppBindData是AMS給應(yīng)用程序的啟動信息,其中就包含了“權(quán)限憑證”——ApplicationInfo等。LoadedApk就是通過這些對象來創(chuàng)建獲取對系統(tǒng)資源的訪問權(quán)限,然后通過LoadApk來創(chuàng)建ContextImpl以及Application。

這里我們只關(guān)注和context創(chuàng)建有關(guān)的邏輯,前面啟動程序的源碼以及AMS如何處理,這里就不講了,讀者有興趣可以讀ContextProvider啟動流程這篇文章,其中對ContextProvider的啟動過程就有對上述源碼進行追蹤詳解。

那么接下來我們繼續(xù)關(guān)注Application是如何創(chuàng)建的:

-LoadeApk.class(api29)

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    // 如果application已經(jīng)存在則直接返回
    if (mApplication != null) {
        return mApplication;
    }
    ...
    Application app = null;
    String appClass = mApplicationInfo.className;
    ...
    try {
        java.lang.ClassLoader cl = getClassLoader();
       ...
        // 創(chuàng)建ContextImpl
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // 利用類加載器加載我們在AndroidMenifest指定的Application類
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        // 把Application的引用給comtextImpl,這樣contextImpl也可以很方便地訪問Application
        appContext.setOuterContext(app);
    } 
    ...
    mActivityThread.mAllApplications.add(app);
    // 把app設(shè)置為mApplication,當(dāng)我們調(diào)用context.getApplicationContext就是獲取這個對象
    mApplication = app;

    if (instrumentation != null) {
        try {
            // 回調(diào)Application的onCreate方法
            instrumentation.callApplicationOnCreate(app);
        } 
        ...
    }
    ...
    return app;
}

代碼的邏輯也不復(fù)雜,首先判斷LoadedApk對象中的mApplication是否存在,否則創(chuàng)建ContextImpl,再利用類加載器和contextImpl創(chuàng)建Application,最后把Application對象賦值給LoadedApk的mApplication,再回調(diào)Application的onCreate方法。我們先來看一下contextImpl是如何創(chuàng)建的:

  • ContextImpl.class(api29)
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
        String opPackageName) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
            null, opPackageName);
    context.setResources(packageInfo.getResources());
    return context;
}

這里直接new了一個ContextImpl,同時給ContextImpl賦值訪問系統(tǒng)資源相關(guān)的“權(quán)限”對象——ActivityThread,LoadedApk等。讓我們再回到Application的創(chuàng)建過程。我們可以猜測,在newApplication包含的邏輯肯定有:利用反射創(chuàng)建Application,再把contextImpl賦值給Application。原因是每個人自定義的Application類不同,需要利用反射來創(chuàng)建對象,其次Application中的mBase屬性是對ContextImpl的引用??丛创a:

  • Instrumentation.class(api29)
public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}
  • Application.class(api29)
final void attach(Context context) {
    attachBaseContext(context);
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
  • ContextWrapper.class(api29)
Context mBase;    
protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}    

結(jié)果非常符合我們的猜測,先創(chuàng)建Application對象,再把ContextImpl通過Application的attach方法賦值給Application。然后Application的attach方法調(diào)用了ContextWrapper的attachBaseContext方法,因為Application也是繼承自ContextWrapper。這樣,就把ContextImpl賦值給Application的mBase屬性了。

再回到前面的邏輯,創(chuàng)建了Application之后需要回調(diào)onCreate方法:

  • Instrumentation.class(api29)
public void callApplicationOnCreate(Application app) {
    app.onCreate();
}

簡單粗暴,直接回調(diào)。到這里,Application的創(chuàng)建以及context的創(chuàng)建流程就走完了。但是需要注意的是,全局初始化需要在onCreate中進行,而不要在Application的構(gòu)造器中執(zhí)行。從代碼中我們可以看到ContextImpl是在Application被創(chuàng)建之后再賦值的。

Activity

Activity的context也是在Activity創(chuàng)建的過程中被創(chuàng)建的,這個就涉及到Activity的啟動流程,這里涉及到三個流程:應(yīng)用程序請求AMS,AMS處理請求,應(yīng)用程序響應(yīng)Activity創(chuàng)建事務(wù):


Activity的創(chuàng)建流程

依然,我們專注于Activity的創(chuàng)建流程,其他的讀者可閱讀Activity啟動流程這篇文章了解。和Application一樣,Activity的創(chuàng)建時由AMS來控制的,AMS向應(yīng)用程序進程發(fā)送消息來執(zhí)行具體的啟動邏輯。最后會執(zhí)行到handleLaunchActivity這個方法:

  • ActivityThread.class(api29)
public Activity handleLaunchActivity(ActivityClientRecord r,
        PendingTransactionActions pendingActions, Intent customIntent) {
    ...
    final Activity a = performLaunchActivity(r, customIntent);
    ...
   return a;
}

最終的就是中間這句代碼,進入看源碼:

  • ActivityThread.class(api29)
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    // 創(chuàng)建Activity的ContextImpl
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        // 利用類加載創(chuàng)建activity實例
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
    }
    try {
        // 創(chuàng)建Application
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        ...
        if (activity != null) {
            ...
            // 把activity設(shè)置給context,這樣context也可以訪問到activity了
            appContext.setOuterContext(activity);
            // 調(diào)用activity的attach方法把contextImpl設(shè)置給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,
                            r.assistToken);

            int theme = r.activityInfo.getThemeResource();
            if (theme != 0) {
                // 設(shè)置主題
                activity.setTheme(theme);
            }
            ...
            // 回調(diào)onCreate方法
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            ...
        }
        ...
    }
    ...
    return activity;
}

代碼的邏輯不是很復(fù)雜,首先創(chuàng)建Activity的ContextImpl,利用類加載創(chuàng)建activity實例,然后再通過LoadedApk創(chuàng)建Application,這個方法在前面我們講過,如果Application已經(jīng)創(chuàng)建會直接返回已經(jīng)創(chuàng)建的對象。然后把activity設(shè)置給context,這樣context也可以訪問到activity了。這里要注意,前面講到使用Activity的context會造成內(nèi)存泄露,那么可不可以用Activity的contextImpl對象呢?答案是不可以,因為ContextImpl也會持有Activity的引用,需要特別注意一下。隨后再調(diào)用activity的attach方法把contextImpl設(shè)置給activity。后面是設(shè)置主題和回調(diào)onCreate方法,我們就不深入了,主要看看attach方法:

  • Activity.class(api29)
final void attach(Context context,...) {
    attachBaseContext(context);
    ...   
}

這里省略了大量的代碼,只保留關(guān)鍵一句:attachBaseContext,是不是很熟悉?調(diào)用ContextWrapper的方法來給mBase屬性賦值,和前面Application是一樣的,就不再贅述。

Service

依然只關(guān)注關(guān)鍵代碼流程,先看Service的啟動流程圖:


Service的啟動流程圖

Service的創(chuàng)建過程也是受AMS的控制,同樣我們看到創(chuàng)建Service的那一步,最終會調(diào)用到handleCreateService這個方法:

private void handleCreateService(CreateServiceData data) {
    ...
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
    } 
    ...
    try {
        ...
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service);

        Application app = packageInfo.makeApplication(false, mInstrumentation);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        service.onCreate();
        mServices.put(data.token, service);
        ...
    } 
    ...
}

Service的邏輯就相對簡單了,同樣創(chuàng)建service實例,再創(chuàng)建contextImpl,最后把contextImpl通過Service的attach方法賦值給mBase屬性,最后回調(diào)Service的onCreate方法。過程和上面的很像,這里就不再深入講了,感興趣的讀者可自行去閱讀源碼,也可以閱讀Android中Service的啟動與綁定過程詳解(基于api29)這篇文章了解Service的詳細內(nèi)容。

Broadcast

Broadcast和上面的組件不同,他并不是繼承自Context,所以他的Context是需要通過上述三者來給予。我們一般使用廣播的context是在接受器中,如:

class MyClass :BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        TODO("use context")
    }
}

那么onReceive的context對象是從哪里來的呢?同樣我們先看廣播接收器的注冊流程:


廣播接收器的注冊流程

同樣,詳細的廣播相關(guān)工作流程可以閱讀Android廣播Broadcast的注冊與廣播源碼過程詳解(基于api29)這篇文章了解。因為在創(chuàng)建Receiver的時候并沒有傳入context,所以我們需要追蹤他的注冊流程,看看在哪里獲取了context。我們先看到ContextImpl的registerReceiver方法:

  • ContextImpl.class(api29)
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
        String broadcastPermission, Handler scheduler) {
    // 注意參數(shù)
    return registerReceiverInternal(receiver, getUserId(),
            filter, broadcastPermission, scheduler, getOuterContext(), 0);
}

registerReceiver方法最終會來到這個重載方法,我們可以注意到,這里有個getOuterContext,這個是什么?還記得Activity的context創(chuàng)建過程嗎?這個方法獲取的就是activity本身。我們繼續(xù)看下去:

  • ContextImpl.class(api29)
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
        IntentFilter filter, String broadcastPermission,
        Handler scheduler, Context context, int flags) {
    IIntentReceiver rd = null;
    if (receiver != null) {
        if (mPackageInfo != null && context != null) {
            ...
            rd = mPackageInfo.getReceiverDispatcher(
                receiver, context, scheduler,
                mMainThread.getInstrumentation(), true);
        }
        ...
    }
    ...
}

這里利用context創(chuàng)建了ReceiverDispatcher,我們繼續(xù)深入看:

  • LoadedApk.class(api29)
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
        Context context, Handler handler,
        Instrumentation instrumentation, boolean registered) {
    synchronized (mReceivers) {
        LoadedApk.ReceiverDispatcher rd = null;
        ...
        if (rd == null) {
            rd = new ReceiverDispatcher(r, context, handler,
                    instrumentation, registered);
            ...
        }
        ...
    }
}
  • ReceiverDispatcher.class(api29)
ReceiverDispatcher(..., Context context,...) {
    ...
    mContext = context;
    ...
}

這里確實把receiver和context創(chuàng)建了ReceiverDispatcher,嗯?怎么沒有給Receiver?其實這涉及到廣播的內(nèi)部設(shè)計結(jié)構(gòu)。Receiver是沒有跨進程通信能力的,而廣播需要AMS的調(diào)控,所以必須有一個可以跟AMS溝通的對象,這個對象是InnerReceiver,而ReceiverDispatcher就是負責(zé)維護他們兩個的聯(lián)系,如下圖:


ReceiverDispatcher

而onReceive方法也是由ReceiverDispatcher回調(diào)的,最后我們再看到回調(diào)onReceive的那部分代碼:

  • ReceiverDispatcher.java/Args.class;
public final Runnable getRunnable() {
    return () -> {
        ...;
        try {
            ...;
            // 可以看到這里回調(diào)了receiver的方法,這樣整個接收廣播的流程就走完了。
            receiver.onReceive(mContext, intent);
        }
    }
}

Args是Receiver的內(nèi)部類,mContext就是在創(chuàng)建ReceiverDispatcher時傳入的對象,到這里我們就知道這個對象確實是Activity了。

但是,,不一定每個都是Activity。在源碼中我們知道是通過getOuterContext來獲取context,如果是通過別的context注冊廣播,那么對應(yīng)的對象也就不同了,只是我們一般都是在Activity中創(chuàng)建廣播,所以這個context一般是activity對象。

ContentProvider

ContextProvider我們用的就比較少了,內(nèi)容提供器主要是用于應(yīng)用間內(nèi)容共享的。雖然ContentProvider是由系統(tǒng)創(chuàng)建的,但是他本身并不屬于Context家族體系內(nèi),所以他的context也是從其他獲取的。老樣子,先看ContentProvider的創(chuàng)建流程:


ContentProvider的創(chuàng)建流程

咦?這不是Application創(chuàng)建的流程圖嗎?是的,ContentProvider是伴隨著應(yīng)用啟動被創(chuàng)建的,來看一張更加詳細的流程圖:


ContentProvider.png

我們把目光聚集到ContentProvider的創(chuàng)建上,也就是installContentProviders方法。同樣,詳細的ContentProvider工作流程可以訪問Android中ContentProvider的啟動與請求源碼流程詳解(基于api29)這篇文章。installContentProviders是在handleBindApplication中被調(diào)用的,我們看到調(diào)用這個方法的地方:

private void handleBindApplication(AppBindData data) {
    try {
        // 創(chuàng)建Application
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        ...
        if (!data.restrictedBackupMode) {
            if (!ArrayUtils.isEmpty(data.providers)) {
                // 安裝ContentProvider
                installContentProviders(app, data.providers);
        }
    }    
}

可以看到這里傳入了application對象,我們繼續(xù)看下去:

private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<ContentProviderHolder> results = new ArrayList<>();
    for (ProviderInfo cpi : providers) {
        ...
        ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        ...
    }
...
}

這里調(diào)用了installProvider,繼續(xù)往下看:

private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        ...
        // 這里c最終是由context構(gòu)造的
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        if (context.getPackageName().equals(ai.packageName)) {
            c = context;
        }
        ...
        try {
            // 創(chuàng)建ContentProvider
            final java.lang.ClassLoader cl = c.getClassLoader();
            LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
            ...
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);
            provider = localProvider.getIContentProvider();
            ...
            // 把context設(shè)置給ContentProvider
            localProvider.attachInfo(c, info);
        } 
        ...
    } 
    ...
}

這里最重要的一行代碼是localProvider.attachInfo(c, info);,在這里把context設(shè)置給了ContentProvider,我們再深入一點看看:

  • ContentProvider.class(api29)
public void attachInfo(Context context, ProviderInfo info) {
    attachInfo(context, info, false);
}
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    ...
    if (mContext == null) {
        mContext = context;
        ...
    }
    ...
}

這里確實把context賦值給了ContentProvider的內(nèi)部變量mContext,這樣ContentProvider就可以使用Context了。而這個context正是一開始傳進來的Application。

從源碼設(shè)計角度看Context

到這里關(guān)于Context的知識也講得差不多了。研究Framework層知識,不能只停留在他是什么,有什么作用即可。Framework層他是一個整體,構(gòu)成了android這個龐大的體系,還需要看Context,在其中扮演著什么樣的角色,解決了什么樣的問題。在window機制中我講到window的存在是為了解決屏幕上view的顯示邏輯與觸摸反饋問題,在Hanlder機制中我寫到整個android程序都是基于Handler機制來驅(qū)動執(zhí)行的,而Context呢?

Android系統(tǒng)是一個完整的生態(tài),他搭建了一個環(huán)境,讓各種程序可以運行在上面。而任何一個程序,想要運行在這個環(huán)境上,必須得到系統(tǒng)的允許,也就是軟件安裝。安卓與電腦不同的是,他不是任意一個程序就可以直接訪問到系統(tǒng)的資源。我們在window上可以寫一個java程序,然后直接開啟一個文件流就可以讀取和修改文件了。而Android沒這么簡單,他任意一個程序的運行都必須經(jīng)過系統(tǒng)的調(diào)控。也就是,即時程序獲得允許(安裝在手機上了),程序本身要運行,還得是系統(tǒng)來控制程序運行,程序無法自發(fā)地執(zhí)行在Android環(huán)境中。我們通過源碼可以知道程序的main方法,僅僅只是開啟了線程的Looper循環(huán),而后續(xù)的一切,都必須等待AMS來控制。

那應(yīng)用程序自己硬要執(zhí)行可不可以?可以,但是沒卵用。想要獲得系統(tǒng)資源,如啟動四大組件、讀取布局文件、讀寫數(shù)據(jù)庫、調(diào)用系統(tǒng)柜攝像頭等等,都必須要通過Context,而context必須要通過AMS來獲取。這就區(qū)分了一個程序是一個普通的Java程序,還是android程序。

Context承受的兩大重要職責(zé)是:身份權(quán)限程序訪問系統(tǒng)的接口。一個Java類,如果沒有context那么就是一個普通的Java類,而當(dāng)他獲得context那么他就可以稱之為一個組件了,因為它獲得了訪問系統(tǒng)的權(quán)限,他不再是一個普通的身份,是屬于android“公民”了。而“公民”并不是無法無天,系統(tǒng)也可以通過context來封裝以及限制程序的權(quán)限。要想彈出一個通知,你必須通過這個api,用戶關(guān)閉你的通知權(quán)限,你就別想通過第二條路來彈出通知了。同時 程序也無需知道底層到底是如何實現(xiàn),只管調(diào)用api即可。四大組件為何稱為四大組件,因為他們生來就有了context,特別是activity和service,包括Application。而我們寫的一切程序,都必須間接或者直接從其中獲取context。

總而言之,context就是負責(zé)區(qū)分android內(nèi)外程序的一個機制,限制程序訪問系統(tǒng)資源的權(quán)限

總結(jié)

文章從什么是context開始介紹,再針對context的不同子類進行解析,最后結(jié)合源碼深入地講解了context的創(chuàng)建過程。最后再談了我對context的設(shè)計理解。

關(guān)于context想說的就已經(jīng)說完了。雖然這些內(nèi)容日常很少用得到,但是非常有助于我們對Android整個系統(tǒng)框架的理解。而當(dāng)我們對系統(tǒng)有更加深入的理解后,寫出來的程序也就會更加健壯。

希望文章對你有幫助。

全文到此,原創(chuàng)不易,覺得有幫助可以點贊收藏評論轉(zhuǎn)發(fā)。
筆者能力有限,有任何想法歡迎評論區(qū)交流指正。
如需轉(zhuǎn)載請私信交流。

另外歡迎光臨筆者的個人博客:傳送門
————————————————
版權(quán)聲明:本文為CSDN博主「一只修仙的猿」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_43766753/article/details/109017196

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 夜鶯2517閱讀 128,103評論 1 9
  • 版本:ios 1.2.1 亮點: 1.app角標可以實時更新天氣溫度或選擇空氣質(zhì)量,建議處女座就不要選了,不然老想...
    我就是沉沉閱讀 7,377評論 1 6
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,822評論 28 54
  • 兔子雖然是枚小碩 但學(xué)校的碩士四人寢不夠 就被分到了博士樓里 兩人一間 在學(xué)校的最西邊 靠山 兔子的室友身體不好 ...
    待業(yè)的兔子閱讀 2,765評論 2 9

友情鏈接更多精彩內(nèi)容