Android插件化基礎(chǔ)的主要內(nèi)容包括
- Android插件化基礎(chǔ)1-----加載SD上APK
- Android插件化基礎(chǔ)2----理解Context
- Android插件化基礎(chǔ)3----Android的編譯打包APK流程詳解
- Android插件化基礎(chǔ)4----APK安裝流程詳解(請期待)
- Android插件化基礎(chǔ)5----Rsources.arsc詳解(請期待)
- Android插件化基礎(chǔ)6----Android的資源系統(tǒng)(請期待)
- Android插件化基礎(chǔ)7----Activity的啟動(dòng)流程(請期待)
- Android插件化基礎(chǔ)8----如何啟動(dòng)一個(gè)沒有注冊過的Activity(請期待)
- Android插件化基礎(chǔ)9----Service的啟動(dòng)流程(請期待)
- Android插件化基礎(chǔ)10----BroadcastReceiver源碼解析(請期待)
為了讓大家在后面更好的理解插件化的內(nèi)容,我們本篇文章圍繞Context(基于Android API 24)進(jìn)行講解,主要內(nèi)容如下:
- 1、前言
- 2、Context的概念
- 3、Context的族譜
- 4、Context家族成員源碼分析
- 5、初始化過程
- 6、APP各種Context訪問資源的唯一性詳解
- 7、Context的內(nèi)存泄露
一、前言:
Context在android 系統(tǒng)中的地位不言而喻,而且Context對于我們Android開發(fā)同學(xué)來說,也并不陌生。在我們平時(shí)工作中我們經(jīng)常會(huì)用Context來獲取APP的資源,開啟Activity,獲取系統(tǒng)Service服務(wù),發(fā)送廣播等,經(jīng)常有人會(huì)問:
Context到底是什么?
Context如何實(shí)現(xiàn)以上功能的?
為何不同的Context訪問資源都得到的是同一套資源?
為何不同場景下要使用不同的Context?
一個(gè)APP中有多少個(gè)Context?
getApplication和getApplicationContext是同一個(gè)對象嗎?
那么我們就帶著以上的內(nèi)容來開始我們今天的內(nèi)容。
二、Context的概念
(一)、源碼解讀
由于Context的源碼內(nèi)容太多,4K多行代碼,太多了,我們就簡單的看下類的的注釋
/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
//省略代碼
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
//省略代碼
}
我們來簡單的翻譯一下注釋:
獲取一個(gè)應(yīng)用程序全部信息的接口。它是一個(gè)抽象類,由Android系統(tǒng)提供它的具體實(shí)現(xiàn)類,它擁有訪問應(yīng)用程序內(nèi)特定的資源和類的權(quán)限,以及應(yīng)用程序級操作,例如啟動(dòng)activity、廣播和接收intent等。
上面是官方回答,我的理解是:
- 從抽象的角度來理解:
咱們平時(shí)在工作生活中經(jīng)常會(huì)使用到一個(gè)詞兒——"場景",一個(gè)使用場景就代表用戶和我們的app軟件交互的過程。比如,你在家里打”農(nóng)藥“就是一個(gè)場景;你在賓館和妹子一起用手機(jī)看"小"電影也是一個(gè)場景;你在公司用電腦調(diào)試你的app也是一個(gè)場景。那么Context 就可以抽象的理解為一個(gè)場景,每個(gè)場景有不同的內(nèi)容。在程序中,我們可以理解為某對象在程序中所處的"場景"(環(huán)境),一個(gè)與系統(tǒng)交互的過程,比如上面你在打”農(nóng)藥“,你的場景就是你的操作界面,和你的手勢相關(guān)操作的數(shù)據(jù)與傳輸。所以Context在加載資源、啟動(dòng)Activity、獲取系統(tǒng)服務(wù)、創(chuàng)建View等操作都要參與進(jìn)來。打電話、發(fā)短信、玩"農(nóng)藥"等都是有界面的,所以是一類"有界面的"場景,還有一些沒有界面的場景,比如你在后臺(tái)播放歌曲的程序,就是一類"沒有界面"的場景。其實(shí)一個(gè)app就描繪了一個(gè)主要的場景,比如淘寶,描繪的是購物的場景;微信是聊天的場景;支付寶,描繪的就是支付的場景,所以說Context其實(shí)就是一個(gè)"場景",因?yàn)镃ontext是一個(gè)"場景",所以它可以獲取這個(gè)場景下的所有信息。- 從研發(fā)的角度來理解:
Context是一個(gè)抽象類,我們通過這個(gè)Context可以訪問包內(nèi)的資源(res和assets)和啟動(dòng)其他組件(activity、service、broadcast)以及系統(tǒng)服務(wù)(systemService)等。所以Context提供了一個(gè)應(yīng)用程序運(yùn)行環(huán)境,在Context的環(huán)境里,應(yīng)用才可以訪問資源,才能和其他組件、服務(wù)交互,Context定義了一套基本功能接口,我們可以理解為一套規(guī)范,而Activity和Service是實(shí)現(xiàn)這套規(guī)范的具體實(shí)現(xiàn)類(其實(shí)內(nèi)部是ContextImpl統(tǒng)一實(shí)現(xiàn)的)。所以可以這樣說,Context是維持Android程序中各個(gè)組件能夠正常工作的一個(gè)核心功能類。
上面說了Context是一個(gè)抽象類,那它的具體子類都有哪些?我們來一起看一下他的族譜。
三、Context的族譜
那我們來看下他們的具體子類,在Context官網(wǎng)的API如下:
圖片太大,加上很多類咱們暫時(shí)不需要考慮,整理了下,如下圖:
我們以activity為本體,以繼承的形式來分析,可以大概分為4輩兒,其中activity是本級;ContextThemeWrapper是Activity的父類,Service、Aplication是Actiivty的叔類;ContextWrapper是Activity的祖父類、ContextImpl是Activity的叔祖父類;Context是Activity的曾祖父類。
我們再自上而下的去講解下:
- 1、Context類是一個(gè)純粹的抽象類
- 2.1、ContextImpl是Context的具體實(shí)現(xiàn)類,實(shí)現(xiàn)了Context的所有功能。
- 2.2、ContextWrapper,通過字面就知道是一個(gè)Context的包裝類,由于ContextWrapper就一個(gè)構(gòu)造函數(shù),且必須要傳入一個(gè)Context,所以ContextWrap中持有有一個(gè)mBase變量的應(yīng)用,通過調(diào)用attachBaseContext(Context)方法用于給ContextWrapper對象中mBase指定真正的Context對象。傳入的變量就是ContextImpl對象。
- 3.1、ContextThemeWrapper內(nèi)部封裝了包含了與主題相關(guān)的接口,這樣就可以在AndroidManifest.xml文件中添加android:theme為Application或者Activity指定的主題,所以Actvity繼承ContextThemeWrapper
- 3.2、由于Application不需要界面,所以直接繼承自ContextWrapper。
- 3.3、由于Service同樣不需要界面,所以也直接繼承自ContextWrapper。
- 4、由于Activity需要主題,所以Activity繼承自ContextThemeWrapper。
PS:因此在絕對大多數(shù)場景下,Activity、Service和Application這三種類型的Context都是可以通用的,因?yàn)樗麄兌际荂ontextWrapper的子類,但是由于如果是啟動(dòng)Activity,彈出Dialog,一般涉及到"界面"的領(lǐng)域,都只能由Activity來啟動(dòng)。因?yàn)槌鲇诎踩目紤],Android是不允許Activity或者Dialog憑空出現(xiàn)的,一個(gè)Activity的啟動(dòng)必須建立在另一個(gè)Activity的基礎(chǔ)之上,也就是以此形成了返回棧。而Dialog則必須在一個(gè)Activity上面彈出(如果是系統(tǒng)的除外),因此這種情況下我們只能使用Activity類型的Context。整理了一些使用場景的規(guī)則,也就是Context的作用域,如下圖:
從上圖,我們知道Activity作為Context,作用域最廣,是因?yàn)锳ctivity繼承自ContextThemeWrapper,而Application和Service繼承自ContextWrapper。很顯然ContextThemeWrapper在ContextWrapper的基礎(chǔ)上又做了一些操作使Activity異常強(qiáng)大,關(guān)于源碼,我們下面會(huì)講解。YES和NO,太明顯就不提了,簡單說下不推薦的幾種情況:
- 1、如果我們用Application作為Context去啟動(dòng)一個(gè)沒有設(shè)置過LaunchMode的Activity會(huì)報(bào)錯(cuò)"android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?",這是因?yàn)榉茿ctivity類型的Context并沒有所謂的任務(wù)棧,所以待啟動(dòng)的Activity就找不到棧了。解決這個(gè)問題方法就是為待啟動(dòng)的Activity指定FLAG_ACTIVITY_NEW_TASK的標(biāo)志位,這樣啟動(dòng)這個(gè)Activity,它就會(huì)創(chuàng)建一個(gè)新的任務(wù)棧,此時(shí)Activity是以singleTask模式啟動(dòng)的,所有這種用Application啟動(dòng)Activity方式不推薦使用,Service同理。
- 2、在Application和Service中使用LayoutInflater也是合法的,但是會(huì)使用系統(tǒng)默認(rèn)的主題樣式,這樣你自定義的樣子可能會(huì)受到影響,所以這種方式也不推薦使用。
總結(jié)一下:
和"界面"(UI)有關(guān)的,都應(yīng)該使用Activity作為Context來處理,其他的可以使用Service、Application或者Activity作為Context。關(guān)于Context內(nèi)存泄露的問題,下面再詳細(xì)將講解。
四、源碼分析初始化過程
(一)、ContextWrapper源碼解析
由于ContextWrapper源碼比較多,我只截取了一部分,源碼如下:
/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context. Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
/**
* @return the base context as set by the constructor or setBaseContext
*/
public Context getBaseContext() {
return mBase;
}
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
//省略了部分代碼
}
ContextWrapper繼承自Context,和我們之前的畫的類繼承圖一樣。
1、類注釋翻譯
Context的代理類,具體的調(diào)用由另外一個(gè)Context來實(shí)現(xiàn),除了不能修改真正的Context,其他方面子類是可以修改的。
- 注釋說的很明顯ContextWrapper是個(gè)代理類,之前我們已經(jīng)講解過了,這是明顯的靜態(tài)代理,起到真正作用的是Context mBase。
- 大家發(fā)現(xiàn)ContextWrapper的所有方法均是調(diào)用的mBase對應(yīng)的方法,所以進(jìn)一步證明了ContextWrapper是代理,mBase才是真正的執(zhí)行者。
- 所以說它的三個(gè)子類ContextThemeWrapper,Service,Application也是不能改變具體由mBase的地址
- 該類的構(gòu)造函數(shù)包含了一個(gè)真正的Context引用(ContextImpl對象),然后就變成了ContextImpl的裝飾著模式。
2、attachBaseContext(Context)方法解析
方法注釋翻譯:
通過這個(gè)方法來設(shè)置ContextWrapper的字段mBase賦值,所有的調(diào)用其實(shí)是代理調(diào)用到mBase這個(gè)Context,如果字段mBase已經(jīng)被賦值了,則會(huì)拋出異常。
這個(gè)方法其實(shí)很簡答, 就是判斷是否被賦值過,賦值過則拋異常,沒有給mBase賦值
(二)、ContextThemeWrapper源碼解析
/**
* A context wrapper that allows you to modify or replace the theme of the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
//省略部分代碼
/**
* Creates a new context wrapper with no theme and no base context.
* <p>
* <stong>Note:</strong> A base context <strong>must</strong> be attached
* using {@link #attachBaseContext(Context)} before calling any other
* method on the newly constructed context wrapper.
*/
public ContextThemeWrapper() {
super(null);
}
/**
* Creates a new context wrapper with the specified theme.
* <p>
* The specified theme will be applied on top of the base context's theme.
* Any attributes not explicitly defined in the theme identified by
* <var>themeResId</var> will retain their original values.
*
* @param base the base context
* @param themeResId the resource ID of the theme to be applied on top of
* the base context's theme
*/
public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
super(base);
mThemeResource = themeResId;
}
/**
* Creates a new context wrapper with the specified theme.
* <p>
* Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to
* this constructor will completely replace the base context's theme.
*
* @param base the base context
* @param theme the theme against which resources should be inflated
*/
public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
mTheme = theme;
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
//省略部分代碼
}
ContextThemeWrapper繼承自ContextWrapper,和我們之前的畫的類繼承圖一樣
1、類注釋
來看下類的注釋,翻譯如下:
一個(gè)允許你修改或者替換主題的Context的包裝類。
2、構(gòu)造函數(shù)解析
- 有三個(gè)構(gòu)造函數(shù)
- 無參的構(gòu)造函數(shù)調(diào)用的是super(null),所以說調(diào)用無參的構(gòu)造函數(shù)的mBase屬性是沒有值的,同時(shí)主題也是null。
- 2個(gè)參數(shù)的構(gòu)造函數(shù),第二個(gè)參數(shù)是int類型的構(gòu)造函數(shù),第一個(gè)參數(shù)Context給mBase字段賦值,第二個(gè)參數(shù)給mThemeResource賦值
- 2個(gè)參數(shù)的構(gòu)造函數(shù),第二個(gè)參數(shù)是Theme類型的構(gòu)造函數(shù),一個(gè)參數(shù)Context給mBase字段賦值,第二個(gè)參數(shù)給mTheme賦值。
- 通過兩個(gè)參數(shù)的構(gòu)造函數(shù)我們知道,只要不是調(diào)用無參的ContextThemeWrapper構(gòu)造函數(shù),ContextThemeWrapper對象的mBase是一定有值的。
PS:mTheme和mThemeResource是有關(guān)聯(lián)關(guān)系的。
3、attachBaseContext(Context newBase)
這個(gè)方法很簡單,就是調(diào)用父類的attachBaseContext方法。
(三)、 Service源碼解析
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
//省略部分代碼
public Service() {
super(null);
}
/** Return the application that owns this service. */
public final Application getApplication() {
return mApplication;
}
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
attachBaseContext(context);
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
mStartCompatibility = getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.ECLAIR;
}
}
關(guān)于Service大家都很熟悉了,我就不說類的注釋,不過我建議大家還是去看下,對大家進(jìn)一步理解Service是很有幫助的。Service繼承自ContextWrapper,和我們之前的畫的類繼承圖一樣
1、構(gòu)造函數(shù)分析
- 通過構(gòu)造函數(shù)我們知道,Service的構(gòu)造函數(shù)就一個(gè)
- 構(gòu)造函數(shù)默認(rèn)調(diào)用父類的,而且傳入的值是null,說明構(gòu)造Service的時(shí)候,mBase是null,那Service的mBase的屬性是什么時(shí)候被賦值的那?其實(shí)他的賦值是在attach(Context,ActivityThread, String, IBinder,Application, Object)方法里面通過調(diào)用attachBaseContext(context)賦值的。所以我們可以預(yù)測Android系統(tǒng)new (也有可能是反射哦)了Service之后 肯定調(diào)用了attach()方法
(四)、Application源碼解析
public class Application extends ContextWrapper implements ComponentCallbacks2 {
//省略部分代碼
public Application() {
super(null);
}
final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
//省略部分代碼
}
Application繼承自ContextWrapper,和我們之前的畫的類繼承圖一樣。
關(guān)于Application大家都很熟悉了,我就不說類的注釋,不過我建議大家還是去看下,對大家進(jìn)一步理解Application是很有幫助的。
1、構(gòu)造函數(shù)分析
- 通過構(gòu)造函數(shù)我們知道,Application的構(gòu)造函數(shù)就一個(gè)
- 構(gòu)造函數(shù)默認(rèn)調(diào)用父類的,而且傳入的值是null,說明構(gòu)造Application實(shí)例的時(shí)候,mBase是null,那Application的mBase的屬性是什么時(shí)候被賦值的那?其實(shí)他的賦值是在attach(Context)方法里面通過調(diào)用attachBaseContext(context)賦值的。所以我們可以預(yù)測Android系統(tǒng)new(也有可能是反射哦)了Application之后 肯定調(diào)用了attach()方法
(五)、Activity源碼解析
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback {
//省略部分代碼
}
Activity繼承自ContextThemeWrapper,和我們之前的畫的類繼承圖一樣
咦!突然發(fā)現(xiàn)沒有找到Activity的構(gòu)造函數(shù),哦~,原來Activity.java是沒有寫構(gòu)造函數(shù)的,所以Activity的是用無參的構(gòu)造函數(shù),默認(rèn)會(huì)調(diào)用父類也就是ContextThemeWrapper的無參構(gòu)造函數(shù),所以Activity對象實(shí)例化的時(shí)候mBase是沒有值,那Activity的mBase是什么時(shí)候被賦值的那?我們找一下發(fā)現(xiàn)是在調(diào)用attach(Context, ActivityThread, Instrumentation,IBinder, int,Application, Intent, ActivityInfo,
CharSequence, Activity, String,NonConfigurationInstances,
Configuration,String,IVoiceInteractor,Window)attachBaseContext(context) 方法里面調(diào)用了attachBaseContext(context);給Activity的mBase賦值的。
(六)、ContextImpl源碼解析
ContextImpl繼承自Context ,和我們之前的畫的類繼承圖一樣
/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {
final LoadedApk mPackageInfo;
//省略部分代碼
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
mOuterContext = this;
// If creator didn't specify which storage to use, use the default
// location for application.
if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE
| Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) {
final File dataDir = packageInfo.getDataDirFile();
if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) {
flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
} else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) {
flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
}
}
mMainThread = mainThread;
mActivityToken = activityToken;
mFlags = flags;
if (user == null) {
user = Process.myUserHandle();
}
mUser = user;
mPackageInfo = packageInfo;
mResourcesManager = ResourcesManager.getInstance();
final int displayId = (createDisplayWithId != Display.INVALID_DISPLAY)
? createDisplayWithId
: (display != null) ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
CompatibilityInfo compatInfo = null;
if (container != null) {
compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();
}
if (compatInfo == null) {
compatInfo = (displayId == Display.DEFAULT_DISPLAY)
? packageInfo.getCompatibilityInfo()
: CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
}
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
if (container != null) {
// This is a nested Context, so it can't be a base Activity context.
// Just create a regular Resources object associated with the Activity.
resources = mResourcesManager.getResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
} else {
// This is not a nested Context, so it must be the root Activity context.
// All other nested Contexts will inherit the configuration set here.
resources = mResourcesManager.createBaseActivityResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
}
}
}
mResources = resources;
mDisplay = (createDisplayWithId == Display.INVALID_DISPLAY) ? display
: mResourcesManager.getAdjustedDisplay(displayId, mResources.getDisplayAdjustments());
if (container != null) {
mBasePackageName = container.mBasePackageName;
mOpPackageName = container.mOpPackageName;
} else {
mBasePackageName = packageInfo.mPackageName;
ApplicationInfo ainfo = packageInfo.getApplicationInfo();
if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {
// Special case: system components allow themselves to be loaded in to other
// processes. For purposes of app ops, we must then consider the context as
// belonging to the package of this process, not the system itself, otherwise
// the package+uid verifications in app ops will fail.
mOpPackageName = ActivityThread.currentPackageName();
} else {
mOpPackageName = mBasePackageName;
}
}
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread,
packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetrics());
return context;
}
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread, packageInfo, activityToken, null, 0,
null, overrideConfiguration, displayId);
}
//省略部分代碼
}
如果大家第一次找ContextImpl類,還是比較麻煩的,因?yàn)镃ontextImpl類是ReceiverRestrictedContext的內(nèi)部類。這個(gè)類的代碼比較多,這里就不詳細(xì)說明了,大家可以自己去了解下
通過類的注釋我們得知:
Context API的通用實(shí)現(xiàn),它為Activity和其他應(yīng)用程序組件提供了基礎(chǔ)的Context。
- 上面我們發(fā)現(xiàn)ContextImpl的構(gòu)造函數(shù)是private,所以外部想要獲取ContextImpl的實(shí)例,所以通過它的其他方法
- createSystemContext(//省略入?yún)?方法可以獲取系統(tǒng)的ContextImpl
- createAppContext(//省略入?yún)?方法獲取獲取Application的ContextImpl
- createActivityContext(省略入?yún)?方法獲取Activity的ContextImpl
五、Context家族成員初始化分析
(一)、ContextImpl 實(shí)例在哪里創(chuàng)建
每一個(gè)應(yīng)用程序在客戶端都是從ActivityThread類(重點(diǎn)提醒:ActivityThread不是Thread)開始的,創(chuàng)建Context對象也是在該類中完成,具體創(chuàng)建ContextImpl的地方一種有6處
- LoadedApk.makeApplication(); //772 line
- ActivityThread.createBaseContextForActivity();//2674 line
- ActivityThread.handleCreateBackupAgent //3061 line
- ActivityThread.handleCreateService //3163 line
- ActivityThread.attach(); // 5930 line
- ActivityThread.getSystemContext() //2052 line
也有人說在PackageInfo類的makeApplication()也有,但是我沒找到,如果有朋友找到也留言告訴我一聲,謝謝!
通過上面方法我們發(fā)現(xiàn)貌似大部分ContextImpl的實(shí)例創(chuàng)建都是在ActivityThread里面,可見ActivityThread很重要。
(二)、Application及對應(yīng)的mBase實(shí)例創(chuàng)建過程
源碼說話:
//ActivityThread.java
private void handleBindApplication(AppBindData data) {
//省略部分代碼
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
// For process that contains content providers, we want to
// ensure that the JIT is enabled "at some point".
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
//省略部分代碼
在ActivityThread的handleBindApplication(AppBindData)里面調(diào)用 data.info.makeApplication(boolean,Instrumentation);,因?yàn)?data是 AppBindData 類的實(shí)例, data.info 是 LoadedApk對象,所以data.info.makeApplication(boolean,Instrumentation)其實(shí)調(diào)用的是 LoadedApk的
makeApplication((boolean,Instrumentation)方法,那我們進(jìn)去看下
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
************************重點(diǎn)******************************
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
************************重點(diǎn)******************************
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
//省略部分代碼
}
看我畫重點(diǎn)的部分
- 1、里面調(diào)用ContextImpl.createAppContext(ActivityThread, LoadedApk)方法獲取一個(gè) ContextImpl 對象
- 2、調(diào)用 Instrumentation的newApplication(ClassLoader ,String, Context) 方法創(chuàng)建一個(gè) *** Application*** 。
- 3、appContext.setOuterContext(app) 來設(shè)置 Application的ContextImpl
至此完成了 Application 及其 mBase 的實(shí)例創(chuàng)建過程,那我們再來看下 Instrumentationd的newApplication(ClassLoader ,String, Context) 方法的源碼
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;
}
我們發(fā)現(xiàn) Instrumentationd的newApplication(ClassLoader ,String, Context) 方法內(nèi)部又調(diào)用 Instrumentationd 的靜態(tài)方法 newApplication(Class<?> clazz, Context context) 來實(shí)現(xiàn)的,在 Instrumentationd 的靜態(tài)方法 newApplication(Class<?> clazz, Context context) 里面是調(diào)用反射的newInstance()方法來獲取的。原來 Application 是通過反射來實(shí)例化的啊。
(三)、Activity及對應(yīng)的mBase實(shí)例創(chuàng)建過程
- 啟動(dòng)Activity時(shí),AMS(ActivityManagerService)會(huì)通過IPC調(diào)用到
ActivtyThread的scheduleLaunchActivity() 方法,該方法包含兩個(gè)參數(shù)(不是只有兩個(gè)參數(shù))。一種是 ActivityInfo ,這是一個(gè)實(shí)現(xiàn)了 Parcelable 接口的數(shù)據(jù)類,意味著該對象是AMS創(chuàng)建的,并通過IPC傳遞到 ActivityThread ;另一種是其他的一些參數(shù)。
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
H類其實(shí)是一個(gè)Handler
private class H extends Handler {
//省略部分代碼
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
//省略部分代碼
}
//省略部分代碼
}
scheduleLaunchActivity() 方法中會(huì)根據(jù)以上兩種參數(shù)構(gòu)造一個(gè) ActivityRecord 數(shù)據(jù)類, ActivityThread 內(nèi)部會(huì)為每一個(gè) Activity 創(chuàng)建一個(gè) ActivityRecord 對象,并使用這些數(shù)據(jù)對象來管理 Activity 。 scheduleLaunchActivity() 方法執(zhí)行完后會(huì)調(diào)用發(fā)送一個(gè)Message到H,H類重寫了一個(gè) handleMessage() ,然后會(huì)調(diào)用到 case LAUNCH_ACTIVITY中 ,在里面又調(diào)用了 handleLaunchActivity(ActivityClientRecord , Intent, String) ,那我們進(jìn)去 handleLaunchActivity 方法內(nèi)部看個(gè)究竟
//ActiviyThread.java
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
//省略部分代碼
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
performPauseActivityIfNeeded(r, reason);
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
//省略部分代碼
}
}
在 handleLaunchActivity(ActivityClientRecord , Intent, String) 方法調(diào)用 performLaunchActivity() 來獲取一個(gè)Activity,我們猜到其實(shí)是 performLaunchActivity(ActivityClientRecord , Intent) 方法內(nèi)部才是真正創(chuàng)建Activity的地方,那我們就來看下源碼,代碼如下:
//ActiviyThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略部分代碼
************************重點(diǎn)******************************
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
//省略部分代碼
if (activity != null) {
Context appContext = createBaseContextForActivity(r, 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);
************************重點(diǎn)******************************
//省略部分代碼
}
大家看重點(diǎn)區(qū)域,通過閱讀源碼,我們可以獲取如下信息:
- 1、 activity 創(chuàng)建時(shí)通過調(diào)用 Instrumentationd的newActivity(ClassLoader, String,Intent) 方法來獲取的
- 2、里面調(diào)用 ContextImpl的(ActivityClientRecord , Activity) 方法獲取一個(gè) ContextImpl 對象
- 3、 Activity 的attach(Context,ActivityThread,Instrumentation, IBinder, int,Application, Intent, ActivityInfo,CharSequence, Activity, String,NonConfigurationInstances,Configuration, String, IVoiceInteractor,Window) ;來設(shè)置 Activity的ContextImpl
那我們來看下 Instrumentation的newActivity(ClassLoader, String,Intent) 方法里面是如何實(shí)現(xiàn)的
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
其實(shí)很簡單,就是直接調(diào)用放射的newInstance()來獲取一個(gè)對象。
至此,Activity及對應(yīng)的mBase實(shí)例創(chuàng)建過程已經(jīng)完成
(四)、Service及對應(yīng)的mBase實(shí)例創(chuàng)建過程
啟動(dòng) Service 時(shí), AMS 會(huì)通過調(diào)用IPC調(diào)用到 ActivityThread的scheduleCreateService() 方法,該方法也包含兩種參數(shù),第一個(gè)是 ServiceInfo ,這是一個(gè)實(shí)現(xiàn)了 Parcelable接口 的數(shù)據(jù)類,該對象由Ams創(chuàng)建,并通過IPC傳遞到 ActivityThread 內(nèi)部;第二種是其他參數(shù)。
//ActivityThread.java
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}
H類其實(shí)是一個(gè)Handler
private class H extends Handler {
//省略部分代碼
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case CREATE_SERVICE:
//省略部分代碼
handleCreateService((CreateServiceData)msg.obj);
//省略部分代碼
break;
//省略部分代碼
}
//省略部分代碼
}
- 在 scheduleCreateService() 方法中,會(huì)使用以上兩種參數(shù)構(gòu)造一個(gè) CreateServiceData 的數(shù)據(jù)對象, ActivityThread 會(huì)為其所包含的每一個(gè) Service 創(chuàng)建該數(shù)據(jù)對象,并通過這些對象來管理 Service 。
- scheduleCreateService() 方法執(zhí)行完后會(huì)調(diào)用發(fā)送一個(gè)Message到H, case CREATE_SERVICE中 ,在里面又調(diào)用了 handleCreateService(CreateServiceData) ,那我們進(jìn)去 handleCreateService 方法內(nèi)部看個(gè)究竟
private void handleCreateService(CreateServiceData data) {
************************重點(diǎn)******************************
//省略部分代碼
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
//省略部分代碼
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,
ActivityManagerNative.getDefault());
************************重點(diǎn)******************************
//省略部分代碼
}
通過閱讀源碼我們知道
- 1、 Application 和 Activity 的創(chuàng)建都是通過 Instrumentation 來闖將實(shí)例的,不過 Service 不是,直接在 ActivityThread 里面直接調(diào)用反射獲取實(shí)例
- 2、里面調(diào)用 ContextImpl.createAppContext(ActivityThread, LoadedApk) 方法獲取一個(gè) ContextImpl 對象,這里和 Application一樣 ,和 Activity 不一樣。
- 3、調(diào)用 Service的attach(context,ActivityThread, String, IBinder,Application, Object) 方法進(jìn)行綁定 ContextImpl 。
至此完成了Service及其mBase的實(shí)例創(chuàng)建過程
(五)、這幾個(gè)Context之間的關(guān)系
- 從以上可以看出,創(chuàng)建Context對象的過程基本一樣,不同的僅僅是針對Application、Activity、Service使用了不同的數(shù)據(jù)對象。
- 一個(gè)應(yīng)用程序的Context的個(gè)數(shù)應(yīng)該為:Context個(gè)數(shù)=Service個(gè)數(shù)+Activity個(gè)數(shù)+Application個(gè)數(shù),如果是普通應(yīng)用Application個(gè)數(shù)是一個(gè),如果是插件化中的多進(jìn)程應(yīng)用,則Application是多進(jìn)程個(gè)數(shù)。
- 應(yīng)用程序中包含多個(gè)ContextImpl對象,而內(nèi)部變量mPackageInfo卻指向了同一個(gè)LoadedApk對象,這種設(shè)計(jì)結(jié)構(gòu)意味著ContextImpl中大多數(shù)進(jìn)行包操作的重量級函數(shù)實(shí)際上都轉(zhuǎn)向了mPackageInfo對象的響應(yīng)方法,也就是事實(shí)上調(diào)用了同一個(gè)LoadedApk對象。
(六)如何獲取Context
通常我們想要獲取Context對象,主要有以下四種方法
- 1、View.getContext(),返回的是當(dāng)前View對象的Context對象,通常是當(dāng)前正在展示的Activity對象
- 2、context.getApplicationContext(),獲取當(dāng)前Context所在的應(yīng)用進(jìn)程的Context對象,通常我們使用Context對象時(shí),要優(yōu)先考慮這個(gè)全局的進(jìn)程Context。
- 3、ContextWrapper.getBaseContext();用來獲取一個(gè)ContextWrapper進(jìn)行裝飾之前的Context,可以使用這個(gè)方法,這個(gè)方法在實(shí)際開發(fā)中使用并不多,也不建議使用。
- 4、Activity.this,返回當(dāng)前的Activity實(shí)例,如果是UI控件需要使用Activity作為Context對象,但是默認(rèn)的Toast實(shí)際上使用ApplicationContext也可以。
(七)getApplication()和getApplicationContext()
上面說到了獲取當(dāng)前Application對象用getAppcationContext,不知道你有沒有聯(lián)想到getAppliation(),這兩個(gè)方法有什么區(qū)別?下面我們就仔細(xì)研究下。
以activiy為例,Activity.getAppliation()返回的是mApplication,代碼如下:
public final Application getApplication() {
return mApplication;
}
那么mApplication是什么時(shí)候被初始化的那,其實(shí)如果你剛剛仔細(xì)讀過源碼就是會(huì)知道是在activity的attach里面被初始化的
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) {
//省略部分代碼
mApplication = application;
//省略部分代碼
}
那調(diào)用attach方法中的第6個(gè)參數(shù)application,是怎么來的?
那我們回到ActivityThread的performLaunchActivity()方法里面一看究竟。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略部分代碼
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
//省略部分代碼
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);
//省略部分代碼
}
通過上述代碼我們知道app來自LoadedApk的makeApplication()方法里面。
那我們來看下LoadedApk.makeApplication()里面具體的執(zhí)行
//LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
//省略部分代碼
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
//省略部分代碼
}
通過上述代碼,我們知道在LoadedApk的makeApplication里面首先會(huì)判斷mApplication是否為空,如果不為空直接返回,如果為空則通過反射實(shí)例化一個(gè)。那我們來看下getAppliationContext()里面的具體實(shí)現(xiàn),首先說明下getApplicationContext()這個(gè)方法是ContextWrapper的方法,代碼如下:
//ContextWrapper.java
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
所以我們知道,其實(shí)調(diào)用的是mBase.getApplicationContext()方法。因?yàn)槲覀冎續(xù)Base其實(shí)就是ContextImpl所以我們看下ContextImpl里面的具體實(shí)現(xiàn)
//ReceiverRestrictedContext.java
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
咦,mPackageInfo不是LoadedApk對象嗎?那調(diào)用 mPackageInfo.getApplication()的源碼是:
Application getApplication() {
return mApplication;
}
哈哈,大家其實(shí)看懂了吧,其實(shí)無論是getApplication()還是getApplicationContext()最后其實(shí)都是調(diào)用的LoadedApk的mApplication。而在一個(gè)安裝包下LoadedApk是同一個(gè)對象,所以getApplication()==getApplicationContext()是true。不知道大家看懂了沒。
其實(shí)還有一種方法,大家在直接打印getApplication()和getApplicationContext(),最后大家會(huì)發(fā)現(xiàn)是指向同一個(gè)內(nèi)存地址,強(qiáng)烈建議大家去試試!
那么問題來了,既然這兩個(gè)方法得到結(jié)果都是相同的,那么Android為什么要提供這兩個(gè)功能重復(fù)的方法那?實(shí)際上這兩個(gè)方法在作用域上有比較大的區(qū)別。getApplication()方法在語義性上非常強(qiáng),一看就知道來獲取Application的,但是她只在Activity和Service上才能調(diào)用。而在一些其他場景,比如BroadcastReceiver中也想獲取Application實(shí)例,這時(shí)就可以借助getApplicationContext()方法如下:
publicclassMyReceiverextendsBroadcastReceiver{
@Override
public void onReceive(Context context,Intent intent){
ApplicationmyApp=(Application)context.getApplicationContext();
}
}
現(xiàn)在希望大家可以清楚的理解getApplicationContext()與getApplication()。
六、APP各種Context訪問資源的唯一性詳解
之前提過一個(gè)問題
為何不同的Context訪問資源都得到的是同一套資源?
首先,我們來看下Activity源碼
public abstract class Context {
public abstract AssetManager getAssets();
public abstract Resources getResources();
}
所以我們知道Context的getAssets()和getResources()方法是個(gè)抽象類,而我們又知道ContextImpl才是Context的具體實(shí)現(xiàn)類,那我們再來看下ContextImpl的源碼:
class ContextImpl extends Context {
//省略部分代碼
private final @NonNull ResourcesManager mResourcesManager;
private final @NonNull Resources mResources;
@Override
public AssetManager getAssets() {
return getResources().getAssets();
}
@Override
public Resources getResources() {
return mResources;
}
//省略部分代碼
}
所以我們知道,平時(shí)寫的各種Context的getAssets()方法和getResources()方法獲取的Resources對象就是上面的ContextImpl的成員變量mResources。
那我們來看下mResources什么時(shí)候被初始化的,我們發(fā)現(xiàn)其實(shí)mResources是在ContextImpl里面被初始化的,代碼如下:
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
//省略部分代碼
mResourcesManager = ResourcesManager.getInstance();
//省略部分代碼
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
if (container != null) {
// This is a nested Context, so it can't be a base Activity context.
// Just create a regular Resources object associated with the Activity.
resources = mResourcesManager.getResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
} else {
// This is not a nested Context, so it must be the root Activity context.
// All other nested Contexts will inherit the configuration set here.
resources = mResourcesManager.createBaseActivityResources(
activityToken,
packageInfo.getResDir(),
packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
}
}
}
mResources = resources;
//省略部分代碼
}
這里補(bǔ)充下內(nèi)容
- 1、ResourcesManager.getInstance()明顯是一個(gè)單例
- 2、對一個(gè)App而言,一個(gè)App對應(yīng)一個(gè)LoadedApk,所以packageInfo.getResources(mainThread);也應(yīng)該是同一個(gè)Resources
- 3、由于之前咱們創(chuàng)建的Activity、Service還是Application中的ContextImpl都是中的container=null(至于為什么等于null因?yàn)檎{(diào)用的靜態(tài)create方法里面第一個(gè)參數(shù)都是null),所以mResourcesManager也是一份。
綜上所述,我們可以看出在設(shè)備其他因素不變的情況下我們通過不同的Context實(shí)例得到的Resources是同一套資源。
關(guān)于Resources我后續(xù)會(huì)專門開個(gè)文章去詳細(xì)講解。
七、Context的內(nèi)存泄露問題
因?yàn)锳ctivity是生命周期是由系統(tǒng)控制的,所以如果長周期的對象持有對Activit的引用,會(huì)導(dǎo)致Activity無法回收。
(一)、靜態(tài)資源導(dǎo)致內(nèi)存泄露
1、問題
由于靜態(tài)資源在類加載以后就會(huì)一直有,聲明周期很長,如果它持有一個(gè)Activity,會(huì)導(dǎo)致Activity無法被回收,而產(chǎn)生內(nèi)存泄漏。
2、解決方案
- 1、盡量避免使用靜態(tài)變量引用Activity
- 2、如果一定要使用靜態(tài)變量,則在Activity的onDestroy的時(shí)候解除引用,或者指向null。
(二)、單例模式導(dǎo)致內(nèi)存泄露
1、問題
單例,意味著這個(gè)對象會(huì)一直存在,如果這個(gè)對象中持有了Activity的引用,也會(huì)導(dǎo)致Activity無法被回收,而產(chǎn)生內(nèi)存泄露
2、解決方案
避免在單例模式下持有Context,如果一定要持有也是Application,絕對不能是Activity。
(三)、內(nèi)部類或者匿名內(nèi)部類導(dǎo)致內(nèi)存泄露
1、問題
內(nèi)部類(匿名內(nèi)部類其實(shí)也是內(nèi)部類的一種)會(huì)持有外部類的引用,當(dāng)內(nèi)部類進(jìn)行延時(shí)操作的時(shí)候,如果外部類是Activity,則在執(zhí)行ondestroy后,并不會(huì)銷毀,從而導(dǎo)致內(nèi)存泄露
什么是"內(nèi)部類持有外部類引用?"
可以這樣理解
public class InnerClass {
private OuterClass outer;
public InnerClass(OuterClass outer) {
this.outer = outer;
}
}
就是說,創(chuàng)建InnerClass對象時(shí),必須傳遞進(jìn)去一個(gè)OuterClass的對象,賦值給InnerClass的一個(gè)字段,該字段是OuterClass對象的引用。大家想一下GC的原理,如果InnerClass對象沒有被標(biāo)記為垃圾對象,那么outer指向的OutClass對象沒有被標(biāo)記為垃圾對象,這樣就會(huì)導(dǎo)致Outer也不會(huì)是垃圾對象(GCRoot對InnerClass有引用,而InnerClass對OuterClass又由引用)
2、解決方案
- 將內(nèi)部類改為:靜態(tài)內(nèi)部類+弱引用
因?yàn)閮H僅改為靜態(tài)內(nèi)部類,我們會(huì)發(fā)現(xiàn)不能調(diào)用外部類的方法,所以我們加上弱引用,這樣就可以調(diào)用外部類的方法了。- 這里重點(diǎn)說下AsyncTask
如果使用AsyncTask靜態(tài)內(nèi)部類,需要保證AsyncTask的周期和Activity的周期保持一致,也就是在Activity的生命周期結(jié)束時(shí)將要將AsyncTask cancel掉,我個(gè)人已經(jīng)4年不用AsyncTask。- 這里重點(diǎn)說下Thread,如果是Thread,也不推薦將Thread生命成static,因?yàn)門hread位于GC根部,Delvik VM會(huì)和所有活動(dòng)線程保持hard references關(guān)系,所以運(yùn)行中的Thread絕不會(huì)被GC無端回收了,所以正確的解決方法是在自定義靜態(tài)內(nèi)部類的基礎(chǔ)上給線程加上取消機(jī)制,因?yàn)槲覀兛梢栽贏ctivity的onDestory方法中將Thread關(guān)閉掉。
- Timer和TimerTask沒有進(jìn)行Cancel,從而導(dǎo)致Timer和TimerTask會(huì)一直持有外部Activity的引用,所以要在適當(dāng)?shù)臅r(shí)機(jī)cancel。
(四)、Handler使用導(dǎo)致內(nèi)存泄露
1、問題
經(jīng)常會(huì)遇見Android程序中這樣使用handler:
public class CustomerActivity {
// 省略部分代碼
void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}
}, 1000);
}
View mBtn = findViewById(R.id.h_button);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createHandler();
nextActivity();
}
});
// 省略部分代碼
}
當(dāng)Appl啟動(dòng)以后,framework會(huì)首先幫助我們完成UI線程的消息循環(huán),也就是在UI線程中,Loop、MessageQueue、Message等等這些實(shí)例已經(jīng)由framework幫我們實(shí)現(xiàn)了。所有的App主要事件,比如Activity的生命周期方法、Button的點(diǎn)擊事件都包含在這個(gè)Message里面,這些Message都會(huì)加入到MessageQueue中去,所以,UI線程的消息循環(huán)貫穿于整個(gè)Application生命周期。因此當(dāng)你在UI線程中生成Handler的實(shí)例,就會(huì)持有Loop以及MessageQueue的引用。
2、解決方案:
將自定義的Handler和Runnable類生命成靜態(tài)內(nèi)部類,來解除和Activity的應(yīng)用。
(五)、Sensor Manager導(dǎo)致的內(nèi)存泄露:
1、問題
//省略部分代碼
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
registerListener();
nextActivity();
}
});
//省略部分代碼
通過Context調(diào)用的getSystemService獲取系統(tǒng)服務(wù),這些服務(wù)運(yùn)行在他們自己的進(jìn)程執(zhí)行一系列后臺(tái)工作或者提供和硬件交互的家口,如果Context對象需要在一個(gè)Service內(nèi)部事件發(fā)生時(shí)隨時(shí)受到通知,則需要把自己作為一個(gè)監(jiān)聽器注冊進(jìn)去,這樣服務(wù)就會(huì)持有一個(gè)Activity。如果開發(fā)者忘記了在Activity被銷毀錢注銷這個(gè)監(jiān)聽器,就會(huì)導(dǎo)致內(nèi)存泄露。
2、解決方案
在onDestroy方法里面注銷監(jiān)聽器。
(六)、檢測內(nèi)存泄露的工具:
LeakCanary
推薦使用LeakCanary來對內(nèi)存泄露進(jìn)行檢測。LeakCanary是非常好用的第三方庫,用來檢測內(nèi)存泄露,感興趣的朋友可以去查閱LeakCannary的使用方法,使用它來檢測App中內(nèi)存泄露。
感謝:
https://juejin.im/post/5865bfa1128fe10057e57c63
http://blog.csdn.net/mr_liabill/article/details/49872527
http://www.cnblogs.com/xgjblog/p/5462417.html
http://blog.csdn.net/feiduclear_up/article/details/47356289
http://www.cnblogs.com/android100/p/Android-Context.html
https://developer.android.com/reference/android/app/Activity.html
http://www.lxweimin.com/p/7e78c535f1f5
http://www.lxweimin.com/p/f24707874b04
http://www.lxweimin.com/p/94e0f9ab3f1d
http://www.lxweimin.com/p/994cd73bde53
http://blog.csdn.net/lmj623565791/article/details/40481055/
http://blog.csdn.net/qinjuning/article/details/7310620
http://blog.csdn.net/guolin_blog/article/details/47028975