關於Context還有哪些是你不知道的?

一、Context 概念
Context是個抽象類,經過類的結構能夠看到:Activity、Service、Application都是Context的子類;android

從Android系統的角度來理解:Context是一個場景,描述的是一個應用程序環境的信息,即上下文,表明與操做系統的交互的一種過程。安全

從程序的角度上來理解:Context是個抽象類,而Activity、Service、Application等都是該類的一個實現。app

查看類的繼承關係:ctrl + H (Windows系統)
ide

應用在三種狀況下會建立Context對象(即一般說的context):
1> 建立Application 對象時,即第一次啓動app時。 整個App共一個Application對象,因此也只有一個Application 的Context,Application銷燬,它也銷燬;
2> 建立Activity對象時。Activity銷燬,它也銷燬;
3> 建立Service對象時。Service銷燬,它也銷燬。工具

由此能夠獲得應用程序App能夠建立的Context(Activity和Service沒啓動就不會建立)個數公式通常爲:
總Context實例個數 = Service個數 + Activity個數 + 1(Application對應的Context對象)oop

二、源碼中的Contextui

`/**
 * 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 {
    /**
     * File creation mode: the default mode, where the created file can only
     * be accessed by the calling application (or all applications sharing the
     * same user ID).
     * @see #MODE_WORLD_READABLE
     * @see #MODE_WORLD_WRITEABLE
     */
    public static final int MODE_PRIVATE = 0x0000;

    public static final int MODE_WORLD_WRITEABLE = 0x0002;

    public static final int MODE_APPEND = 0x8000;

    public static final int MODE_MULTI_PROCESS = 0x0004;

    .
    .
    .
    }`

源碼中的註釋是這麼來解釋Context的:Context提供了關於應用環境全局信息的接口。它是一個抽象類,它的執行被Android系統所提供。它容許獲取以應用爲特徵的資源和類型,是一個統領一些資源(應用程序環境變量等)的上下文。就是說,它描述一個應用程序環境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現類;經過它咱們能夠獲取應用程序的資源和類(包括應用級別操做,如啓動Activity,發廣播,接受Intent等)。既然上面Context是一個抽象類,那麼確定有他的實現類咯,咱們在Context的源碼中經過IDE能夠查看到他的子類最終能夠獲得以下關係圖:
this

Context 繼承結構
三、Context做用域
雖然Context神通廣大,但並非隨便拿到一個Context實例就能夠隨心所欲,它的使用仍是有一些規則限制的。因爲Context的具體實例是由ContextImpl類去實現的,所以在絕大多數場景下,Activity、Service和Application這三種類型的Context都是能夠通用的。不過有幾種場景比較特殊,好比啓動Activity,還有彈出Dialog。出於安全緣由的考慮,Android是不容許Activity或Dialog憑空出現的,一個Activity的啓動必需要創建在另外一個Activity的基礎之上,也就是以此造成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),所以在這種場景下,咱們只能使用Activity類型的Context,不然將會出錯。
spa

Context做用域操作系統

從上圖咱們能夠發現Activity所持有的Context的做用域最廣,無所不能。由於Activity繼承自ContextThemeWrapper,而Application和Service繼承自ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎上又作了一些操做使得Activity變得更強大,這裏我就再也不貼源碼給你們分析了,有興趣的童鞋能夠本身查查源碼。上圖中的YES和NO我也再也不作過多的解釋了,這裏我說一下上圖中Application和Service所不推薦的兩種使用狀況。
1:若是咱們用ApplicationContext去啓動一個LaunchMode爲standard的Activity的時候會報錯android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?這是由於非Activity類型的Context並無所謂的任務棧,因此待啓動的Activity就找不到棧了。解決這個問題的方法就是爲待啓動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啓動的時候就爲它建立一個新的任務棧,而此時Activity是以singleTask模式啓動的。全部這種用Application啓動Activity的方式不推薦使用,Service同Application。
2:在Application和Service中去layout inflate也是合法的,可是會使用系統默認的主題樣式,若是你自定義了某些樣式可能不會被使用。因此這種方式也不推薦使用。
一句話總結:凡是跟UI相關的,都應該使用Activity作爲Context來處理;其餘的一些操做,Service,Activity,Application等實例均可以,固然了,注意Context引用的持有,防止內存泄漏。

4 Context數量
那麼一個應用程序中到底有多少個Context呢?其實根據上面的Context類型咱們就已經能夠得出答案了。Context一共有Application、Activity和Service三種類型,所以一個應用程序中Context數量的計算公式就能夠這樣寫:

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

上面的1表明着Application的數量,由於一個應用程序中能夠有多個Activity和多個Service,可是隻能有一個Application。

5 Application Context的設計
基本上每個應用程序都會有一個本身的Application,並讓它繼承自系統的Application類,而後在本身的Application類中去封裝一些通用的操做。其實這並非Google所推薦的一種作法,由於這樣咱們只是把Application當成了一個通用工具類來使用的,而實際上使用一個簡單的單例類也能夠實現一樣的功能。可是根據個人觀察,有太多的項目都是這樣使用Application的。固然這種作法也並無什麼反作用,只是說明仍是有很多人對於Application理解的還有些欠缺。那麼這裏咱們先來對Application的設計進行分析,講一些你們所不知道的細節,而後再看一下平時使用Application的問題。

首先新建一個MyApplication並讓它繼承自Application,而後在AndroidManifest.xml文件中對MyApplication進行指定,以下所示:

<application
    android:name=".MyApplication"
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    ......
</application>

指定完成後,當咱們的程序啓動時Android系統就會建立一個MyApplication的實例,若是這裏不指定的話就會默認建立一個Application的實例。

前面提到過,如今不少的Application都是被看成通用工具類來使用的,那麼既然做爲一個通用工具類,咱們要怎樣才能獲取到它的實例呢?以下所示:

public class MainActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyApplication myApp = (MyApplication) getApplication();
        Log.d("TAG", "getApplication is " + myApp);
    }
    
}

能夠看到,代碼很簡單,只須要調用getApplication()方法就能拿到咱們自定義的Application的實例了,打印結果以下所示:


那麼除了getApplication()方法,其實還有一個getApplicationContext()方法,這兩個方法看上去好像有點關聯,那麼它們的區別是什麼呢?咱們將代碼修改一下:

public class MainActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyApplication myApp = (MyApplication) getApplication();
        Log.d("TAG", "getApplication is " + myApp);
        Context appContext = getApplicationContext();
        Log.d("TAG", "getApplicationContext is " + appContext);
    }
    
}

一樣,咱們把getApplicationContext()的結果打印了出來,如今從新運行代碼,結果以下圖所示:

咦?好像打印出的結果是同樣的呀,連後面的內存地址都是相同的,看來它們是同一個對象。其實這個結果也很好理解,由於前面已經說過了,Application自己就是一個Context,因此這裏獲取getApplicationContext()獲得的結果就是MyApplication自己的實例。

那麼有的朋友可能就會問了,既然這兩個方法獲得的結果都是相同的,那麼Android爲何要提供兩個功能重複的方法呢?實際上這兩個方法在做用域上有比較大的區別。getApplication()方法的語義性很是強,一看就知道是用來獲取Application實例的,可是這個方法只有在Activity和Service中才能調用的到。那麼也許在絕大多數狀況下咱們都是在Activity或者Service中使用Application的,可是若是在一些其它的場景,好比BroadcastReceiver中也想得到Application的實例,這時就能夠藉助getApplicationContext()方法了,以下所示:

public class MyReceiver extends BroadcastReceiver {
 
    @Override
    public void onReceive(Context context, Intent intent) {
        MyApplication myApp = (MyApplication) context.getApplicationContext();
        Log.d("TAG", "myApp is " + myApp);
    }
 
}

也就是說,getApplicationContext()方法的做用域會更廣一些,任何一個Context的實例,只要調用getApplicationContext()方法均可以拿到咱們的Application對象。

那麼更加細心的朋友會發現,除了這兩個方法以外,其實還有一個getBaseContext()方法,這個baseContext又是什麼東西呢?咱們仍是經過打印的方式來驗證一下:

哦?此次獲得的是不一樣的對象了,getBaseContext()方法獲得的是一個ContextImpl對象。這個ContextImpl是否是感受有點似曾相識?回去看一下Context的繼承結構圖吧,ContextImpl正是上下文功能的實現類。也就是說像Application、Activity這樣的類其實並不會去具體實現Context的功能,而僅僅是作了一層接口封裝而已,Context的具體功能都是由ContextImpl類去完成的。那麼這樣的設計究竟是怎麼實現的呢?咱們仍是來看一下源碼吧。由於Application、Activity、Service都是直接或間接繼承自ContextWrapper的,咱們就直接看ContextWrapper的源碼,以下所示:

/**
 * Proxying implementation of Context that simply delegates all of its calls to
 * another Context.  Can be subclassed to modify behavior without changing
 * the original Context.
 */
public class ContextWrapper extends Context {
    Context mBase;
    
    /**
     * Set the base context for this ContextWrapper.  All calls will then be
     * delegated to the base context.  Throws
     * IllegalStateException if a base context has already been set.
     * 
     * @param base The new base context for this wrapper.
     */
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
 
    /**
     * @return the base context as set by the constructor or setBaseContext
     */
    public Context getBaseContext() {
        return mBase;
    }
 
    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }
 
    @Override
    public Resources getResources() {
        return mBase.getResources();
    }
 
    @Override
    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }
 
    @Override
    public Looper getMainLooper() {
        return mBase.getMainLooper();
    }
    
    @Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }
 
    @Override
    public String getPackageName() {
        return mBase.getPackageName();
    }
 
    @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }
    
    @Override
    public void sendBroadcast(Intent intent) {
        mBase.sendBroadcast(intent);
    }
 
    @Override
    public Intent registerReceiver(
        BroadcastReceiver receiver, IntentFilter filter) {
        return mBase.registerReceiver(receiver, filter);
    }
 
    @Override
    public void unregisterReceiver(BroadcastReceiver receiver) {
        mBase.unregisterReceiver(receiver);
    }
 
    @Override
    public ComponentName startService(Intent service) {
        return mBase.startService(service);
    }
 
    @Override
    public boolean stopService(Intent name) {
        return mBase.stopService(name);
    }
 
    @Override
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        return mBase.bindService(service, conn, flags);
    }
 
    @Override
    public void unbindService(ServiceConnection conn) {
        mBase.unbindService(conn);
    }
 
    @Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }
 
    ......
}

因爲ContextWrapper中的方法仍是很是多的,我就進行了一些篩選,只貼出來了部分方法。那麼上面的這些方法相信你們都是很是熟悉的,getResources()、getPackageName()、getSystemService()等等都是咱們常常要用到的方法。那麼全部這些方法的實現又是什麼樣的呢?其實全部ContextWrapper中方法的實現都很是統一,就是調用了mBase對象中對應當前方法名的方法。

那麼這個mBase對象又是什麼呢?咱們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法實際上是由系統來調用的,它會把ContextImpl對象做爲參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,以後ContextWrapper中的全部方法其實都是經過這種委託的機制交由ContextImpl去具體實現的,因此說ContextImpl是上下文功能的實現類是很是準確的。

那麼另外再看一下咱們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,所以剛纔的打印結果也獲得了印證。

6正確使用Context
通常Context形成的內存泄漏,幾乎都是當Context銷燬的時候,卻由於被引用致使銷燬失敗,而Application的Context對象能夠理解爲隨着進程存在的,因此咱們總結出使用Context的正確姿式:
1:當Application的Context能搞定的狀況下,而且生命週期長的對象,優先使用Application的Context。
2:不要讓生命週期長於Activity的對象持有到Activity的引用。
3:儘可能不要在Activity中使用非靜態內部類,由於非靜態內部類會隱式持有外部類實例的引用,若是使用靜態內部類,將外部實例引用做爲弱引用持有。

7總結
總之Context在Android系統中的地位很重要,它幾乎無所不能,但它也不是你想用就能隨便用的,謹防使用不當引發的內存問題。

好了,文章到這裏就結束了,若是你以爲文章寫得不錯就給個讚唄?若是你以爲那裏值得改進的,請給我留言。必定會認真查詢,修正不足。謝謝。

相關文章
相關標籤/搜索