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

Context

在Android開(kāi)發(fā)的時(shí)候,很多地方我們都會(huì)用上Context這個(gè)東西,比如我們最常用的startActivity,以前也沒(méi)怎么在意這個(gè)到底有什么用,方法要參數(shù)就直接傳過(guò)去,今天看到getApplicationContextgetApplication有點(diǎn)懵逼,我覺(jué)得有必要去一探究竟了,首先看看什么是Context:

Context,翻譯為上下文,環(huán)境。多么直白又艸的翻譯,想問(wèn)啥又是上下文,啥又是環(huán)境,程序還有上下文。。。為了不誤人子弟,來(lái)Google的官方說(shuō)法:

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

翻譯下:它是一個(gè)應(yīng)用程序的全局環(huán)境,是Android系統(tǒng)的一個(gè)抽象類(lèi),可以通過(guò)它獲取程序的資源,比如:加載Activity,廣播,接收Intent信息等等。

總的來(lái)說(shuō)它就像是一個(gè)程序運(yùn)行的時(shí)候的環(huán)境,如果Activity,Service這些是水里的魚(yú),那它就是水?(原諒我的理解能力,不知道怎么形容),好吧,理解不透就看代碼(以下代碼來(lái)自API-23):

public abstract class Context {}

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

//[這個(gè)可以看看我的博客另外一篇專(zhuān)門(mén)講消息機(jī)制的](http://blog.csdn.net/ly502541243/article/details/52062179/)
public abstract Looper getMainLooper();
//獲取當(dāng)前應(yīng)用上下文
public abstract Context getApplicationContext();
//開(kāi)啟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());
}
//發(fā)送廣播
public abstract void sendBroadcast(Intent intent);
//開(kāi)啟服務(wù)
public abstract ComponentName startService(Intent service);
//獲取系統(tǒng)服務(wù)(ALARM_SERVICE,WINDOW_SERVICE,AUDIO_SERVICE、、、)
public abstract Object getSystemService(@ServiceName @NonNull String name);

我們發(fā)現(xiàn)Context這個(gè)抽象類(lèi)里面聲明了很多我們開(kāi)發(fā)中常用一些方法,那有哪些類(lèi)實(shí)現(xiàn)了這個(gè)抽象類(lèi)呢(普及一個(gè)快捷鍵,eclipse下點(diǎn)擊類(lèi)后按F4可以看這個(gè)類(lèi)的繼承結(jié)構(gòu),AndroidStudio我設(shè)置的是eclipse的快捷鍵),結(jié)構(gòu)如下

  • Context
    • ContextWrapper
      • TintContextWrapper
      • ContextThemeWrapper
      • IsolatedContext
      • MutableContextWrapper
      • ContextThemeWrapper
        • Activity
      • Service
      • RenamingDelegatingContext
      • Application
      • BackupAgent

我們主要關(guān)注一下:ContextWrapper,Activity,Service,Application,先來(lái)看看Context的主要實(shí)現(xiàn)類(lèi)ContextWrapper(劇透:其實(shí)這并不是真正的實(shí)現(xiàn)類(lèi)):看下官方注釋?zhuān)馑季褪沁@是個(gè)簡(jiǎn)單的實(shí)現(xiàn):

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

構(gòu)造方法:

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    //設(shè)置BaseContext,同構(gòu)造方法,多了個(gè)不為空的判斷
    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的,所以繼承類(lèi)可以直接訪問(wèn),看了方法的實(shí)現(xiàn),我們發(fā)現(xiàn)真是simply,就是都交給mBase來(lái)做相應(yīng)的處理,關(guān)鍵就是構(gòu)造方法或者attachBaseContext方法設(shè)置mBase并且進(jìn)行操作。

來(lái)看看我們最常用的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調(diào)用了我們剛才說(shuō)的attachBaseContext,還有給mApplication賦值。這里出現(xiàn)了另外一個(gè)我們關(guān)注的Application,到源碼看看:

//構(gòu)造方法傳了個(gè)空,貌似沒(méi)什么用
public Application() {
        super(null);
    }
//同樣在attach中我們看到了具體的東西
final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

Application和Activity的attach方法感覺(jué)都差不多,都調(diào)用了attachBaseContext(context),成為了一個(gè)Context。

這里還看到了ContextImpl,其實(shí)它才是Context的真正實(shí)現(xiàn)類(lèi)(看名字也看出來(lái)了),可是剛才我們看Context的繼承結(jié)構(gòu)時(shí)沒(méi)看到這個(gè)類(lèi)啊,原來(lái)它跟ActivityThread一樣,并沒(méi)有在sdk中,所以是看不到的。這個(gè)類(lèi)的具體實(shí)現(xiàn)就不仔細(xì)看了,再看要暈了,后面有時(shí)間再細(xì)細(xì)品味...

但是我們知道了mApplicationcontext是兩個(gè)不同的東西,所以嚴(yán)格意義上來(lái)說(shuō)getApplicationContextgetApplication是不一樣的,雖然很多時(shí)候他們返回的都是同一個(gè)對(duì)象,但是getApplication只存在于Activity或者Service中,我們要注意具體的情況,這個(gè)我們后面再說(shuō)

Application

為何物

看到這里我們發(fā)現(xiàn),Application和Activity都繼承自Context,他們都是環(huán)境,只不過(guò)Application是隨著我們的應(yīng)用(或者包)啟動(dòng)的時(shí)候就存在的環(huán)境,Activity是一個(gè)界面的環(huán)境

使用方法

既然Application是在應(yīng)用一創(chuàng)建就初始化了,而且是在應(yīng)用運(yùn)行時(shí)一直存在的,那我們可以把它當(dāng)做是一個(gè)全局變量來(lái)使用,可以保存一些共享的數(shù)據(jù),或者說(shuō)做一些工具類(lèi)的初始化工作。要自己來(lái)使用Application的話我們需要先新建一個(gè)類(lèi)來(lái)繼承Application

public class MyApplication extends Application {}

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

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

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

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

然后說(shuō)說(shuō)Application的獲取問(wèn)題,一個(gè)方法是我們直接 (MyApplication)getApplication(),但是還有一種更常見(jiàn)的做法,要在其他沒(méi)有Context的地方也能拿到怎么辦呢?可以這樣,仿照單例的做法(只是仿照?。?,在MyApplication聲明一個(gè)靜態(tài)變量

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實(shí)例,注意這跟我們常見(jiàn)的單例不一樣,不要自作聰明去在getMyApplication里面做一下空的判斷,Application在應(yīng)用中本來(lái)就是一個(gè)單例,所以每次返回的都是同一個(gè)實(shí)體,原文如下:

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

總結(jié)

ApplicationActivity,Service都是繼承自Context,是應(yīng)用運(yùn)行時(shí)的環(huán)境,我們可以把Application看做是應(yīng)用,Activity看做是一個(gè)界面,至于getApplicationContextgetApplication,他們返回的對(duì)象有可能不一樣(雖然大部分時(shí)間是一樣的,都是整個(gè)應(yīng)用的上下文),如果想要拿到在manifest里面聲明的那個(gè)Application,務(wù)必用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).

還有就是因?yàn)樗麄兌祭^承自Context,比如在打開(kāi)Dialog的時(shí)候好像是都可以,其實(shí)不然,比如我們大多數(shù)情況:

 AlertDialog.Builder builder = new Builder(Activity.this);//可以
 AlertDialog.Builder builder = new Builder(getApplicationContext());//內(nèi)存泄漏(可能叫超出生命周期范圍更合適)

如果把this換成getApplicationContext(),不會(huì)報(bào)錯(cuò),但是就如我們剛才所說(shuō),getApplicationContext() 返回的上下文會(huì)隨著應(yīng)用一直存在,這就超出了dialog應(yīng)該有的生命周期,不會(huì)隨著Activity而關(guān)閉

所以在使用的時(shí)候要注意具體的使用場(chǎng)景,避免內(nèi)存泄漏問(wèn)題。

這里順便附上一個(gè)stackoverflow對(duì)于這個(gè)問(wèn)題的鏈接


這篇文章還有個(gè)續(xù)集和補(bǔ)充
到底getApplicationContext和getApplication是不是返回同一個(gè)對(duì)象?

這里為了回復(fù)評(píng)論的質(zhì)疑,現(xiàn)在嘗試傳入application的方式來(lái)顯示dialog看能不能隨著activity回收:

測(cè)試的Activity代碼如下:

public class TestActivity extends Activity {

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            finish();
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        findViewById(R.id.bt_test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Dialog dialog=new Dialog(getApplicationContext());
                dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
                TextView textView=new TextView(TestActivity.this);
                textView.setText("Hello");
                dialog.setContentView(textView);
                dialog.show();

                dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        Log.d("test", "dismiss");
                    }
                });

                mHandler.sendMessageDelayed(Message.obtain(), 2000);
            }
        });
    }
}

因?yàn)檫@是特殊情況,一般是不允許的,所以需要在manifest加入:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

安卓6.0之后還要申請(qǐng)動(dòng)態(tài)權(quán)限,代碼如下:

Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 1);

在顯示窗口之后2秒,關(guān)閉當(dāng)前Activity,再看有沒(méi)有“dismiss”的日志輸出,結(jié)果是:

在顯示dialog之后,關(guān)閉Activity,dialog仍顯示,且沒(méi)有輸出“dismiss”。

如果改成傳入TestActivity .this,dialog可以隨著Activity一起關(guān)閉。

至于dialog是不是隨著application生命周期一致的問(wèn)題,我覺(jué)得這里很明顯了,就不多說(shuō)了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容