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

SillyMonkey發表於2019-04-02

Context

在Android開發的時候,很多地方我們都會用上Context這個東西,比如我們最常用的startActivity,以前也沒怎麼在意這個到底有什麼用,方法要引數就直接傳過去,今天看到getApplicationContextgetApplication有點懵逼,我覺得有必要去一探究竟了,首先看看什麼是Context:

Context,翻譯為上下文,環境。不過又想問啥又是上下文,啥又是環境,程式還有上下文,來個Google的官方說法:

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資訊等等。

總的來說它就像是一個程式執行的時候的環境,如果Activity,Service這些是水裡的魚,那它就是水?(原諒我的理解能力,不知道怎麼形容),好吧,理解不透就看程式碼(以下程式碼來自API-23):

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的快捷鍵),結構如下

  • Context
    • ContextWrapper
      • TintContextWrapper
      • ContextThemeWrapper
      • IsolatedContext
      • MutableContextWrapper
      • ContextThemeWrapper
        • 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,他們返回的物件有可能不一樣(雖然大部分時間是一樣的,都是整個應用的上下文),如果想要拿到在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());//這裡Dialog應該是跟Activity相關聯,而不是整個APP生命週期
複製程式碼

如果把this換成getApplicationContext(),執行的時候會報錯,Unable to add window -- token null is not for an application,如我們剛才所說,getApplicationContext() 返回的上下文會隨著應用一直存在,而這裡的Dialog應該屬於Activity。

所以在使用的時候要注意具體的使用場景。

這裡順便附上一個stackoverflow對於這個問題的連結


這篇文章還有個續集和補充 到底getApplicationContext和getApplication是不是返回同一個物件?

相關文章