主頁 > 區塊鏈 > Android全面決議之Context機制

Android全面決議之Context機制

2020-10-12 12:02:06 區塊鏈

前言

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

在文章Android全面決議之由淺及深Handler訊息機制中討論到,Handler可以:

避免我們自己去手動寫 死回圈和輸入阻塞 來不斷獲取用戶的輸入以及避免執行緒直接結束,而是采用事務驅動型設計,使用Handler訊息機制,讓AMS可以控制整個程式的運行邏輯,

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

什么是Context

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

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

當初也不知道什么是Context,只知道他需要一個context型別,把activity物件傳進去即可,從此context貫穿在我開發程序的方方面面,但我始終不知道這個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 {...}

關于應用程式環境的全域資訊的介面, 這是一個抽象類,它的實作是由Android系統提供, 它允許訪問特定應用的資源和類,以及向上呼叫應用程式級的操作,如啟動活動,廣播和接收Intent等

可以看到Context最重要的作用就是獲取全域訊息、訪問系統資源、呼叫應用程式級的操作,可能對于這些作用沒什么印象,想一下,如果沒有context,我們如何做到以下操作:

  • 彈出一個toast
  • 啟動一個activity
  • 獲取程式布局檔案、drawable檔案等
  • 訪問資料庫

這些平時看似簡單的操作,一旦失去了context將無法執行,這些行為都有一個共同點:需要與系統交匯,四大組件為什么配為組件,而我們的寫的就只能叫做一個普通的Java類,正是因為context的這些功能讓四大組件有了不一樣的能力,簡單來說,context是:

應用程式和系統之間的橋梁,應用程式訪問系統各種資源的介面,

我們一般使用context最多的是兩種情景:直接呼叫context的方法和呼叫介面時需要context引數,這些行為都意味著我們需要訪問系統相關的資源,

那context是從哪里來的?AMS!AMS是系統級行程,擁有訪問系統級操作的權利,應用程式的啟動受AMS的調控,在程式啟動的程序中,AMS會把一個“憑證”通過跨行程通信給到我們的應用程式,我們的程式會把這個“憑證”封裝成context,并提供一系列的介面,這樣我們的程式也就可以很方便地訪問系統資源了,這樣的好處是:

系統可以對應用程式級的操作進行調控,限制各種情景下的權限,同時也可以防止惡意攻擊,

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

因而,什么是Context?Context是應用程式與系統之間溝通的橋梁,是應用程式訪問系統資源的介面,同時也是系統給應用程式的一張“權限憑證”,有了context,一個Java類才可以被稱之為組件,

Context家族

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

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

這樣的設計有這樣的優點:

  • Activity等可以更加方便地使用context,可以把自身當成context來使用,遇到需要context的介面直接把自身傳進去即可,
  • 運用裝飾者模式,向外屏蔽ContextImpl的內部邏輯,同時當需要更改ContextImpl的邏輯實作,ContextWrapper的邏輯幾乎不需要更改,
  • 更方便地擴展不同情景下的邏輯,如service和activity,情景不同,需要的介面方法也不同,但是與系統互動的介面是相同的,使用裝飾者模式可以拓展出很多的功能,同時只需要把ContextImpl物件賦值進去即可,

context的分類

前面講到Context的家族體系時,了解到他的最終實作類有:Application、Activity、Service,ContextImpl被前三者持有,是Context介面的真正實作,那么這里討論一下這三者有什么不同,和使用時需要注意的問題,

Application

Application是全域Context,整個應用程式只有一個,他可以訪問到應用程式的包資訊等資源資訊,獲取Application的方法一般有兩個:

context.getApplicationContext()
activity.getApplication()

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

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

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

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

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

這樣我們就可以在應用啟動的時候對一些組件進行初始化,同時可以通過MyApplication.context來獲取Application物件,

但是!!!請不要把Application當成工具類使用,由于Application獲取的便利性,有開發者會在Application中撰寫一些工具方法,全域獲取使用,這樣是不行的,自定義Application的目的是在程式啟動的時候做全域初始化作業,而不能拿來取代工具類,這嚴重違背谷歌設計Application的原則,也違背Java代碼規范的單一職責原則,

四大組件

Activity繼承自ContextThemeWrapper,是一個擁有主題的context物件,Activity常用于與UI有關的操作,如添加window等,常規使用可以直接用activity.this

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

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

BaseContext

嗯?baseContext是什么?把這個拿出來單獨講,細心的讀者可能會發現activity中有一個方法:getBaseContext,這個是ContextWrapper中的mBase物件,也就是ContextImpl,也是context介面的真正邏輯實作,

context的使用問題

使用context最重要的問題之一是注意記憶體泄露,不同的context的生命周期不同,Application是在應用存在的期間會一直存在,而Activity是會隨著界面的銷毀而銷毀,如果當我們的代碼長時間持有了activity的context,如靜態參考或者單例類,那么會導致activity無法被釋放,如下面的代碼:

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

單例類在應用持續的時間都會一直存在,這樣context也就會被一直被持有,activity無法被回收,導致記憶體泄露,

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

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

答案是:不可以,什么時候可以使用Application?**不涉及UI以及啟動Activity操作,**Activity的context是擁有主題屬性的,如果使用Application來操作UI,那么會丟失自定義的主題,采用系統默認的主題,同時,有些UI操作只有Activity可以執行,如彈出dialog,這涉及到window的token問題,我將會在另一篇文章詳細講解,同時也是官方對于context不同權限的設計,沒有界面的context,就不應該有操作界面的權利,使用Application啟動的Activity必須指定task以及標記為singleTask,因為Application是沒有任務堆疊的,需要重新開一個新的任務堆疊,因此,我們需要根據不同context的不同職責來執行不同的任務

Context的創建程序

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

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

Application

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

應用程式從ActivityThread的main方法開始執行,從Handler訊息機制中我們知道main方法主要是開啟執行緒的Looper以及handler,然后由AMS向主執行緒發送message控制應用的啟動程序,因而我們可以把目標鎖定在圖中的最后一個方法:handleBindApplication,Application最有可能在這里被創建:

ActivityThread.class (api29)

private void handleBindApplication(AppBindData data) {
    ...
	// 創建LoadedApk物件
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    ...
    Application app;
    ...
    try {
		// 創建Application
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        ...
    }
    try {
        ...
		// 回呼Application的onCreate方法
        mInstrumentation.callApplicationOnCreate(app);
    }
    ...
}

handleBindApplication的引數AppBindData是AMS給應用程式的啟動資訊,其中就包含了“權限憑證”——ApplicationInfo等,LoadedApk就是通過這些物件來創建獲取對系統資源的訪問權限,然后通過LoadApk來創建ContextImpl以及Application,

這里我們只關注和context創建有關的邏輯,前面啟動程式的原始碼以及AMS如何處理,這里就不講了,讀者有興趣可以讀ContextProvider啟動流程這篇文章,其中對ContextProvider的啟動程序就有對上述原始碼進行追蹤詳解,

那么接下來我們繼續關注Application是如何創建的:

LoadeApk.class(api29)
public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    // 如果application已經存在則直接回傳
    if (mApplication != null) {
        return mApplication;
    }
	...
    Application app = null;
    String appClass = mApplicationInfo.className;
    ...
    try {
        java.lang.ClassLoader cl = getClassLoader();
       ...
		// 創建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設定為mApplication,當我們呼叫context.getApplicationContext就是獲取這個物件
    mApplication = app;

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

代碼的邏輯也不復雜,首先判斷LoadedApk物件中的mApplication是否存在,否則創建ContextImpl,再利用類加載器和contextImpl創建Application,最后把Application物件賦值給LoadedApk的mApplication,再回呼Application的onCreate方法,我們先來看一下contextImpl是如何創建的:

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賦值訪問系統資源相關的“權限”物件——ActivityThread,LoadedApk等,讓我們再回到Application的創建程序,我們可以猜測,在newApplication包含的邏輯肯定有:利用反射創建Application,再把contextImpl賦值給Application,原因是每個人自定義的Application類不同,需要利用反射來創建物件,其次Application中的mBase屬性是對ContextImpl的參考,看原始碼:

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;
}    

結果非常符合我們的猜測,先創建Application物件,再把ContextImpl通過Application的attach方法賦值給Application,然后Application的attach方法呼叫了ContextWrapper的attachBaseContext方法,因為Application也是繼承自ContextWrapper,這樣,就把ContextImpl賦值給Application的mBase屬性了,

再回到前面的邏輯,創建了Application之后需要回呼onCreate方法:

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

簡單粗暴,直接回呼,到這里,Application的創建以及context的創建流程就走完了,但是需要注意的是,全域初始化需要在onCreate中進行,而不要在Application的構造器中執行,從代碼中我們可以看到ContextImpl是在Application被創建之后再賦值的,

Activity

Activity的context也是在Activity創建的程序中被創建的,這個就涉及到Activity的啟動流程,這里涉及到三個流程:應用程式請求AMS,AMS處理請求,應用程式回應Activity創建事務:

依然,我們專注于Activity的創建流程,其他的讀者可閱讀Activity啟動流程這篇文章了解,和Application一樣,Activity的創建時由AMS來控制的,AMS向應用程式行程發送訊息來執行具體的啟動邏輯,最后會執行到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) {
    ...
	// 創建Activity的ContextImpl
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        // 利用類加載創建activity實體
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
    }
    try {
		// 創建Application
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
		...
        if (activity != null) {
            ...
			// 把activity設定給context,這樣context也可以訪問到activity了
            appContext.setOuterContext(activity);
            // 呼叫activity的attach方法把contextImpl設定給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) {
                // 設定主題
                activity.setTheme(theme);
            }
            ...
			// 回呼onCreate方法
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            ...
        }
        ...
    }
	...
    return activity;
}

代碼的邏輯不是很復雜,首先創建Activity的ContextImpl,利用類加載創建activity實體,然后再通過LoadedApk創建Application,這個方法在前面我們講過,如果Application已經創建會直接回傳已經創建的物件,然后把activity設定給context,這樣context也可以訪問到activity了,這里要注意,前面講到使用Activity的context會造成記憶體泄露,那么可不可以用Activity的contextImpl物件呢?答案是不可以,因為ContextImpl也會持有Activity的參考,需要特別注意一下,隨后再呼叫activity的attach方法把contextImpl設定給activity,后面是設定主題和回呼onCreate方法,我們就不深入了,主要看看attach方法:

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

這里省略了大量的代碼,只保留關鍵一句:attachBaseContext,是不是很熟悉?呼叫ContextWrapper的方法來給mBase屬性賦值,和前面Application是一樣的,就不再贅述,

Service

依然只關注關鍵代碼流程,先看Service的啟動流程圖:

Service的創建程序也是受AMS的控制,同樣我們看到創建Service的那一步,最侄訓呼叫到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的邏輯就相對簡單了,同樣創建service實體,再創建contextImpl,最后把contextImpl通過Service的attach方法賦值給mBase屬性,最后回呼Service的onCreate方法,程序和上面的很像,這里就不再深入講了,感興趣的讀者可自行去閱讀原始碼,也可以閱讀Android中Service的啟動與系結程序詳解(基于api29)這篇文章了解Service的詳細內容,

Broadcast

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

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

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

Broadcast注冊原始碼流程.png

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

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

registerReceiver方法最侄訓來到這個多載方法,我們可以注意到,這里有個getOuterContext,這個是什么?還記得Activity的context創建程序嗎?這個方法獲取的就是activity本身,我們繼續看下去:

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創建了ReceiverDispatcher,我們繼續深入看:

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創建了ReceiverDispatcher,嗯?怎么沒有給Receiver?其實這涉及到廣播的內部設計結構,Receiver是沒有跨行程通信能力的,而廣播需要AMS的調控,所以必須有一個可以跟AMS溝通的物件,這個物件是InnerReceiver,而ReceiverDispatcher就是負責維護他們兩個的聯系,如下圖:

而onReceive方法也是由ReceiverDispatcher回呼的,最后我們再看到回呼onReceive的那部分代碼:

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

Args是Receiver的內部類,mContext就是在創建ReceiverDispatcher時傳入的物件,到這里我們就知道這個物件確實是Activity了,

但是,,不一定每個都是Activity,在原始碼中我們知道是通過getOuterContext來獲取context,如果是通過別的context注冊廣播,那么對應的物件也就不同了,只是我們一般都是在Activity中創建廣播,所以這個context一般是activity物件,

ContentProvider

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

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

我們把目光聚集到ContentProvider的創建上,也就是installContentProviders方法,同樣,詳細的ContentProvider作業流程可以訪問Android中ContentProvider的啟動與請求原始碼流程詳解(基于api29)這篇文章,installContentProviders是在handleBindApplication中被呼叫的,我們看到呼叫這個方法的地方:

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

可以看到這里傳入了application物件,我們繼續看下去:

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*/);
        ...
    }
...
}

這里呼叫了installProvider,繼續往下看:

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構造的
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        if (context.getPackageName().equals(ai.packageName)) {
            c = context;
        }
        ...
        try {
            // 創建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設定給ContentProvider
            localProvider.attachInfo(c, info);
        } 
        ...
    } 
    ...
}

這里最重要的一行代碼是localProvider.attachInfo(c, info);,在這里把context設定給了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的內部變數mContext,這樣ContentProvider就可以使用Context了,而這個context正是一開始傳進來的Application,

從原始碼設計角度看Context

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

Android系統是一個完整的生態,他搭建了一個環境,讓各種程式可以運行在上面,而任何一個程式,想要運行在這個環境上,必須得到系統的允許,也就是軟體安裝,安卓與電腦不同的是,他不是任意一個程式就可以直接訪問到系統的資源,我們在window上可以寫一個java程式,然后直接開啟一個檔案流就可以讀取和修改檔案了,而Android沒這么簡單,他任意一個程式的運行都必須經過系統的調控,也就是,即時程式獲得允許(安裝在手機上了),程式本身要運行,還得是系統來控制程式運行,程式無法自發地執行在Android環境中,我們通過原始碼可以知道程式的main方法,僅僅只是開啟了執行緒的Looper回圈,而后續的一切,都必須等待AMS來控制,

那應用程式自己硬要執行可不可以?可以,但是沒卵用,想要獲得系統資源,如啟動四大組件、讀取布局檔案、讀寫資料庫、呼叫系統柜攝像頭等等,都必須要通過Context,而context必須要通過AMS來獲取,這就區分了一個程式是一個普通的Java程式,還是android程式,

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

總而言之,context就是負責區分android內外程式的一個機制,限制程式訪問系統資源的權限,

總結

文章從什么是context開始介紹,再針對context的不同子類進行決議,最后結合原始碼深入地講解了context的創建程序,最后再談了我對context的設計理解,

關于context想說的就已經說完了,雖然這些內容日常很少用得到,但是非常有助于我們對Android整個系統框架的理解,而當我們對系統有更加深入的理解后,寫出來的程式也就會更加健壯,

希望文章對你有幫助,

全文到此,原創不易,覺得有幫助可以點贊收藏評論轉發,
筆者能力有限,有任何想法歡迎評論區交流指正,
如需轉載請私信交流,

另外歡迎光臨筆者的個人博客:傳送門

轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/168929.html

標籤:區塊鏈

上一篇:2020/10/02 rating賽

下一篇:微信小游戲中three.js離屏畫布

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • JAVA使用 web3j 進行token轉賬

    最近新學習了下區塊鏈這方面的知識,所學不多,給大家分享下。 # 1. 關于web3j web3j是一個高度模塊化,反應性,型別安全的Java和Android庫,用于與智能合約配合并與以太坊網路上的客戶端(節點)集成。 # 2. 準備作業 jdk版本1.8 引入maven <dependency> < ......

    uj5u.com 2020-09-10 03:03:06 more
  • 以太坊智能合約開發框架Truffle

    前言 部署智能合約有多種方式,命令列的瀏覽器的渠道都有,但往往跟我們程式員的風格不太相符,因為我們習慣了在IDE里寫了代碼然后打包運行看效果。 雖然現在IDE中已經存在了Solidity插件,可以撰寫智能合約,但是部署智能合約卻要另走他路,沒辦法進行一個快捷的部署與測驗。 如果團隊管理的區塊節點多、 ......

    uj5u.com 2020-09-10 03:03:12 more
  • 谷歌二次驗證碼成為區塊鏈專用安全碼,你怎么看?

    前言 谷歌身份驗證器,前些年大家都比較陌生,但隨著國內互聯網安全的加強,它越來越多地出現在大家的視野中。 比較廣泛接觸的人群是國際3A游戲愛好者,游戲盜號現象嚴重+國外賬號安全應用廣泛,這類游戲一般都會要求用戶系結名為“兩步驗證”、“雙重驗證”等,平臺一般都推薦用谷歌身份驗證器。 后來區塊鏈業務風靡 ......

    uj5u.com 2020-09-10 03:03:17 more
  • 密碼學DAY1

    目錄 ##1.1 密碼學基本概念 密碼在我們的生活中有著重要的作用,那么密碼究竟來自何方,為何會產生呢? 密碼學是網路安全、資訊安全、區塊鏈等產品的基礎,常見的非對稱加密、對稱加密、散列函式等,都屬于密碼學范疇。 密碼學有數千年的歷史,從最開始的替換法到如今的非對稱加密演算法,經歷了古典密碼學,近代密 ......

    uj5u.com 2020-09-10 03:03:50 more
  • 密碼學DAY1_02

    目錄 ##1.1 ASCII編碼 ASCII(American Standard Code for Information Interchange,美國資訊交換標準代碼)是基于拉丁字母的一套電腦編碼系統,主要用于顯示現代英語和其他西歐語言。它是現今最通用的單位元組編碼系統,并等同于國際標準ISO/IE ......

    uj5u.com 2020-09-10 03:04:50 more
  • 密碼學DAY2

    ##1.1 加密模式 加密模式:https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html ECB ECB : Electronic codebook, 電子密碼本. 需要加密的訊息按照塊密碼的塊大小被分為數個塊,并對每個塊進 ......

    uj5u.com 2020-09-10 03:05:42 more
  • NTP時鐘服務器的特點(京準電子)

    NTP時鐘服務器的特點(京準電子) NTP時鐘服務器的特點(京準電子) 京準電子官V——ahjzsz 首先對時間同步進行了背景介紹,然后討論了不同的時間同步網路技術,最后指出了建立全球或區域時間同步網存在的問題。 一、概 述 在通信領域,“同步”概念是指頻率的同步,即網路各個節點的時鐘頻率和相位同步 ......

    uj5u.com 2020-09-10 03:05:47 more
  • 標準化考場時鐘同步系統推進智能化校園建設

    標準化考場時鐘同步系統推進智能化校園建設 標準化考場時鐘同步系統推進智能化校園建設 安徽京準電子科技官微——ahjzsz 一、背景概述隨著教育事業的快速發展,學校建設如雨后春筍,隨之而來的學校教育、管理、安全方面的問題成了學校管理人員面臨的最大的挑戰,這些問題同時也是學生家長所擔心的。為了讓學生有更 ......

    uj5u.com 2020-09-10 03:05:51 more
  • 位元幣入門

    引言 位元幣基本結構 位元幣基礎知識 1)哈希演算法 2)非對稱加密技術 3)數字簽名 4)MerkleTree 5)哪有位元幣,有的是UTXO 6)位元幣挖礦與共識 7)區塊驗證(共識) 總結 引言 上一篇我們已經知道了什么是區塊鏈,此篇說一下區塊鏈的第一個應用——位元幣。其實先有位元幣,后有的區塊 ......

    uj5u.com 2020-09-10 03:06:15 more
  • 北斗對時服務器(北斗對時設備)電力系統應用

    北斗對時服務器(北斗對時設備)電力系統應用 北斗對時服務器(北斗對時設備)電力系統應用 京準電子科技官微(ahjzsz) 中國北斗衛星導航系統(英文名稱:BeiDou Navigation Satellite System,簡稱BDS),因為是目前世界范圍內唯一可以大面積提供免費定位服務的系統,所以 ......

    uj5u.com 2020-09-10 03:06:20 more
最新发布
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:46:47 more
  • Hyperledger Fabric 使用 CouchDB 和復雜智能合約開發

    在上個實驗中,我們已經實作了簡單智能合約實作及客戶端開發,但該實驗中智能合約只有基礎的增刪改查功能,且其中的資料管理功能與傳統 MySQL 比相差甚遠。本文將在前面實驗的基礎上,將 Hyperledger Fabric 的默認資料庫支持 LevelDB 改為 CouchDB 模式,以實作更復雜的資料... ......

    uj5u.com 2023-04-16 07:28:31 more
  • .NET Core 波場鏈離線簽名、廣播交易(發送 TRX和USDT)筆記

    Get Started NuGet You can run the following command to install the Tron.Wallet.Net in your project. PM> Install-Package Tron.Wallet.Net 配置 public reco ......

    uj5u.com 2023-04-14 08:08:00 more
  • DKP 黑客分析——不正確的代幣對比率計算

    概述: 2023 年 2 月 8 日,針對 DKP 協議的閃電貸攻擊導致該協議的用戶損失了 8 萬美元,因為 execute() 函式取決于 USDT-DKP 對中兩種代幣的余額比率。 智能合約黑客概述: 攻擊者的交易:0x0c850f,0x2d31 攻擊者地址:0xF38 利用合同:0xf34ad ......

    uj5u.com 2023-04-07 07:46:09 more
  • Defi開發簡介

    Defi開發簡介 介紹 Defi是去中心化金融的縮寫, 是一項旨在利用區塊鏈技術和智能合約創建更加開放,可訪問和透明的金融體系的運動. 這與傳統金融形成鮮明對比,傳統金融通常由少數大型銀行和金融機構控制 在Defi的世界里,用戶可以直接從他們的電腦或移動設備上訪問廣泛的金融服務,而不需要像銀行或者信 ......

    uj5u.com 2023-04-05 08:01:34 more
  • solidity簡單的ERC20代幣實作

    // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; import "hardhat/console.sol"; //ERC20 同質化代幣,每個代幣的本質或性質都是相同 //ETH 是原生代幣,它不是ERC20代幣, ......

    uj5u.com 2023-03-21 07:56:29 more
  • solidity 參考型別修飾符memory、calldata與storage 常量修飾符C

    在solidity語言中 參考型別修飾符(參考型別為存盤空間不固定的數值型別) memory、calldata與storage,它們只能修飾參考型別變數,比如字串、陣列、位元組等... memory 適用于方法傳參、返參或在方法體內使用,使用完就會清除掉,釋放記憶體 calldata 僅適用于方法傳參 ......

    uj5u.com 2023-03-08 07:57:54 more
  • solidity注解標簽

    在solidity語言中 注釋符為// 注解符為/* 內容*/ 或者 是 ///內容 注解中含有這幾個標簽給予我們使用 @title 一個應該描述合約/介面的標題 contract, library, interface @author 作者的名字 contract, library, interf ......

    uj5u.com 2023-03-08 07:57:49 more
  • 評價指標:相似度、GAS消耗

    【代碼注釋自動生成方法綜述】 這些評測指標主要來自機器翻譯和文本總結等研究領域,可以評估候選文本(即基于代碼注釋自動方法而生成)和參考文本(即基于手工方式而生成)的相似度. BLEU指標^[^?88^^?^]^:其全稱是bilingual evaluation understudy.該指標是最早用于 ......

    uj5u.com 2023-02-23 07:27:39 more
  • 基于NOSTR協議的“公有制”版本的Twitter,去中心化社交軟體Damus

    最近,一個幽靈,Web3的幽靈,在網路游蕩,它叫Damus,這玩意詮釋了什么叫做病毒式營銷,滑稽的是,一個Web3產品卻在Web2的產品鏈上瘋狂傳銷,各方大佬紛紛為其背書,到底發生了什么?Damus的葫蘆里,賣的是什么藥? 注冊和簡單實用 很少有什么產品在用戶注冊環節會有什么噱頭,但Damus確實出 ......

    uj5u.com 2023-02-05 06:48:39 more