android context
韓夢飛沙 韓亞飛 [email protected] yue31313 han_meng_fei_sha
context 就是 上下文環境,
常見的 比如 應用環境, activity,服務,
Context的兩個子類分工明確,其中ContextImpl是Context的具體實現類,ContextWrapper是Context的包裝類。Activity,Application,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會創建ContextImpl對象,由ContextImpl實現Context中的方法。
一般Context造成的內存泄漏,幾乎都是當Context銷毀的時候,卻因為被引用導致銷毀失敗,而Application的Context對象可以理解為隨著進程存在的,所以我們總結出使用Context的正確姿勢:
1:當Application的Context能搞定的情況下,並且生命周期長的對象,優先使用Application的Context。
2:不要讓生命周期長於Activity的對象持有到Activity的引用。
3:盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作為弱引用持有。
==============
大家在編寫一些類時,例如工具類,可能會編寫成單例的方式,這些工具類大多需要去訪問資源,也就說需要Context的參與。
在這樣的情況下,就需要註意Context的引用問題。
例如以下的寫法:
[java] view plain copy
- package com.mooc.shader.roundimageview;
- import android.content.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()
- {
- }
- }
對於上述的單例,大家應該都不陌生(請別計較getInstance的效率問題),內部保持了一個Context的引用;
這麽寫是沒有問題的,問題在於,這個Context哪來的我們不能確定,很大的可能性,你在某個Activity裏面為了方便,直接傳了個this;這樣問題就來了,我們的這個類中的sInstance是一個static且強引用的,在其內部引用了一個Activity作為Context,也就是說,我們的這個Activity只要我們的項目活著,就沒有辦法進行內存回收。而我們的Activity的生命周期肯定沒這麽長,所以造成了內存泄漏。
那麽,我們如何才能避免這樣的問題呢?
有人會說,我們可以軟引用,嗯,軟引用,假如被回收了,你不怕NullPointException麽。
把上述代碼做下修改:
[java] view plain copy
- public static synchronized CustomManager getInstance(Context context)
- {
- if (sInstance == null)
- {
- sInstance = new CustomManager(context.getApplicationContext());
- }
- return sInstance;
- }
這樣,我們就解決了內存泄漏的問題,因為我們引用的是一個ApplicationContext,它的生命周期和我們的單例對象一致。
這樣的話,可能有人會說,早說嘛,那我們以後都這麽用不就行了,很遺憾的說,不行。上面我們已經說過,Context和Application Context的區別是很大的,也就是說,他們的應用場景(你也可以認為是能力)是不同的,並非所有Activity為Context的場景,Application Context都能搞定。
下面就開始介紹各種Context的應用場景。
Context的應用場景
大家註意看到有一些NO上添加了一些數字,其實這些從能力上來說是YES,但是為什麽說是NO呢?下面一個一個解釋:
數字1:啟動Activity在這些類中是可以的,但是需要創建一個新的task。一般情況不推薦。
數字2:在這些類中去layout inflate是合法的,但是會使用系統默認的主題樣式,如果你自定義了某些樣式可能不會被使用。
數字3:在receiver為null時允許,在4.2或以上的版本中,用於獲取黏性廣播的當前值。(可以無視)
註:ContentProvider、BroadcastReceiver之所以在上述表格中,是因為在其內部方法中都有一個context用於使用。
好了,這裏我們看下表格,重點看Activity和Application,可以看到,和UI相關的方法基本都不建議或者不可使用Application,並且,前三個操作基本不可能在Application中出現。實際上,只要把握住一點,凡是跟UI相關的,都應該使用Activity做為Context來處理;其他的一些操作,Service,Activity,Application等實例都可以,當然了,註意Context引用的持有,防止內存泄漏。
大家在以後的使用過程中,能夠稍微考慮下,這裏使用Activity合適嗎?會不會造成內存泄漏?這裏傳入Application work嗎?
=========
Context類本身是一個純abstract類,它有兩個具體的實現子類:ContextImpl和ContextWrapper。其中ContextWrapper類,如其名所言,這只是一個包裝而已,ContextWrapper構造函數中必須包含一個真正的Context引用,同時ContextWrapper中提供了attachBaseContext()用於給ContextWrapper對象中指定真正的Context對象,調用ContextWrapper的方法都會被轉向其所包含的真正的Context對象。ContextThemeWrapper類,如其名所言,其內部包含了與主題(Theme)相關的接口,這裏所說的主題就是指在AndroidManifest.xml中通過android:theme為Application元素或者Activity元素指定的主題。當然,只有Activity才需要主題,Service是不需要主題的,因為Service是沒有界面的後臺場景,所以Service直接繼承於ContextWrapper,Application同理。而ContextImpl類則真正實現了Context中的所以函數,應用程序中所調用的各種Context類的方法,其實現均來自於該類。一句話總結:Context的兩個子類分工明確,其中ContextImpl是Context的具體實現類,ContextWrapper是Context的包裝類。Activity,Application,Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會創建ContextImpl對象,由ContextImpl實現Context中的方法。
Context作用域.png ======== getApplication()和getApplicationContext() Application本身就是一個Context,所以這裏獲取getApplicationContext()得到的結果就是Application本身的實例。那麽問題來了,既然這兩個方法得到的結果都是相同的,那麽Android為什麽要提供兩個功能重復的方法呢?實際上這兩個方法在作用域上有比較大的區別。getApplication()方法的語義性非常強,一看就知道是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那麽也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的實例,這時就可以借助getApplicationContext()方法了。
====
Context引起的內存泄露
但Context並不能隨便亂用,用的不好有可能會引起內存泄露的問題,下面就示例兩種錯誤的引用方式。
錯誤的單例模式
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
這是一個非線程安全的單例模式,instance作為靜態對象,其生命周期要長於普通的對象,其中也包含Activity,假如Activity A去getInstance獲得instance對象,傳入this,常駐內存的Singleton保存了你傳入的Activity A對象,並一直持有,即使Activity被銷毀掉,但因為它的引用還存在於一個Singleton中,就不可能被GC掉,這樣就導致了內存泄漏。
View持有Activity引用
public class MainActivity extends Activity {
private static Drawable mDrawable;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
iv.setImageDrawable(mDrawable);
}
}
有一個靜態的Drawable對象當ImageView設置這個Drawable時,ImageView保存了mDrawable的引用,而ImageView傳入的this是MainActivity的mContext,因為被static修飾的mDrawable是常駐內存的,MainActivity是它的間接引用,MainActivity被銷毀時,也不能被GC掉,所以造成內存泄漏。
正確使用Context
一般Context造成的內存泄漏,幾乎都是當Context銷毀的時候,卻因為被引用導致銷毀失敗,而Application的Context對象可以理解為隨著進程存在的,所以我們總結出使用Context的正確姿勢:
1:當Application的Context能搞定的情況下,並且生命周期長的對象,優先使用Application的Context。
2:不要讓生命周期長於Activity的對象持有到Activity的引用。
3:盡量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類實例的引用,如果使用靜態內部類,將外部實例引用作為弱引用持有。
=======
android context