1. Context的概念和作用
/**
* 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源碼對Context的描述:
- 它是應用程序環(huán)境的全局信息的接口质礼。
- 這是一個抽象類膳帕,由Android系統(tǒng)提供履腋。
- 它允許訪問特定于應用程序的資源和類,以及調(diào)用應用程序級操作矮瘟,如啟動活動,廣播和接收意圖等塑娇。
綜上:Context的第一個作用是獲取應用程序的資源和類澈侠,第二個作用是調(diào)用應用程序級操作,比如啟動活動埋酬、廣播和接收意圖等哨啃。
補充:理解以上內(nèi)容的朋友,下面的內(nèi)容可以忽略写妥。下面的內(nèi)容都是對Context概念和作用有一個更好的理解所做的補充拳球。
2. 如何理解Context是一個環(huán)境
Context是維持Android程序各組件能夠正常工作的一個核心功能類,而這個核心功能類相當于一個大的環(huán)境珍特,只有在這個環(huán)境下祝峻,Android的資源才能被獲取以及Android的各項組件才能被調(diào)用。
3. 如何理解Context環(huán)境下各項組件才能被調(diào)用
Android雖然采用java語言開發(fā)扎筒,但是Android程序并非和java程序那樣使用main()方法就可以運行莱找。Android應用模型是基于組件的應用設計模式,組件的運行一定要有一個完整的Android環(huán)境嗜桌。在這個環(huán)境下:Actvity奥溺、Service、BroadCast症脂、ContentProvider才可以正常創(chuàng)建和運行谚赎。所以這些組件并不能按照java對象創(chuàng)建方式,new一下就能創(chuàng)建實例诱篷!而是需要一個環(huán)境壶唤,而這個環(huán)境就是我們所需要了解的Context。
通俗的方式來說:Android應用程序就像一部電影棕所,Android的四大組件相當于電影的四大主角闸盔,而Context就相當于是攝像鏡頭,只有通過攝像鏡頭我們才能看到四大主角琳省。主角是被內(nèi)定好的迎吵,不能隨便new出來躲撰。而其他跑龍?zhí)椎牟⒉皇莾?nèi)定的,也就是說不是那么重要击费,他們可以被new出來拢蛋,但是他們也需要通過攝像鏡頭才可以看到,所以才有了Button mButton=new Button(Context)蔫巩。
4. Context作用的具體體現(xiàn)
有了Context這個環(huán)境谆棱,Android組件才可以正常被創(chuàng)建和調(diào)用,所以Context的作用如下:
TextView tv = new TextView(getContext());
ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);
AudioManager am = (AudioManager) getContext().
getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode);
getApplicationContext().getContentResolver().query(uri, ...);
getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;
getContext().startActivity(intent);
getContext().startService(intent);
getContext().sendBroadcast(intent);
5. Context的繼承結(jié)構(gòu)
- 從繼承結(jié)構(gòu)可以看出:Context有兩個子類:ContextWrapper和ContextImpl圆仔。
- 從名稱上可以看出:ContextWrapper是Context的封裝類垃瞧;ContextImpl是Context的實現(xiàn)類。
- ContextWrapper有三個子類:Application坪郭、Service和ContextThemeWrapper个从。
- ContextThemeWrapper是一個帶主題的封裝類,它的直接子類就是Activity歪沃。
- Context環(huán)境一共有三種類型:Activity嗦锐、Service和Application,它們承擔不同的作用绸罗,而具體Context功能由ContextImpl類實現(xiàn)意推。
綜上:
Activity、Service和Application這三種類型的Context珊蟀,在多數(shù)情況下菊值,都是可以通用的,而為了安全考慮育灸,在某些情況下腻窒,是不可以通用的,諸如啟動Activity磅崭、彈出Dialog等儿子。一個Activity的啟動必須建立在另一個Actvity的基礎(chǔ)上;Dialog也必須在Activity上面彈出砸喻,這些情況下柔逼,也只能使用Activity的Context
-
也就是說:凡是跟UI相關(guān)的,都應該使用Activity做為Context來處理割岛;其他的一些操作愉适,Service,Activity,Application等實例都可以,如下圖所示:
6. Context的數(shù)量
Context數(shù)量 = Activity數(shù)量 + Service數(shù)量 + 1(Application)
7. Application入口類作為工具類癣漆,這種做法是否可行维咸?
強力不推薦!Application入口類的職責就是為了初始化!根據(jù)面向?qū)ο蟮膯我宦氊熢瓌t癌蓖,Application不推薦作為工具類使用瞬哼。
8. getApplication()、getApplicationContext()和getBaseContext()的關(guān)系
我們在Activity中獲取分別打印這個三個方法:
MyApplication myApp = (MyApplication) getApplication();
Context appContext = getApplicationContext();
Context baseContext = getBaseContext();
打印結(jié)果:
getApplication::::com.beidou.mvptest.MyApplication@53502fac
getApplicationContext::::com.beidou.mvptest.MyApplication@53502fac
baseContext::::android.app.ContextImpl@53505ce4
結(jié)論:
- getApplication和getApplicationContext得到的是一個對象MyApplication租副。
- getBaseContext得到的是ContextImpl坐慰。
疑問:
- 第一問:getApplication和getApplicationContext得到的對象是一樣的,那為何設計兩個方法呢用僧?
- 答:兩者范圍不同讨越,后者比前者適用范圍更廣。getApplication只適用于Activity和Service永毅,而getApplicationContext還用于其他場景,比如BroadcastReceiver中人弓。
- 第二問:ContextImpl是什么東東沼死?
- 答:
①:ContextImpl是Context功能的實現(xiàn)類。Application和Service崔赌、Activity并不會去實現(xiàn)Context的功能意蛀,只是做了接口的封裝,具體的功能由ContextImpl完成健芭。
②:因為Application县钥、Activity、Service都是直接或間接繼承自ContextWrapper的慈迈,我們就直接看ContextWrapper的源碼若贮,就會發(fā)現(xiàn)所有ContextWrapper中方法的實現(xiàn)都非常統(tǒng)一,就是調(diào)用了mBase對象中對應當前方法名的方法痒留。
③:那么這個mBase對象又是什么呢谴麦?我們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數(shù)伸头,并把這個參數(shù)賦值給了mBase對象匾效。而attachBaseContext()方法其實是由系統(tǒng)來調(diào)用的,它會把ContextImpl對象作為參數(shù)傳遞到attachBaseContext()方法當中恤磷,從而賦值給mBase對象面哼,之后ContextWrapper中的所有方法其實都是通過這種委托的機制交由ContextImpl去具體實現(xiàn)的,所以說ContextImpl是上下文功能的實現(xiàn)類扫步。
④:再看一下我們剛剛打印的getBaseContext()方法魔策,在第26行。這個方法只有一行代碼锌妻,就是返回了mBase對象而已代乃,而mBase對象其實就是ContextImpl對象,因此剛才的打印結(jié)果也得到了印證。
/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context. Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
Context mBase;
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
/**
* @return the base context as set by the constructor or setBaseContext
*/
public Context getBaseContext() {
return mBase;
}
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
@Override
public Looper getMainLooper() {
return mBase.getMainLooper();
}
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
@Override
public String getPackageName() {
return mBase.getPackageName();
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
@Override
public void sendBroadcast(Intent intent) {
mBase.sendBroadcast(intent);
}
@Override
public Intent registerReceiver(
BroadcastReceiver receiver, IntentFilter filter) {
return mBase.registerReceiver(receiver, filter);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
mBase.unregisterReceiver(receiver);
}
@Override
public ComponentName startService(Intent service) {
return mBase.startService(service);
}
@Override
public boolean stopService(Intent name) {
return mBase.stopService(name);
}
@Override
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
return mBase.bindService(service, conn, flags);
}
@Override
public void unbindService(ServiceConnection conn) {
mBase.unbindService(conn);
}
@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
......
}
9. 使用Application出現(xiàn)的問題和解決辦法
9.1 問題一:在Application的構(gòu)造方法中去獲取Context實現(xiàn)類的各種方法
public class MyApplication extends Application {
public MyApplication() {
String packageName = getPackageName();
Log.d("TAG", "package name is " + packageName);
}
}
運行結(jié)果:
空指針:
java.lang.RuntimeException: Unable to instantiate application
com.example.test.MyApplication: java.lang.NullPointerException
修改代碼如下:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
String packageName = getPackageName();
Log.d("TAG", "package name is " + packageName);
}
}
運行結(jié)果正常搁吓!發(fā)生了什么事情呢原茅?回顧ContextWrapper源碼,ContextWrapper中有一個attachBaseContext()方法堕仔,這個方法會將傳入的一個Context參數(shù)賦值給mBase對象擂橘,之后mBase對象就有值了。而我們又知道摩骨,所有Context的方法都是調(diào)用這個mBase對象的同名方法通贞,那么也就是說如果在mBase對象還沒賦值的情況下就去調(diào)用Context中的任何一個方法時,就會出現(xiàn)空指針異常恼五。Application中方法的執(zhí)行順序:
在onCreate()方法中去初始化全局的變量數(shù)據(jù)是一種比較推薦的做法昌罩,假如你想把初始化提前到極致,也可以重寫attachBaseContext()方法:
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
// 在這里調(diào)用Context的方法會崩潰
super.attachBaseContext(base);
// 在這里調(diào)用Context的方法就沒問題
}
}
9.2 問題二:把Application當做工具類使用時獲取實例采用new的方式
public class MyApplication extends Application {
private static MyApplication app;
public static MyApplication getInstance() {
if (app == null) {
app = new MyApplication();
}
return app;
}
}
我們已經(jīng)在上文指出了:new MyApplication實例的方式灾馒,得到的對象并不具備Context的能力茎用,如果進行Context操作就會報空指針,因為它只是一個java對象睬罗。而我們知道Application本身就是一個單例了轨功,所以我們直接返回本身即可,不用再去new對象獲取實例容达,否則弄巧成拙古涧。
public class MyApplication extends Application {
private static MyApplication app;
public static MyApplication getInstance() {
return app;
}
@Override
public void onCreate() {
super.onCreate();
app = this;
}
}
10. Context亂用導致的內(nèi)存泄漏的問題和解決辦法
public class CustomManager
{
private static CustomManager sInstance;
private Context mContext;
private CustomManager(Context context)
{
this.mContext = context;
}
public static synchronized CustomManager getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new CustomManager(context);
}
return sInstance;
}
//some methods
private void someOtherMethodNeedContext()
{
}
}
假如:sInstance作為靜態(tài)對象,其生命周期要長于普通的對象花盐,其中也包含Activity羡滑,假如Activity A去getInstance獲得sInstance對象,傳入this算芯,常駐內(nèi)存的CustomManager保存了你傳入的ActivityA 對象(也就是Context)啄栓,并一直持有,即使Activity(Context)被銷毀掉也祠,但因為它的引用還存在于一個CustomManager中昙楚,就不可能被GC掉,這樣就導致了內(nèi)存泄漏诈嘿。也就是所以解決辦法是:
public static synchronized CustomManager getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new CustomManager(context.getApplicationContext());
}
return sInstance;
}
11. 正確使用Context
一般Context造成的內(nèi)存泄漏堪旧,幾乎都是當Context銷毀的時候,卻因為被引用導致銷毀失敗奖亚,而Application的Context對象可以理解為隨著進程存在的淳梦,所以我們總結(jié)出使用Context的正確姿勢:
當Application的Context能搞定的情況下,并且生命周期長的對象昔字,優(yōu)先使用Application的Context爆袍。
不要讓生命周期長于Activity的對象持有到Activity的引用首繁。
盡量不要在Activity中使用非靜態(tài)內(nèi)部類,因為非靜態(tài)內(nèi)部類會隱式持有外部類實例的引用陨囊,如果使用靜態(tài)內(nèi)部類弦疮,將外部實例引用作為弱引用持有。