對 Context 的理解
1. Context的作用:
Context 是應用組件的上下文,有了 Context 才可以方便地訪問系統(tǒng)資源,調用系統(tǒng)服務。
public abstract class Context {
...
public abstract Resources getResources();
public abstract @Nullable Object getSystemService(String name);
public abstract void startActivity(@RequiresPermission Intent intent);
public abstract void sendBroadcast(@RequiresPermission Intent intent);
...
}
2. Context 的繼承關系:
Context 下有兩個子類,ContextWrapper 是上下文功能的封裝類,而 ContextImpl 則是上下文功能的實現(xiàn)類。
其中 ContextWrapper 又有三個直接的子類,ContextThemeWrapper、Service 和 Application,其中 ContextThemeWrapper 是一個帶主題的封裝類,它有一個直接子類就是 Activity,所以 Activity 和 Service 以及 Application 的 Context 是不一樣的,只有 Activity 需要主題,Service 不需要主題。
Context 一共有三種類型,分別是 Application、Activity 和 Service,這三個類雖然分別承擔著不同的作用,但是它們都屬于 Context 的一種,而它們具體 Context 的功能則是由 ContextImpl 類去實現(xiàn)的,因此在絕大多數(shù)場景下,Activity、Service 和 Application 這三種類型的 Context 都是可以通用的,不過有幾種場景比較特殊,比如啟動 Activity,還有彈出 Dialog,出于安全原因的考慮,Android 是不允許 Activity 或 Dialog 憑空出現(xiàn)的,一個 Activity 的啟動必須要建立在另一個 Activity 的基礎之上,也就是以此形成的返回棧,而 Dialog 則必須在一個 Activity 上面彈出(除非是 System Alert 類型的 Dialog),因此在這種場景下,我們只能使用 Activity 類型的 Context,否則將會出錯。
3. Context的初始化流程
(1)Application 的初始化流程:
ApplicationContext 是跟隨 Application 一起初始化的,而 Application 又是跟隨應用進程的啟動初始化的,過程是創(chuàng)建應用進程,然后執(zhí)行一個 java 類的入口函數(shù),就是 ActivityThread 的 main 函數(shù),這個函數(shù)會通知 AMS,應用進程啟動了,然后 AMS 就會通知創(chuàng)建 Application,調用的是 handleBindApplication() 函數(shù),這里首先要創(chuàng)建一個 Application 對象,然后執(zhí)行它的 onCreate() 回調,application 對象的創(chuàng)建過程是調用 makeApplication() 函數(shù),先通過 createAppContext() 函數(shù)創(chuàng)建一個 Context,然后調用 newApplication() 函數(shù)并將該 context 傳進去,生成 application 對象。
private void handleBindApplication(AppBindData data) {
...
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInstrumentation.callApplicationOnCreate(app);
...
}
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
...
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
...
return app;
}
在 newApplication() 函數(shù)中先用 ClassLoader 加載 Application 類,然后調用 newInstance() 函數(shù)得到 application 對象,并通過 attachBaseContext() 函數(shù)給該對象賦予上下文。
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
另外 attach() 調用的是 attachBaseContext() 函數(shù)。
其中 application 是繼承 ContextWrapper的,而 ContextWrapper 又是繼承 Context,在 attachBaseContext 函數(shù)中就是設置了 mBase 對象。
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
public Context getBaseContext() {
return mBase;
}
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
···
}
Application 初始化的結論:
繼承關系,Application <- ContextWrapper <- Context
調用順序,<init> -> attachBaseContext -> onCreate
ContextWrapper 里包含一個 Context,調用都委托給他了。
(2)Activity 的初始化流程:
Activity 啟動調用的就是 performLaunchActivity() 函數(shù),首先就是 newActivity 創(chuàng)建 Activity 對象,也是通過 ClassLoader 加載類然后調用 newInstance() 創(chuàng)建 activity 對象。接著是調用 makeApplication() 函數(shù)返回 application 對象,然后調用 createBaseContextForActivity() 函數(shù)創(chuàng)建 Context,并將 context 賦予 activity,最后調用 onCreate() 生命周期。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
ContextImpl appContext = createBaseContextForActivity(r);
activity.attach();
activity.onCreate();
...
return activity;
}
Activity 繼承自 ContextThemeWrapper,這點和 Application 不一樣。
public class Activity extends ContextThemeWrapper{...}
Activity的結論:
繼承關系,Application <- ContextThemeWrapper <- ContextWrapper <- Context
調用順序,<init> -> attachBaseContext -> onCreate
(3)Service 的初始化流程:
Service 的創(chuàng)建也是通過 ClassLoader 加載類,然后調用 newInstance() 創(chuàng)建,然后調用 createAppContext() 函數(shù)創(chuàng)建 Context 對象,接著調用 makeApplication() 函數(shù)準備 application,然后調用 attach 將 context 和 application 賦予 service,然后調用 onCreate() 方法執(zhí)行生命周期。
public abstract class Service extends ContextWrapper{...}
private void handleCreateService(CreateServiceData data) {
...
Service service = null;
service = (Service) cl.loadClass(data.info.name).newInstance();
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
service.onCreate();
...
}
其中 createAppContext() 函數(shù)中 new ContextImpl() 創(chuàng)建了 ContextImpl 對象。
attach() 函數(shù)調用的是 attachBaseContext(context) 函數(shù)。
4. 與 Context 相關的問題
(1)應用里面有多少個 Context? 不同的 Context 之間有什么區(qū)別?
Context 的數(shù)量 = activity 數(shù)量 + service 數(shù)量 + application 數(shù)量
因為 Activity 要顯示 UI,繼承的是 ContextThemeWrapper,而 Service 和 Application 繼承自 ContextWrapper。
(2)Activity 里的 “this” 和 getBaseContext 有什么區(qū)別?
Activity 最終繼承的是 Context,所以 this 是 Activity 自己。而 getBaseContext 返回的是 ContextWrapper 里面的 mBase。
(3)getApplication 和 getApplicationContext 有什么區(qū)別?
返回的都是 application 對象,但是 getApplication 是 Activity 和 Service 特有的。
(4)應用組件的構造、onCreate()、attachBaseContext() 調用順序?
應用組件的構造函數(shù) -> attachBaseContext() -> onCreate()
5. Context 會引起的相關問題
(1)Context 引起的內存泄漏
Context 用的不好有可能會引起內存泄漏的問題。
示例1:
錯誤的單例模式:
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 作為靜態(tài)對象,其生命周期要長于普通的對象,其中也包含 Activity A 去 getInstance 獲得 instance 對象,傳入 this,常駐內存的 Singleton 保存了傳入的 Activity A 對象,并一直持有,即使 Activity 被銷毀掉,但因為它的引用還存在于一個 Singleton 中,就不可能被 GC 掉,這樣就導致了內存泄漏。
示例2:
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);
}
}
有一個靜態(tài)的 Drawable 對象當 ImageView 設置這個 Drawable 時,ImageView 保存了 mDrawable 的引用,而 ImageView 傳入的 this 是 MainActivity 的 mContext,因為被 static 修飾的 mDrawable 是常駐內存的,MainActivity 是它的間接引用,MainActivity 被銷毀時,也不能被 GC 掉,所以造成內存泄漏。
(2)正確使用 Context
一般 Context 造成的內存泄漏,幾乎都是當 Context 銷毀的時候,卻因為被引用導致銷毀失敗,而 Application 的 Context 對象可以理解為隨著進程存在的,所以我們總結出使用 Context 的正確用法:
- 當 Application 的 Context 能滿足的情況下,并且生命周期長的對象,優(yōu)先使用 Application 的 Context。
- 不要讓生命周期長于 Activity 的對象持有到 Activity 的引用。
- 盡量不要在 Activity 中使用非晶態(tài)內部類,因為非靜態(tài)內部類會隱式持有外部類實例的引用,如果使用靜態(tài)內部類,將外部實例引用作為弱引用持有。