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


原文地址http://blog.csdn.net/ly502541243/article/details/52105466

原文地址http://blog.csdn.net/ly502541243/article/details/52105466

Context

在Android開發的時候,很多地方我們都會用上Context這個東西,比如我們最常用的startActivity,以前也沒怎么在意這個到底有什么用,方法要參數就直接傳過去,今天看到getApplicationContext和getApplication有點懵逼,我覺得有必要去一探究竟了,首先看看什么是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 {}
  • 1

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

//[這個可以看看我的博客另外一篇專門講消息機制的](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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

我們發現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(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

注意方法是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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

//構造方法傳了個空,貌似沒什么用 public Application() { super(null); } //同樣在attach中我們看到了具體的東西 final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Application和Activity的attach方法感覺都差不多,都調用了attachBaseContext(context),成為了一個Context。

這里還看到了ContextImpl,其實它才是Context的真正實現類(看名字也看出來了),可是剛才我們看Context的繼承結構時沒看到這個類啊,原來它跟ActivityThread一樣,並沒有在sdk中,所以是看不到的。這個類的具體實現就不仔細看了,再看要暈了,后面有時間再細細品味…

但是我們知道了mApplication和context是兩個不同的東西,所以嚴格意義上來說getApplicationContext和getApplication是不一樣的,雖然很多時候他們返回的都是同一個對象,但是getApplication只存在於Activity或者Service中,我們要注意具體的情況,這個我們后面再說

Application

為何物

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

使用方法

既然Application是在應用一創建就初始化了,而且是在應用運行時一直存在的,那我們可以把它當做是一個全局變量來使用,可以保存一些共享的數據,或者說做一些工具類的初始化工作。要自己來使用Application的話我們需要先新建一個類來繼承Application

public class MyApplication extends Application {}
  • 1

然后重寫它的onCreate做一些工具的初始化:

@Override public void onCreate() { super.onCreate(); ToastUtils.register(this); //LeakCanary檢測OOM LeakCanary.install(this); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

<application
    android:name=".MyApplication" ... </application>
  • 1
  • 2
  • 3
  • 4

然后說說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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

至此我們拿到了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.

總結

Application,Activity,Service都是繼承自Context,是應用運行時的環境,我們可以把Application看做是應用,Activity看做是一個界面,至於getApplicationContext和getApplication,他們返回的對象有可能不一樣(雖然大部分時間是一樣的,都是整個應用的上下文),如果想要拿到在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());//內存泄漏
  • 1
  • 2

如果把this換成getApplicationContext(),不會報錯,但是就如我們剛才所說,getApplicationContext() 返回的上下文會隨着應用一直存在,而這里的Dialog應該屬於Activity,Activity關閉了我們無法銷毀上下文(Dialog持有全局的上下文)。

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

 

=====================================================================================================

=====================================================================================================

 

深入探究getApplicationContext和getApplication是不是返回同一個對象?

在上篇文章從getApplicationContext和getApplication再次梳理Android的Application正確用法中,我提到

但是我們知道了mApplication和context是兩個不同的東西,所以嚴格意義上來說getApplicationContext和getApplication是不一樣的,雖然很多時候他們返回的都是同一個對象

注意到我這里說的是這兩個方法返回的對象是不一樣的,因為我看到Activity中這兩個方法返回了兩個對象,就單純的以為他們真的是不一樣的,看來真是淺嘗輒止了,做了個錯誤示范,代碼還是要刨根問底啊。

找不同

今天來做一個糾正和補充,我們來繼續往下看代碼,看看他們是不是真的不一樣,還是有相似之處:

public abstract Context getApplicationContext();
  • 1

getApplicationContext我們知道是一個抽象方法,他的真正實現是在ContextImpl中:

@Override public Context getApplicationContext() { return (mPackageInfo != null) ? mPackageInfo.getApplication() : mMainThread.getApplication(); }
  • 1
  • 2
  • 3
  • 4
  • 5

再來看看getApplication方法(只存在於Activity和Service中):

public final Application getApplication() { return mApplication; }
  • 1
  • 2
  • 3

那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; } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在上篇文章中,我看到了這里就覺得這兩個方法返回的對象不一樣,可是我們忽略了getApplicationContext這個方法,當mPackageInfo不為空和為空是分別調用了mPackageInfo.getApplication()和mMainThread.getApplication(),那getApplicationContext到底返回的東西跟mApplication有什么不同,來看看這兩個方法,在LoadedApk.java中看到mPackageInfo.getApplication():

Application getApplication() { return mApplication; }
  • 1
  • 2
  • 3

在LoadedApk也有一個mApplication,這個mApplication的賦值在LoadedApk的makeApplication:

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
... if (mApplication != null) { return mApplication; } Application app = null; ... app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); ... mActivityThread.mAllApplications.add(app); mApplication = app; ... } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

看到首先是一個空判斷(單例),為空的話新建了一個Application然后賦值給mApplication,我們再看看mMainThread.getApplication()返回了什么,在ActivityThread.java中:

public Application getApplication() { return mInitialApplication; }
  • 1
  • 2
  • 3

再來看看mInitialApplication的賦值在哪里:

private void handleBindApplication(AppBindData data) {
... Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我們又看到了makeApplication,至於data.info也是LoadedApk這個類,看到這里我們就一目了然了,繞來繞去結果都是同一個東西,只是可能創建的時機不同,一個是在LoadedApk,一個是在ActivityThread,不過最后我們發現這個getApplicationContext()返回的都是mApplication。

真相大白

這個命名就很有意思了,在LoadedApk我們看到了一個叫mApplication的東西,在Activity也有一個叫mApplication,那他們是不是有什么聯系呢?來看看在Activity中mApplication的賦值,在attach方法中找到了它(方法中的其他參數我去掉了):

final void attach(Application application) { mApplication = application; }
  • 1
  • 2
  • 3

也就是說等於調用attach方法時傳入的application,那Activity的attach是在哪里調用呢,我們要來到反復提到的一個應用程序入口類ActivityThread,它有一個performLaunchActivity的方法,用來加載一個Activity,這里就有attach()的調用(我去掉了其他參數):

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
... Application app = r.packageInfo.makeApplication(false, mInstrumentation); ... activity.attach(app); ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我們發現又來了。。。熟悉的makeApplication(),r.packageInfo果然是LoadedApk類,最后殊途同歸,又來到了這個單例,返回程序唯一的mApplication,還是一樣的配方。。。

結果

結果很明顯了,標題的問題已解,getApplicationContext和getApplication返回的是不是同一個對象?答:是的!

當然話不能說的那么死,他們相同的前提是mApplication不為空,話又說回來,這個是全局的上下文,程序都啟動了他怎么會為空呢,至於它到底什么情況會為空造成返回的對象不一樣呢,待武功精進了繼續分解。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM