從getApplicationContext和getApplication再次梳理Android的Application正確用法

Context

在Android開發的時候,不少地方咱們都會用上Context這個東西,好比咱們最經常使用的startActivity,之前也沒怎麼在乎這個到底有什麼用,方法要參數就直接傳過去,今天看到getApplicationContextgetApplication有點懵逼,我以爲有必要去一探究竟了,首先看看什麼是Context:android

Context,翻譯爲上下文,環境。多麼直白又艹的翻譯,想問啥又是上下文,啥又是環境,程序還有上下文。。。爲了避免誤人子弟,來Google的官方說法:app

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系統的一個抽象類,能夠經過它獲取程序的資源,好比:加載Activity,廣播,接收Intent信息等等。eclipse

總的來講它就像是一個程序運行的時候的環境,若是Activity,Service這些是水裏的魚,那它就是水?(原諒個人理解能力,不知道怎麼形容),好吧,理解不透就看代碼(如下代碼來自API-23):ide

public abstract class Context {}

首先它是個抽象類,那它提供了哪些方法,哎,太多了,隨便看幾個吧:工具

//[這個能夠看看個人博客另一篇專門講消息機制的](http://blog.csdn.net/ly502541243/article/details/52062179/)
public abstract Looper getMainLooper();
//獲取當前應用上下文
public abstract Context getApplicationContext();
//開啓activity
public abstract void startActivity(Intent intent);
//獲取valus/strings.xml聲明的字符串
public final String getString(@StringRes int resId) {
        return getResources().getString(resId);
    }
    
//獲取valus/colors.xml聲明的顏色    
public final int getColor(int id) {
    return getResources().getColor(id, getTheme());
}
//發送廣播
public abstract void sendBroadcast(Intent intent);
//開啓服務
public abstract ComponentName startService(Intent service);
//獲取系統服務(ALARM_SERVICE,WINDOW_SERVICE,AUDIO_SERVICE、、、)
public abstract Object getSystemService(@ServiceName @NonNull String name);

咱們發現Context這個抽象類裏面聲明瞭不少咱們開發中經常使用一些方法,那有哪些類實現了這個抽象類呢(普及一個快捷鍵,eclipse下點擊類後按F4能夠看這個類的繼承結構,AndroidStudio我設置的是eclipse的快捷鍵),結構以下oop

  • Contextui

    • ContextWrapperthis

      • TintContextWrapper
      • ContextThemeWrapper
      • IsolatedContext
      • MutableContextWrapper
      • ContextThemeWrapper.net

        • Activity
      • Service
      • RenamingDelegatingContext
      • Application
      • BackupAgent

咱們主要關注一下:ContextWrapper,Activity,Service,Application,先來看看Context的主要實現類ContextWrapper(劇透:其實這並非真正的實現類):看下官方註釋,意思就是這是個簡單的實現:翻譯

Proxying implementation of Context that simply delegates all of its calls to another Context.

構造方法:

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    //設置BaseContext,同構造方法,多了個不爲空的判斷
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    
    .....
    .....
    @Override
    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }
    @Override
    public Looper getMainLooper() {
        return mBase.getMainLooper();
    }
    @Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }
}

注意方法是public的,因此繼承類能夠直接訪問,看了方法的實現,咱們發現真是simply,就是都交給mBase來作相應的處理,關鍵就是構造方法或者attachBaseContext方法設置mBase而且進行操做。

來看看咱們最經常使用的Activity,主要看看getApplication

public class Activity extends ContextThemeWrapper implements ... {
       private Application mApplication; 
       final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
            attachBaseContext(context); 
            ...
            mApplication = application;
        }
}
public final Application getApplication() {
        return mApplication;
}

咱們看到了在attach調用了咱們剛纔說的attachBaseContext,還有給mApplication賦值。這裏出現了另一個咱們關注的Application,到源碼看看:

//構造方法傳了個空,貌似沒什麼用
public Application() {
        super(null);
    }
//一樣在attach中咱們看到了具體的東西
final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

Application和Activity的attach方法感受都差很少,都調用了attachBaseContext(context),成爲了一個Context。

這裏還看到了ContextImpl,其實它纔是Context的真正實現類(看名字也看出來了),但是剛纔咱們看Context的繼承結構時沒看到這個類啊,原來它跟ActivityThread同樣,並無在sdk中,因此是看不到的。這個類的具體實現就不仔細看了,再看要暈了,後面有時間再細細品味...

可是咱們知道了mApplicationcontext是兩個不一樣的東西,因此嚴格意義上來講getApplicationContextgetApplication是不同的,雖然不少時候他們返回的都是同一個對象,可是getApplication只存在於Activity或者Service中,咱們要注意具體的狀況,這個咱們後面再說

Application

爲什麼物

看到這裏咱們發現,Application和Activity都繼承自Context,他們都是環境,只不過Application是隨着咱們的應用(或者包)啓動的時候就存在的環境,Activity是一個界面的環境

使用方法

既然Application是在應用一建立就初始化了,並且是在應用運行時一直存在的,那咱們能夠把它當作是一個全局變量來使用,能夠保存一些共享的數據,或者說作一些工具類的初始化工做。要本身來使用Application的話咱們須要先新建一個類來繼承Application

public class MyApplication extends Application {}

而後重寫它的onCreate作一些工具的初始化:

@Override
    public void onCreate() {
        super.onCreate();
        ToastUtils.register(this);
        //LeakCanary檢測OOM
        LeakCanary.install(this);
    }

最後一個關鍵的工做是要在manifest裏面作一下聲明(無關代碼我忽略了)

<application
    android:name=".MyApplication"
    ...
</application>

而後說說Application的獲取問題,一個方法是咱們直接 (MyApplication)getApplication(),可是還有一種更常見的作法,要在其餘沒有Context的地方也能拿到怎麼辦呢?能夠這樣,仿照單例的作法(只是仿照!),在MyApplication聲明一個靜態變量

public class MyApplication extends Application {
    private static MyApplication instance;
}
@Override
    public void onCreate() {
        super.onCreate();
        instance = this;
}
 // 獲取ApplicationContext
    public static Context getMyApplication() {
        return instance;
}

至此咱們拿到了MyApplication實例,注意這跟咱們常見的單例不同,不要自做聰明去在getMyApplication裏面作一下空的判斷,Application在應用中原本就是一個單例,因此每次返回的都是同一個實體,原文以下:

There is normally no need to subclass Application. In most situation, static singletons can provide the same functionality in a more modular way.

總結

ApplicationActivityService都是繼承自Context,是應用運行時的環境,咱們能夠把Application看作是應用,Activity看作是一個界面,至於getApplicationContextgetApplication,在Activity和Service中他們返回的對象是同樣的,可是在某些特殊狀況下並不保證都是同樣,因此若是想要拿到在manifest裏面聲明的那個Application,務必用getApplication,貼下原文:

getApplication() is available to Activity and Services only. Although in current Android Activity and Service implementations, getApplication() and getApplicationContext() return the same object, there is no guarantee that this will always be the case (for example, in a specific vendor implementation). So if you want the Application class you registered in the Manifest, you should never call getApplicationContext() and cast it to your application, because it may not be the application instance (which you obviously experienced with the test framework).

還有就是由於他們都繼承自Context,好比在打開Dialog的時候好像是均可以,其實否則,好比咱們大多數狀況:

AlertDialog.Builder builder = new Builder(Activity.this);//能夠
 AlertDialog.Builder builder = new Builder(getApplicationContext());//內存泄漏

若是把this換成getApplicationContext(),不會報錯,可是就如咱們剛纔所說,getApplicationContext() 返回的上下文會隨着應用一直存在,而這裏的Dialog應該屬於Activity,Activity關閉了咱們沒法銷燬上下文(Dialog持有全局的上下文)。

因此在使用的時候要注意具體的使用場景,避免內存泄漏問題。

這裏順便附上一個stackoverflow對於這個問題的連接


這篇文章還有個續集和補充
到底getApplicationContext和getApplication是否是返回同一個對象?

相關文章
相關標籤/搜索