image.png
一、源碼角度解析Context
從系統的角度來了解:
Context
是一個場景,代表與作業系統的互動的一種過程。
Context
是一個抽象類;
Activity
、
Service
Application
是它的子類;
二、Context 應用場景
數字1:啟動Activity在這些類中是可以的,但是需要建立一個新的task。一般情況不推薦。
數字2:在這些類中去layout inflate是合法的,但是會使用系統預設的主題樣式,如果你自定義了某些樣式可能不會被使用。
數字3:在receiver為null時允許,在4.2或以上的版本中,用于擷取黏性廣播的目前值。(可以無視)
注:ContentProvider、BroadcastReceiver之是以在上述表格中,是因為在其内部方法中都有一個context用于使用。
預設的Toast實際上使用ApplicationContext也可以,因為總有時候在異步線程中做了一些土司操作,這種情況下在
Activity
關閉時候,很容易造成
context
為空的情況,是以所有的土司都采用
ApplicationContext
。
四、Fragment 中 Context 的擷取
在
Fragment
的生命周期中,在生命周期處于
onAttach()
和
onDetach()
之間的時候
getActivity()
方法才不會傳回
null
。是以我們可以在
fragment
初始化的時候建立
Context
引用。在fragment銷毀的時候銷毀引用。代碼如下:
private Context mContext;
Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;//mContext 是成員變量,上下文引用
}
Override
public void onDetach() {
super.onDetach();
mContext = null;
}
注意:Activity 中有的
onAttach
有兩個方法
void onAttach(Activity activity);
void onAttach(Context context);
五、Context 引發的記憶體洩露解決
- 不要讓生命周期長于Activity的對象持有到Activity的引用
- 盡量使用Application的Context而不是Activity的Context。
- 盡量不要在Activity中使用非靜态内部類,因為非靜态内部類會隐式持有外部類執行個體的引用(具體可以檢視細話Java:”失效”的private修飾符了解)。如果使用靜态内部類,将外部執行個體引用作為弱引用持有。
解決的方法就是不持有Activity的引用,而是持有Application的Context引用。擷取方式檢視
ContextHolder
方式。下面會介紹。
-
單例模式用application的context。
如果我們在Activity A中或者其他地方使用Foo.getInstance()時,我們總是會順手寫一個『this』或者『mContext』(這個變量也是指向this)。 試想一下,目前我們所用的Foo是單例,意味着被初始化後會一直存在與記憶體中,以友善我們以後調用的時候不會在此次建立Foo對象。但Foo中的 『mContext』變量一直都會持有Activity A中的『Context』,導緻Activity A即使執行了onDestroy方法,也不能夠将自己銷毀。但『applicationContext』就不同了,它一直伴随着我們應用存在(中途也可能 會被銷毀,但也會自動reCreate),是以就不用擔心Foo中的『mContext』會持有某Activity的引用,讓其無法銷毀。
實際上,隻要把握住一點,凡是跟UI相關的,都應該使用 Activity做為Context來處理(吐司除外);其他的一些操作,Service,Activity,Application等執行個體都可以,當然了,注意 Context引用的持有,防止記憶體洩漏。
六、擷取Context的四種方式方式
-
,傳回目前View對象的Context對象,通常是目前正在展示的Activity對象。View.getContext
-
,擷取目前Activity所在的(應用)程序的Context對象,通常我們使用Context對象時,要優先考慮這個全局的程序Context。Activity.getApplicationContext
-
:用來擷取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發中使用并不多,也不建議使用。ContextWrapper.getBaseContext()
- Activity.this 傳回目前的Activity執行個體,如果是UI控件需要使用Activity作為Context對象,但是預設的Toast實際上使用ApplicationContext也可以。
七、第三方庫通用的擷取Context方式
首先我們構造一個存儲Context的類ContextHolder,在Application初始化時将Application傳入ContextHolder,這個方法在很多第三方庫都能見到類似的處理。
public class ContextHolder {
static Context ApplicationContext;
public static void initial(Context context) {
ApplicationContext = context;
}
public static Context getContext() {
return ApplicationContext;
}
}
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
ContextHolder.initial(this);
}
}
這樣我們就能在任意位置調用ContextHolder.getContext()來擷取應用Context。
那麼有沒有可能不需要任何初始化操作就能完成這個需求呢?筆者做了一些嘗試。
由于實際上擷取應用Context也就是擷取目前應用執行個體,經筆者研究下面2種方法都可以通過反射直接擷取目前應用。
try {
Application application = (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null, (Object[]) null);
} catch (Exception e) {
e.printStackTrace();
}
try {
Application application = (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null, (Object[]) null);
} catch (Exception e) {
e.printStackTrace();
}
經測試,即使應用處于背景仍能正确擷取到調用此方法的Application。
參考: http://blog.csdn.net/lmj623565791/article/details/40481055 http://www.jianshu.com/p/9d75e328f1de http://www.jianshu.com/p/808b9d92d6cd http://blog.nimbledroid.com/2016/05/23/memory-leaks-zh.html