Context
在Android開發(fā)的時候,很多地方我們都會用上Context這個東西龙宏,比如我們最常用的startActivity,以前也沒怎么在意這個到底有什么用,方法要參數(shù)就直接傳過去蚣常,今天看到getApplicationContext和getApplication有點懵逼厘惦,我覺得有必要去一探究竟了,首先看看什么是Context:
Context鸵赫,翻譯為上下文衣屏,環(huán)境。多么直白又艸的翻譯辩棒,想問啥又是上下文狼忱,啥又是環(huán)境,程序還有上下文一睁。钻弄。。為了不誤人子弟者吁,來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
翻譯下:它是一個應用程序的全局環(huán)境窘俺,是Android系統(tǒng)的一個抽象類,可以通過它獲取程序的資源砚偶,比如:加載Activity批销,廣播洒闸,接收Intent信息等等。
總的來說它就像是一個程序運行的時候的環(huán)境均芽,如果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());
}
//發(fā)送廣播
public abstract void sendBroadcast(Intent intent);
//開啟服務
public abstract ComponentName startService(Intent service);
//獲取系統(tǒng)服務(ALARM_SERVICE,WINDOW_SERVICE侨颈,AUDIO_SERVICE余赢、、哈垢、)
public abstract Object getSystemService(@ServiceName @NonNull String name);
我們發(fā)現(xiàn)Context這個抽象類里面聲明了很多我們開發(fā)中常用一些方法妻柒,那有哪些類實現(xiàn)了這個抽象類呢(普及一個快捷鍵,eclipse下點擊類后按F4可以看這個類的繼承結(jié)構(gòu)耘分,AndroidStudio我設置的是eclipse的快捷鍵)举塔,結(jié)構(gòu)如下
- Context
- ContextWrapper
- TintContextWrapper
- ContextThemeWrapper
- IsolatedContext
- MutableContextWrapper
- ContextThemeWrapper
- Activity
- Service
- RenamingDelegatingContext
- Application
- BackupAgent
我們主要關注一下:ContextWrapper,Activity求泰,Service央渣,Application,先來看看Context的主要實現(xiàn)類ContextWrapper(劇透:其實這并不是真正的實現(xiàn)類):看下官方注釋,意思就是這是個簡單的實現(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;
}
//設置BaseContext渴频,同構(gòu)造方法痹屹,多了個不為空的判斷
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的,所以繼承類可以直接訪問枉氮,看了方法的實現(xiàn),我們發(fā)現(xiàn)真是simply暖庄,就是都交給mBase來做相應的處理聊替,關鍵就是構(gòu)造方法或者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調(diào)用了我們剛才說的attachBaseContext惹悄,還有給mApplication賦值。這里出現(xiàn)了另外一個我們關注的Application肩钠,到源碼看看:
//構(gòu)造方法傳了個空泣港,貌似沒什么用
public Application() {
super(null);
}
//同樣在attach中我們看到了具體的東西
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
Application和Activity的attach方法感覺都差不多暂殖,都調(diào)用了attachBaseContext(context),成為了一個Context当纱。
這里還看到了ContextImpl呛每,其實它才是Context的真正實現(xiàn)類(看名字也看出來了),可是剛才我們看Context的繼承結(jié)構(gòu)時沒看到這個類啊坡氯,原來它跟ActivityThread一樣晨横,并沒有在sdk中,所以是看不到的箫柳。這個類的具體實現(xiàn)就不仔細看了手形,再看要暈了,后面有時間再細細品味...
但是我們知道了mApplication和context是兩個不同的東西悯恍,所以嚴格意義上來說getApplicationContext和getApplication是不一樣的库糠,雖然很多時候他們返回的都是同一個對象,但是getApplication只存在于Activity或者Service中涮毫,我們要注意具體的情況瞬欧,這個我們后面再說
Application
為何物
看到這里我們發(fā)現(xiàn),Application和Activity都繼承自Context,他們都是環(huán)境窒百,只不過Application是隨著我們的應用(或者包)啟動的時候就存在的環(huán)境黍判,Activity是一個界面的環(huán)境
使用方法
既然Application是在應用一創(chuàng)建就初始化了,而且是在應用運行時一直存在的篙梢,那我們可以把它當做是一個全局變量來使用顷帖,可以保存一些共享的數(shù)據(jù),或者說做一些工具類的初始化工作渤滞。要自己來使用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的地方也能拿到怎么辦呢陶舞?可以這樣,仿照單例的做法(只是仿照P骼)肿孵,在MyApplication聲明一個靜態(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實例,注意這跟我們常見的單例不一樣疏魏,不要自作聰明去在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.
總結(jié)
Application蛉腌,Activity,Service都是繼承自Context,是應用運行時的環(huán)境烙丛,我們可以把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的時候好像是都可以寒瓦,其實不然情屹,比如我們大多數(shù)情況:
AlertDialog.Builder builder = new Builder(Activity.this);//可以
AlertDialog.Builder builder = new Builder(getApplicationContext());//內(nèi)存泄漏(可能叫超出生命周期范圍更合適)
如果把this換成getApplicationContext(),不會報錯杂腰,但是就如我們剛才所說垃你,getApplicationContext() 返回的上下文會隨著應用一直存在,這就超出了dialog應該有的生命周期喂很,不會隨著Activity而關閉
所以在使用的時候要注意具體的使用場景,避免內(nèi)存泄漏問題惜颇。
這里順便附上一個stackoverflow對于這個問題的鏈接
這篇文章還有個續(xù)集和補充
到底getApplicationContext和getApplication是不是返回同一個對象?
這里為了回復評論的質(zhì)疑少辣,現(xiàn)在嘗試傳入application的方式來顯示dialog看能不能隨著activity回收:
測試的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);
}
});
}
}
因為這是特殊情況凌摄,一般是不允許的,所以需要在manifest加入:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
安卓6.0之后還要申請動態(tài)權限漓帅,代碼如下:
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 1);
在顯示窗口之后2秒锨亏,關閉當前Activity,再看有沒有“dismiss”的日志輸出忙干,結(jié)果是:
在顯示dialog之后器予,關閉Activity,dialog仍顯示捐迫,且沒有輸出“dismiss”乾翔。
如果改成傳入TestActivity .this,dialog可以隨著Activity一起關閉施戴。
至于dialog是不是隨著application生命周期一致的問題反浓,我覺得這里很明顯了,就不多說了