Android 複習筆記目錄java
本文永久更新地址: xiaozhuanlan.com/topic/03675…android
文章開頭,先來看一段代碼:編程
public class ContextActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_context);
Log.e("context", "getApplication in Activity: " + getApplication().getClass().getName());
Log.e("context", "getApplicationContext in Activity: " + getApplicationContext().getClass().getName());
Log.e("context", "getBaseContext in Activity: " + getBaseContext().getClass().getName());
startService(new Intent(this,ContextService.class));
}
}
複製代碼
你能準確的說出這三行打印語句的執行結果嗎?若是不能,你須要認真閱讀這篇文章。設計模式
Context
是一個抽象類。既然是抽象類,那麼它就表明了一類具體對象的通用特徵。先來看一下 Context 的類圖:微信
其中看到了咱們很熟悉的 Activity、Service、Application,這些都是 Context
的具體實現類,也就是說 Context 抽象了這些類的通用特徵和功能:markdown
getResources()
,getAssets()
等這些與系統環境息息相關的功能都是由 Context 提供的,因此通常將其稱爲 上下文,它其實就是對當前運行環境的具體描述,爲系統組件的正常運行提供必要的環境和資源。app
在上面的類圖中,可能有兩個讀者比較陌生的類,ContextWraaper 和 ContextImpl。socket
ContextImpl
很好理解,它就是 Context 的具體實現類。Context 類中的全部抽象方法都是在 ContextImpl 中實現的。編程語言
class ContextImpl extends Context {
......
@Override
public AssetManager getAssets() {
return getResources().getAssets();
}
@Override
public Resources getResources() {
return mResources;
}
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
@Override
public Looper getMainLooper() {
return mMainThread.getLooper();
}
......
}
複製代碼
ContextWraaper 其實也很簡單,直接看它的實現代碼:ide
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/** * 在這個方法中給 mBase 賦值 */
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();
}
......
}
複製代碼
這是一個典型的 裝飾者模式,也叫作 修飾模式,一下來自維基百科:
修飾模式,是面向對象編程領域中,一種動態地往一個類中添加新的行爲的設計模式。就功能而言,修飾模式相比生成子類更爲靈活,這樣能夠給某個對象而不是整個類添加一些功能。
經過使用修飾模式,能夠在運行時擴充一個類的功能。原理是:增長一個修飾類包裹原來的類,包裹的方式通常是經過在將原來的對象做爲修飾類的構造函數的參數。裝飾類實現新的功能,可是,在不須要用到新功能的地方,它能夠直接調用原來的類中的方法。修飾類必須和原來的類有相同的接口。
修飾模式是類繼承的另一種選擇。類繼承在編譯時候增長行爲,而裝飾模式是在運行時增長行爲。
當有幾個相互獨立的功能須要擴充時,這個區別就變得很重要。在有些面向對象的編程語言中,類不能在運行時被建立,一般在設計的時候也不能預測到有哪幾種功能組合。這就意味着要爲每一種組合建立一個新類。相反,修飾模式是面向運行時候的對象實例的,這樣就能夠在運行時根據須要進行組合。一個修飾模式的示例是JAVA裏的Java I/O Streams的實現。
Context 是基本的抽象類,不管是實現類,仍是裝飾類,都直接或間接的實現它。ContextImpl 是 Context 的直接實現類,但各個組件並非直接繼承 ContextImpl,而是經過裝飾類 ContextWrapper 來持有 ContextImpl。這是爲何呢?對於 Activity 和 Service 來講,它們都須要系統上下文運行環境,但它們又是不一樣的。Activity 須要顯示到前臺,它有頁面,它須要主題,因而有了繼承自 ContextWrapper 的 ContextThemeWrapper
,擴展了功能,給 Activity 提供了主題。同時,Activity、Service、Application 這些具體組件自己又擴展出了不一樣的生命週期功能。
因此,裝飾器模式經過組合和擴展裝飾類,來給不一樣的具體對象提供了不一樣的功能擴展。
Activity
、Service
、Application
最終都是繼承自裝飾類 ContextWrapper
,ContextWrapper
經過 attachBaseContext()
方法來獲取實際作事的 ContextImpl
對象。 因此這些組件的建立過程當中,必定會在某一時機調用 attachBaseContext()
方法對 mBase
對象進行賦值,讓咱們從源碼裏面找找答案。
先說 Activity
,Activity 的啓動過程極其複雜,咱們就直接從 ActivityThread
的 performLaunchActivity()
方法看起。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
// 1. 獲取 LoadedApk 對象
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
......
// 2. 建立 ContextImpl 對象
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 3. 反射建立 Activity 對象
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
......
} catch (Exception e) {
......
}
try {
// 4. 建立 Application 對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
......
appContext.setOuterContext(activity);
// 5. 綁定 activity
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
......
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
// 設置主題
activity.setTheme(theme);
}
// 6. 回調 onCreate()
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
......
r.activity = activity;
}
r.setState(ON_CREATE);
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
......
}
return activity;
}
複製代碼
整理一下大體的執行流程:
createBaseContextForActivity()
方法建立 ContextImpl 對象androoid.app.Application
activity.attach()
,這個方法很重要,後面詳細說onCreate()
接着就是 Activity 正常的生命週期流程了。
重點看一下 createBaseContextForActivity()
方法和 attach()
方法。
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
......
return appContext;
}
複製代碼
調用了 ContextImpl.createActivityContext()
方法。
static ContextImpl createActivityContext(ActivityThread mainThread, LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) {
......
// 建立 ContextImpl 對象
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
......
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
複製代碼
裝飾類 ContextWrapper 真正須要的 ContextImpl 對象如今已經建立出來了,可是尚未綁定到 Activity 。繼續看 Activity.attach()
方法,注意 attach()
方法的第一個參數就是剛剛建立出來的 ContextImpl 對象。
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
// 回調 attachBaseContext()
attachBaseContext(context);
......
// 建立 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
......
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
......
}
複製代碼
你對 attachBaseContext()
方法應該還有印象。ContextWrapper 正是經過這個方法給 mBase
對象賦值,拿到真正的 ContextImpl 對象。到這裏,整個邏輯就通順了。
注意
attach()
方法中的setWindowManager()
方法中的 mToken 參數,這決定了 Application Context 沒法建立和顯示 Dialog 。後續會進行詳細分析。
再回頭看看文章開頭的問題。
Log.e("context", "getApplication in Activity: " + getApplication().getClass().getName());
Log.e("context", "getApplicationContext in Activity: " + getApplicationContext().getClass().getName());
Log.e("context", "getBaseContext in Activity: " + getBaseContext().getClass().getName());
複製代碼
第一個 getApplication()
,看下源碼就知道了:
public final Application getApplication() {
return mApplication;
}
複製代碼
getApplication()
返回的是當前的 Application 對象。開發者沒有聲明本身實現的 Application 的話,就是系統默認的 android.app.Application
。
第二個 getApplicationContext()
,它並非 Activity 中的方法,而是 ContextWrapper 的。直接看源碼:
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
複製代碼
調用的是 ContextImpl.getApplicationContext()
。
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
複製代碼
因此返回的一樣是 Application 對象。
第三個,getBaseContext()
,一樣是 ContextWrapper 中的方法:
public Context getBaseContext() {
return mBase;
}
複製代碼
因此這裏返回的是 ContextImpl 對象。
最後的打印語句是:
E/context: getApplication in Activity: luyao.android.App
E/context: getApplicationContext in Activity: luyao.android.App
E/context: getBaseContext in Activity: android.app.ContextImpl
複製代碼
關於 Activity 就說這麼多了。下面來看看 Service 。
Service 其實和 Activity 的總體流程基本一致,建立服務的主要邏輯在 ActivityThread.handleCreateService()
方法中。這裏我就不貼源碼了,簡單敘述一下:
直接看一下 Service.attach()
方法:
public final void attach( Context context, ActivityThread thread, String className, IBinder token, Application application, Object activityManager) {
attachBaseContext(context);
......
}
複製代碼
又看到了熟悉的 attachBaseContext()
方法。
Activity
和 Service
都是繼承自 ContextWrapper
的,最後都是經過 attachBaseContext()
對 ContextImpl 類型的 mBase
賦值。而 ContentProvider
和 BroadcastReceiver
都沒有繼承 Context,因此它們獲取 Context 的方式會有一點不同。
先來看 ContentProvider
,建立 Provider 的邏輯在 Activity.installProvider()
方法中:
private ContentProviderHolder installProvider(Context context, ContentProviderHolder holder, ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
// 建立 LoadedApk 和 ContextImpl
c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);
try {
......
// 建立 ContentProvider
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
......
// 綁定 Context
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
......
}
......
return retHolder;
}
複製代碼
最後在 ContentProvider.attachInfo()
方法中進行了 ContextImpl 的賦值操做。
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
if (mContext == null) {
// 給 mContext 賦值
mContext = context;
......
// 回調 onCreate()
ContentProvider.this.onCreate();
}
}
複製代碼
這樣 ContentProvider 也能拿到 Context 對象了。
最後就是 BroadcastReceiver 了,對應 ActivityThread.handleReceiver()
方法:
private void handleReceiver(ReceiverData data) {
......
// 建立 LoadedApk 對象
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Application app;
BroadcastReceiver receiver;
ContextImpl context;
try {
// 建立 Application 對象
app = packageInfo.makeApplication(false, mInstrumentation);
// 建立 ContextImpl 對象
context = (ContextImpl) app.getBaseContext();
......
// 建立 BroadcastReceiver 對象
receiver = packageInfo.getAppFactory()
.instantiateReceiver(cl, data.info.name, data.intent);
} catch (Exception e) {
......
}
try {
......
// 回調 onReceive()
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
} catch (Exception e) {
......
} finally {
sCurrentBroadcastIntent.set(null);
}
......
}
複製代碼
大多數步驟和 Activity 仍是相似的,只是到最後回調 onReceive()
方法的時候,纔會把 ContextImpl 對象傳過去。注意,這裏並非直接返回原生的 ContextImpl 對象,而是調用 context.getReceiverRestrictedContext()
返回一個 受限制 的 ReceiverRestrictedContext
,你沒法使用這個 Context 對象啓動 Service 。
這不正是 裝飾者模式 的體現?想給廣播的 Context 對象加點限制,那就再來一個裝飾類 ReceiverRestrictedContext
,它繼承了 ContextWrapper
, 重寫部分方法以限制應用場景。經過增長和組合裝飾類,而不是增長子類,來實現功能擴展。
四大組件說完了,別忘了 Application
也是 Context
的間接子類。
Application 的建立時機得從應用進程的建立開始提及。Zygote 進程在接收到客戶端請求建立應用進程的 socket 請求以後,會 fork 出子進程,並反射調用 ActivityThread 的靜態 main() 方法。接着是 AMS 和客戶端的一系列 Binder 調用以及 Handler 通訊,最終主線程在收到 BIND_APPLICATION
消息以後回調 handleBindApplication()
方法,到這裏就是咱們須要的邏輯了:
private void handleBindApplication(AppBindData data){
......
// 獲取 ContextImpl
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
......
// 建立 Application 對象
app = data.info.makeApplication(data.restrictedBackupMode, null);
......
// 調用 Application 的 onCreate() 方法
mInstrumentation.callApplicationOnCreate(app);
}
複製代碼
你可能會疑惑怎麼沒有回調 attBaseContext()
方法,別急,看看 LoadedApk.makeApplication()
方法是如何建立 Application 的。
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
......
// 建立 ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 反射建立 Application
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
}
複製代碼
經過 Instrumentation.newApplication()
方法建立 Application 。
public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
// 反射建立
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
// 重點
app.attach(context);
return app;
}
複製代碼
重點就在 Application.attach()
方法。
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
複製代碼
在這裏調用了 attachBaseContext()
方法進行賦值,也驗證了 attachBaseContext()
的確比 onCreate()
先調用。
使用 Application 的 Context 建立 Dialog 並顯示,會報以下錯誤:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:951)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96)
at android.app.Dialog.show(Dialog.java:344)
複製代碼
注意錯誤信息 token null is not valid ,還記得文章前面說到 Activity 和 Context 的時候,有這麼一段代碼:
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
複製代碼
注意其中的 mToken 參數不爲 null ,是否是就說明了 Application 的 token 參數爲空呢?
原本準備接着說說這個問題,但可能形成文章篇幅過長,因此 Android 複習筆記 下一篇會單獨來嘮嘮這個問題。
若是你以爲文章還不錯,關注個人微信公衆號:秉心說TM