Context

1. 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.
 */

以上是Android源碼對Context的描述:
- 它是應用程序環境的全局信息的接口。
- 這是一個抽象類,由Android系統提供。
- 它允許訪問特定於應用程序的資源和類,以及調用應用程序級操作,如啓動活動,廣播和接收意圖等。

綜上:Context的第一個作用是獲取應用程序的資源和類,第二個作用是調用應用程序級操作,比如啓動活動、廣播和接收意圖等。

補充:理解以上內容的朋友,下面的內容可以忽略。下面的內容都是對Context概念和作用有一個更好的理解所做的補充。

2. 如何理解Context是一個環境

Context是維持Android程序各組件能夠正常工作的一個核心功能類,而這個核心功能類相當於一個大的環境,只有在這個環境下,Android的資源才能被獲取以及Android的各項組件才能被調用。

3. 如何理解Context環境下各項組件才能被調用

Android雖然採用java語言開發,但是Android程序並非和java程序那樣使用main()方法就可以運行。Android應用模型是基於組件的應用設計模式,組件的運行一定要有一個完整的Android環境。在這個環境下:Actvity、Service、BroadCast、ContentProvider纔可以正常創建和運行。所以這些組件並不能按照java對象創建方式,new一下就能創建實例!而是需要一個環境,而這個環境就是我們所需要了解的Context。

通俗的方式來說:Android應用程序就像一部電影,Android的四大組件相當於電影的四大主角,而Context就相當於是攝像鏡頭,只有通過攝像鏡頭我們才能看到四大主角。主角是被內定好的,不能隨便new出來。而其他跑龍套的並不是內定的,也就是說不是那麼重要,他們可以被new出來,但是他們也需要通過攝像鏡頭纔可以看到,所以纔有了Button mButton=new Button(Context)。

4. Context作用的具體體現

有了Context這個環境,Android組件纔可以正常被創建和調用,所以Context的作用如下:

  • TextView tv = new TextView(getContext());

  • ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), …);

  • AudioManager am = (AudioManager) getContext().
    getSystemService(Context.AUDIO_SERVICE);

  • getApplicationContext().getSharedPreferences(name, mode);

  • getApplicationContext().getContentResolver().query(uri, …);

  • getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;

  • getContext().startActivity(intent);

  • getContext().startService(intent);

  • getContext().sendBroadcast(intent);

5. Context的繼承結構

image

  • 從繼承結構可以看出:Context有兩個子類:ContextWrapper和ContextImpl。
  • 從名稱上可以看出:ContextWrapper是Context的封裝類;ContextImpl是Context的實現類。
  • ContextWrapper有三個子類:Application、Service和ContextThemeWrapper。
  • ContextThemeWrapper是一個帶主題的封裝類,它的直接子類就是Activity。
  • Context環境一共有三種類型:Activity、Service和Application,它們承擔不同的作用,而具體Context功能由ContextImpl類實現。

綜上:

  • Activity、Service和Application這三種類型的Context,在多數情況下,都是可以通用的,而爲了安全考慮,在某些情況下,是不可以通用的,諸如啓動Activity、彈出Dialog等。一個Activity的啓動必須建立在另一個Actvity的基礎上;Dialog也必須在Activity上面彈出,這些情況下,也只能使用Activity的Context

  • 也就是說:凡是跟UI相關的,都應該使用Activity做爲Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,如下圖所示:
    image

6. Context的數量

Context數量 = Activity數量 + Service數量 + 1(Application)

7. Application入口類作爲工具類,這種做法是否可行?

強力不推薦!Application入口類的職責就是爲了初始化!根據面向對象的單一職責原則,Application不推薦作爲工具類使用。

8. getApplication()、getApplicationContext()和getBaseContext()的關係

我們在Activity中獲取分別打印這個三個方法:

MyApplication myApp = (MyApplication) getApplication();  

Context appContext = getApplicationContext();  

Context baseContext = getBaseContext();

打印結果:

getApplication::::com.beidou.mvptest.MyApplication@53502fac

getApplicationContext::::com.beidou.mvptest.MyApplication@53502fac

baseContext::::android.app.ContextImpl@53505ce4

結論:
- getApplication和getApplicationContext得到的是一個對象MyApplication。
- getBaseContext得到的是ContextImpl。

疑問:

  • 第一問:getApplication和getApplicationContext得到的對象是一樣的,那爲何設計兩個方法呢?
  • 答:兩者範圍不同,後者比前者適用範圍更廣。getApplication只適用於Activity和Service,而getApplicationContext還用於其他場景,比如BroadcastReceiver中。
  • 第二問:ContextImpl是什麼東東?
  • 答:

①:ContextImpl是Context功能的實現類。Application和Service、Activity並不會去實現Context的功能,只是做了接口的封裝,具體的功能由ContextImpl完成。

②:因爲Application、Activity、Service都是直接或間接繼承自ContextWrapper的,我們就直接看ContextWrapper的源碼,就會發現所有ContextWrapper中方法的實現都非常統一,就是調用了mBase對象中對應當前方法名的方法。

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

④:再看一下我們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,因此剛纔的打印結果也得到了印證。

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

    ......  
}  

9. 使用Application出現的問題和解決辦法

9.1 問題一:在Application的構造方法中去獲取Context實現類的各種方法

public class MyApplication extends Application {  

    public MyApplication() {  
        String packageName = getPackageName();  
        Log.d("TAG", "package name is " + packageName);  
    }  

}

運行結果:

空指針:

java.lang.RuntimeException: Unable to instantiate application 

com.example.test.MyApplication: java.lang.NullPointerException

修改代碼如下:

public class MyApplication extends Application {  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        String packageName = getPackageName();  
        Log.d("TAG", "package name is " + packageName);  
    }  

}  

運行結果正常!發生了什麼事情呢?回顧ContextWrapper源碼,ContextWrapper中有一個attachBaseContext()方法,這個方法會將傳入的一個Context參數賦值給mBase對象,之後mBase對象就有值了。而我們又知道,所有Context的方法都是調用這個mBase對象的同名方法,那麼也就是說如果在mBase對象還沒賦值的情況下就去調用Context中的任何一個方法時,就會出現空指針異常。Application中方法的執行順序:
image
在onCreate()方法中去初始化全局的變量數據是一種比較推薦的做法,假如你想把初始化提前到極致,也可以重寫attachBaseContext()方法:

public class MyApplication extends Application {  

    @Override  
    protected void attachBaseContext(Context base) {  
        // 在這裏調用Context的方法會崩潰  
        super.attachBaseContext(base);  
        // 在這裏調用Context的方法就沒問題
    }  
}  

9.2 問題二:把Application當做工具類使用時獲取實例採用new的方式

public class MyApplication extends Application {  

    private static MyApplication app;  

    public static MyApplication getInstance() {  
        if (app == null) {  
            app = new MyApplication();  
        }  
        return app;  
    }  
}  

我們已經在上文指出了:new MyApplication實例的方式,得到的對象並不具備Context的能力,如果進行Context操作就會報空指針,因爲它只是一個java對象。而我們知道Application本身就是一個單例了,所以我們直接返回本身即可,不用再去new對象獲取實例,否則弄巧成拙。

public class MyApplication extends Application {  

    private static MyApplication app;  

    public static MyApplication getInstance() {  
        return app;  
    }  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        app = this;  
    }  

}  

10. Context亂用導致的內存泄漏的問題和解決辦法

public class CustomManager  
{  
    private static CustomManager sInstance;  
    private Context mContext;  

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

    public static synchronized CustomManager getInstance(Context context)  
    {  
        if (sInstance == null)  
        {  
            sInstance = new CustomManager(context);  
        }  
        return sInstance;  
    }  

    //some methods   
    private void someOtherMethodNeedContext()  
    {  

    }  
}  

假如:sInstance作爲靜態對象,其生命週期要長於普通的對象,其中也包含Activity,假如Activity A去getInstance獲得sInstance對象,傳入this,常駐內存的CustomManager保存了你傳入的ActivityA 對象(也就是Context),並一直持有,即使Activity(Context)被銷燬掉,但因爲它的引用還存在於一個CustomManager中,就不可能被GC掉,這樣就導致了內存泄漏。也就是所以解決辦法是:

public static synchronized CustomManager getInstance(Context context)  
    {  
        if (sInstance == null)  
        {  
            sInstance = new CustomManager(context.getApplicationContext());  
        }  
        return sInstance;  
    }  

11. 正確使用Context

一般Context造成的內存泄漏,幾乎都是當Context銷燬的時候,卻因爲被引用導致銷燬失敗,而Application的Context對象可以理解爲隨着進程存在的,所以我們總結出使用Context的正確姿勢:

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

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

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

12. 參考文章

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