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的描述:
- 它是應用程序環境的全局信息的接口。
- 這是一個抽象類,由Android系統提供。
- 它允許訪問特定於應用程序的資源和類,以及調用應用程序級操作,如啓動活動,廣播和接收意圖等。
綜上:Context的第一個作用是獲取應用程序的資源和類,第二個作用是調用應用程序級操作,比如啓動活動、廣播和接收意圖等。
補充:理解以上內容的朋友,下面的內容可以忽略。下面的內容都是對Context概念和作用有一個更好的理解所做的補充。
2. 如何理解Context是一個環境
Context是維持Android程序各組件能夠正常工作的一個核心功能類,而這個核心功能類相當於一個大的環境,只有在這個環境下,Android的資源才能被獲取以及Android的各項組件才能被調用。
3. 如何理解Context環境下各項組件才能被調用
Android雖然採用java語言開發,但是Android程序並非和java程序那樣使用main()方法就可以運行。Android應用模型是基於組件的應用設計模式,組件的運行一定要有一個完整的Android環境。在這個環境下:Actvity、Service、BroadCast、ContentProvider纔可以正常創建和運行。所以這些組件並不能按照java對象創建方式,new一下就能創建實例!而是需要一個環境,而這個環境就是我們所需要了解的Context。
通俗的方式來說:Android應用程序就像一部電影,Android的四大組件相當於電影的四大主角,而Context就相當於是攝像鏡頭,只有通過攝像鏡頭我們才能看到四大主角。主角是被內定好的,不能隨便new出來。而其他跑龍套的並不是內定的,也就是說不是那麼重要,他們可以被new出來,但是他們也需要通過攝像鏡頭纔可以看到,所以纔有了Button mButton=new Button(Context)。
4. Context作用的具體體現
有了Context這個環境,Android組件纔可以正常被創建和調用,所以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的繼承結構
- 從繼承結構可以看出:Context有兩個子類:ContextWrapper和ContextImpl。
- 從名稱上可以看出:ContextWrapper是Context的封裝類;ContextImpl是Context的實現類。
- ContextWrapper有三個子類:Application、Service和ContextThemeWrapper。
- ContextThemeWrapper是一個帶主題的封裝類,它的直接子類就是Activity。
- Context環境一共有三種類型:Activity、Service和Application,它們承擔不同的作用,而具體Context功能由ContextImpl類實現。
綜上:
Activity、Service和Application這三種類型的Context,在多數情況下,都是可以通用的,而爲了安全考慮,在某些情況下,是不可以通用的,諸如啓動Activity、彈出Dialog等。一個Activity的啓動必須建立在另一個Actvity的基礎上;Dialog也必須在Activity上面彈出,這些情況下,也只能使用Activity的Context
也就是說:凡是跟UI相關的,都應該使用Activity做爲Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,如下圖所示:
6. Context的數量
Context數量 = Activity數量 + Service數量 + 1(Application)
7. Application入口類作爲工具類,這種做法是否可行?
強力不推薦!Application入口類的職責就是爲了初始化!根據面向對象的單一職責原則,Application不推薦作爲工具類使用。
8. getApplication()、getApplicationContext()和getBaseContext()的關係
我們在Activity中獲取分別打印這個三個方法:
MyApplication myApp = (MyApplication) getApplication();
Context appContext = getApplicationContext();
Context baseContext = getBaseContext();
打印結果:
getApplication::::com.beidou.mvptest.MyApplication@53502fac
getApplicationContext::::com.beidou.mvptest.MyApplication@53502fac
baseContext::::android.app.ContextImpl@53505ce4
結論:
- getApplication和getApplicationContext得到的是一個對象MyApplication。
- getBaseContext得到的是ContextImpl。
疑問:
- 第一問:getApplication和getApplicationContext得到的對象是一樣的,那爲何設計兩個方法呢?
- 答:兩者範圍不同,後者比前者適用範圍更廣。getApplication只適用於Activity和Service,而getApplicationContext還用於其他場景,比如BroadcastReceiver中。
- 第二問:ContextImpl是什麼東東?
- 答:
①:ContextImpl是Context功能的實現類。Application和Service、Activity並不會去實現Context的功能,只是做了接口的封裝,具體的功能由ContextImpl完成。
②:因爲Application、Activity、Service都是直接或間接繼承自ContextWrapper的,我們就直接看ContextWrapper的源碼,就會發現所有ContextWrapper中方法的實現都非常統一,就是調用了mBase對象中對應當前方法名的方法。
③:那麼這個mBase對象又是什麼呢?我們來看第16行的attachBaseContext()方法,這個方法中傳入了一個base參數,並把這個參數賦值給了mBase對象。而attachBaseContext()方法其實是由系統來調用的,它會把ContextImpl對象作爲參數傳遞到attachBaseContext()方法當中,從而賦值給mBase對象,之後ContextWrapper中的所有方法其實都是通過這種委託的機制交由ContextImpl去具體實現的,所以說ContextImpl是上下文功能的實現類。
④:再看一下我們剛剛打印的getBaseContext()方法,在第26行。這個方法只有一行代碼,就是返回了mBase對象而已,而mBase對象其實就是ContextImpl對象,因此剛纔的打印結果也得到了印證。
/**
* 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出現的問題和解決辦法
9.1 問題一:在Application的構造方法中去獲取Context實現類的各種方法
public class MyApplication extends Application {
public MyApplication() {
String packageName = getPackageName();
Log.d("TAG", "package name is " + packageName);
}
}
運行結果:
空指針:
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);
}
}
運行結果正常!發生了什麼事情呢?回顧ContextWrapper源碼,ContextWrapper中有一個attachBaseContext()方法,這個方法會將傳入的一個Context參數賦值給mBase對象,之後mBase對象就有值了。而我們又知道,所有Context的方法都是調用這個mBase對象的同名方法,那麼也就是說如果在mBase對象還沒賦值的情況下就去調用Context中的任何一個方法時,就會出現空指針異常。Application中方法的執行順序:
在onCreate()方法中去初始化全局的變量數據是一種比較推薦的做法,假如你想把初始化提前到極致,也可以重寫attachBaseContext()方法:
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
// 在這裏調用Context的方法會崩潰
super.attachBaseContext(base);
// 在這裏調用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;
}
}
我們已經在上文指出了: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亂用導致的內存泄漏的問題和解決辦法
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作爲靜態對象,其生命週期要長於普通的對象,其中也包含Activity,假如Activity A去getInstance獲得sInstance對象,傳入this,常駐內存的CustomManager保存了你傳入的ActivityA 對象(也就是Context),並一直持有,即使Activity(Context)被銷燬掉,但因爲它的引用還存在於一個CustomManager中,就不可能被GC掉,這樣就導致了內存泄漏。也就是所以解決辦法是:
public static synchronized CustomManager getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new CustomManager(context.getApplicationContext());
}
return sInstance;
}
11. 正確使用Context
一般Context造成的內存泄漏,幾乎都是當Context銷燬的時候,卻因爲被引用導致銷燬失敗,而Application的Context對象可以理解爲隨着進程存在的,所以我們總結出使用Context的正確姿勢:
當Application的Context能搞定的情況下,並且生命週期長的對象,優先使用Application的Context。
不要讓生命週期長於Activity的對象持有到Activity的引用。
儘量不要在Activity中使用非靜態內部類,因爲非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作爲弱引用持有。