前言
Context對象在我們的項(xiàng)目中實(shí)在是太常見了账千。啟動(dòng)Activity威恼、Service传蹈、發(fā)送一個(gè)Broadcast央拖,作為獲取各種系統(tǒng)Resources的參數(shù),Layout Inflation的參數(shù)垮卓,show a Dialog的參數(shù)等等垫桂,Context的使用不當(dāng),是可能造成內(nèi)存泄漏的粟按,當(dāng)你的工程代碼已經(jīng)達(dá)到十幾萬行甚至幾十萬行時(shí)诬滩,Context對象就對內(nèi)存泄漏造成非常可觀的影響了灭将,所以我們應(yīng)該對Context對象的使用疼鸟,做到心中有數(shù)
什么是Context
- Context是為一個(gè)Android程序提供各種功能、資源庙曙、服務(wù)的一個(gè)環(huán)境空镜,Context的資源系統(tǒng)中只有一套,因?yàn)樗淖宇悾ˋpplication捌朴,Activity吴攒,Service)對這同一塊資源處理方式的不同,讓Context對象在功能上表現(xiàn)出各自之間的差異
Context對象之間的差異
重寫Context
public class LauncherApplication extends Application {
private static Context context;
@Override
public void onCreate() {
// 獲取Application中的Context
context = getApplicationContext();
}
/**
* 獲得Application的Context
*
* @return Context
*/
public static Context getContext() {
return context;
}
}
清單文件配置
<application
android:name=".LauncherApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
- 配置完 android:name這個(gè)屬性砂蔽,以后應(yīng)用使用的就是你自定義的Application
在界面中show a Dialog
public class MainActivity extends Activity {
private Context application;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
application = LauncherApplication.getContext();
}
public void ShowDialog (View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(application);
builder.setTitle("ContextDemo");
builder.setMessage("Context參數(shù)為applicationContext");
builder.create();
builder.show();
}
}
-
點(diǎn)擊后閃退閃退信息
04-21 04:43:21.590 2104-2104/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.cat14.demo, PID: 2104 java.lang.IllegalStateException: Could not execute method of the activity at android.view.View$1.onClick(View.java:3823) at android.view.View.performClick(View.java:4438) at android.view.View$PerformClick.run(View.java:18422) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at android.view.View$1.onClick(View.java:3818) at android.view.View.performClick(View.java:4438) at android.view.View$PerformClick.run(View.java:18422) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method) Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application at android.view.ViewRootImpl.setView(ViewRootImpl.java:540) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:259) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69) at android.app.Dialog.show(Dialog.java:286) at android.app.AlertDialog$Builder.show(AlertDialog.java:951) at com.cat14.demo.MainActivity.ShowDialog(MainActivity.java:26) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at android.view.View$1.onClick(View.java:3818) at android.view.View.performClick(View.java:4438) at android.view.View$PerformClick.run(View.java:18422) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
lvik.system.NativeStart.main(thod)
- 可見洼怔,當(dāng)使用Application的Context時(shí),是無法彈出一個(gè)Dialog的左驾,因?yàn)镈ialog作為一個(gè)View镣隶,依附在Activity上,并且與Theme相關(guān)诡右,當(dāng)傳入?yún)?shù)為Activity的Context時(shí)矾缓,崩潰就解決了
-
Context對象之間的使用上的差異
Context相關(guān)的內(nèi)存泄漏問題
錯(cuò)誤的單例模式
public class Singleton {
private static Single instance;
private Context context;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(mContext.getApplicationContext());
}
return instance;
}
}
- 這是一個(gè)非線程安全的單例模式
- Activity A去getInstance獲得instance對象,傳入this
- 常駐內(nèi)存的Singleton保存了你傳入的Activity A對象稻爬,并一直持有
- 即時(shí)Activity被銷毀掉,但因?yàn)樗囊眠€存在于一個(gè)Singleton中蜕依,就不可能被GC掉桅锄,這樣就導(dǎo)致了內(nèi)存泄漏
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);
}
}
- 有一個(gè)靜態(tài)的Drawable對象
- 當(dāng)ImageView設(shè)置這個(gè)Drawable時(shí),ImageView保存了mDrawable的引用
- 而ImageView傳入的this样眠,是MainActivity的mContext
- 因?yàn)楸籹tatic修飾的mDrawable是常駐內(nèi)存的友瘤,MainActivity是它的間接引用
- MainActivity被銷毀時(shí),也不能被GC掉檐束,所以造成內(nèi)存泄漏
正確使用Context
- 一般Context造成的內(nèi)存泄漏辫秧,幾乎都是當(dāng)Context銷毀的時(shí)候,卻因?yàn)楸灰脤?dǎo)致銷毀失敗
- 而Application的Context對象可以理解為隨著進(jìn)程存在的被丧,所以當(dāng)Application的Context能搞定的情況下盟戏,并且生命周期長的對象绪妹,優(yōu)先使用Application的Context
- 內(nèi)存檢測的自動(dòng)化工具,LeakCanary