【轉載請註明出處:http://blog.csdn.net/feiduclear_up CSDN 廢墟的樹】
PS:修該了一些有誤區的地方。
引言
Context對於Android開發人員來說並不陌生,項目中我們會經常使用Context來獲取APP資源,創建UI,獲取系統Service服務,啓動Activity,綁定Service,發送廣播,獲取APP信息等等。那麼Context到底是什麼?Context又是怎麼來實現以上功能的?在什麼場景下使用不同的Context?一個APP中總共有多少個Context?這篇博客將從源碼角度帶你分析Android Context到底是個啥。
1.Context是什麼
相信很多人多會問Context到底是什麼?
- 我們可以理解爲“上下文”:它貫穿整個應用;
- 也可以理解成“運行環境”:它提供了一個應用運行所需要的信息,資源,系統服務等;
- 同樣可以理解成“場景”:用戶操作和系統交互這一過程就是一個場景,比如Activity之間的切換,服務的啓動等都少不了Context。
然而以上這些都是我們從抽象角度去理解Context的作用,那麼從Code代碼來看Context到底是什麼呢?Activity是一個Context,Application是一個Context,Service也是一個Context你信麼?不信的話,同樣還是那句話“look the fuck resource code”。
public abstract class Context {
..............................
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
/** Return a Resources instance for your application's package. */
public abstract Resources getResources();
/** Return PackageManager instance to find global package information. */
public abstract PackageManager getPackageManager();
/** Return a ContentResolver instance for your application's package. */
public abstract ContentResolver getContentResolver();
...................
//獲取系統服務
public abstract Object getSystemService(@ServiceName @NonNull String name);
//發送廣播
public abstract void sendBroadcast(Intent intent);
//啓動Activity
public abstract void startActivity(Intent intent);
//啓動服務,綁定服務
public abstract ComponentName startService(Intent service);
public abstract boolean bindService(Intent service, @NonNull ServiceConnection conn,
@BindServiceFlags int flags);
................
}
從源碼看Context就是一個抽象類,裏面定義了各種抽象方法,包括獲取系統資源,獲取系統服務,發送廣播,啓動Activity,Service等。所以從源碼角度看Context就是抽象出一個App應用所有功能的集合,由於Context是一個純的抽象類,所以它的具體的方法實現是在其之類ContextImpl中實現了,稍後分析。我們平時在MainActivity中會這麼給mContext = this賦值,其言外之意就是當前Activity類就是Context,那說明Activity是Context的子類。通過Android Studio查看Context的子類圖如下:
有圖可知,Context的子類很多,我們主要分析以上紅色矩形框內的即可。接下來就分析Android系統中Context的繼承關係!
2.Android系統中Context的繼承關係
有上一節我們知道,Activity是一個Context,Service也是一個Context等等,那麼這些類跟Context具體什麼關係呢?接下來有一幅圖Context繼承關係圖來說明:
Context類是一個抽象類,具體實現在ContextImpl類中;而ContextWrapper是Context的一個包裝類,其裏面所有的方法實現都是調用其內部mBase變量的方法,而mBase就是ContextImpl對象,稍後分析。然而ContextWrapper還有一個ContextThemeWrapper子類,該類中擴展了主題相關的方法。有繼承關係圖可以看出,Application和Service是繼承自ContextWrapper,而Activity是繼承自ContextThemeWrapper,是不是有點奇怪?其實一點都不奇怪,Activity在啓動的時候系統都會加載一個主題,也就是我們平時在AndroidManifest.xml文件裏面寫的android:theme=”@style/AppTheme”屬性啦!然而Service和Applicaton都和UI界面並沒有卵關係!因此他們繼承自ContextWrapper。所以Activity,Application,Service其實都關聯着一個mBase變量,而mBase變量是ContextImpl對象的賦值,也是真正實現抽象類Context的地方。雖然Activity,Application,Service都有一個共同的祖先Context,但是他們自己本身持有的Context對象是不同的,接下來我們從源碼角度分析以上幾個類的實現。
3.不同Context源碼分析
3.1ContextImpl—真正實現Context功能的類
從源碼看出Context類僅僅是定義了一組抽象方法的抽象類,其內部的方法真正實現的地方都在ContextImpl類中。
class ContextImpl extends Context {
//整個App的主線程
final ActivityThread mMainThread;
//整個App的相關信息
final LoadedApk mPackageInfo;
//資源解析器
private final ResourcesManager mResourcesManager;
//App資源類
private final Resources mResources;
//外部Context的引用
private Context mOuterContext;
//默認主題
private int mThemeResource = 0;
private Resources.Theme mTheme = null;
//包管理器
private PackageManager mPackageManager;
................................
//以下是靜態區註冊系統的各種服務,多大五六十種系統服務,因此每個持有Context引用的對象都可以隨時通過getSystemService方法來輕鬆獲取系統服務。
static {
registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {
public Object getService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
public Object getService(ContextImpl ctx) {
return new CaptioningManager(ctx);
}});
registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
IAccountManager service = IAccountManager.Stub.asInterface(b);
return new AccountManager(ctx, service);
}});
........................
}
.................
//啓動Activity的地方
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
..........
//啓動服務的地方
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, mUser);
}
...............
}
分析:ContextImpl實現了抽象類Context裏面的所有方法,獲取資源,啓動Activity,Service等。值得注意的是在ContextImpl創建的時候就會利用靜態區來註冊系統的各種服務,因此每個持有Context引用的類都可以通過getSystemService來輕鬆的獲取系統服務了。比如我們平時LayoutInflater類來加載一個XML佈局時時這麼寫的
LayoutInflater inflater = LayoutInflater.from(mContext);
View layout = inflater.inflate(R.layout.activity_main,null);
其實源碼內部是這樣實現的:
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
可以看出LayoutInflater佈局加載器也是調用context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);來獲取系統服務得到的。因此我們以後在代碼中也可以這麼來加載一個XML佈局:
//獲取服務
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.activity_main,null);
由於ContextImpl是抽象類Context的具體實現,而Application,Activity,Service的祖先又都是Context類,那麼它們都關聯着一個ContextImpl對象來真正實現Context裏面所有的方法。現在來分析下不同子類創建的Context對象。
3.2ContextWrapper
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();
}
...............
}
分析:從ContextWrapper源碼可以看到,這個類只是一個裝飾類,其內部所有方法的實現都指向mBase成員變量,而然誰給mBase成員變量賦值呢?結論是Context的真正實現類ContextImpl對象,後面會分析到。該類中通過attachBaseContext方法將ContextImpl對象賦值給mBase成員變量。
每一個App應用都是由ASM通過Binder機制創建一個新的進程然後調用ActivityThread類中的main方法開始的。很多人可能會感到奇怪爲啥Android也是基於Java實現的,爲啥沒有看到main方法呢?其實整個App應用的入口在ActivityThread.main方法裏面啦!關於Activity啓動過程請參考大神級別人物老羅的這篇博客:Android應用程序的Activity啓動過程簡要介紹和學習計劃。所有有關Application,Activity,Service的創建都是在ActivityThread類中,其實該類就是我們App的主線程。
3.3Application中的Context
每一個應用在啓動的時候都會創建一個Application對象,該對象是全局的,開發者可以實現一個子類MyApplication類來繼承Application,然後實現一些全局的方法或者數據。
應用入口ActivityThread#main
public static void main(String[] args) {
................
//初始化Looper
Looper.prepareMainLooper();
//創建一個APP主線程ActivityThread對象
ActivityThread thread = new ActivityThread();
//初始化App應用信息
thread.attach(false);
//獲得主線程也就是UI線程的handler對象
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//此處值得注意了,在Android4.1版本之後添加了這麼一個方法,目的就是爲了能讓AsyncTask能在子線程創建,
//在之前的版本是不能在子線程中創建初始化AsyncTask的。
//對AsyncTask感興趣的童鞋可以參考這篇博客[ Android異步任務處理框架AsyncTask源碼分析](http://blog.csdn.net/feiduclear_up/article/details/46860015)
AsyncTask.init();
//啓動Looper循環,進入消息循環。
Looper.loop();
}
分析:main方法主要工作就是創建一個App應用的主線程ActivityThread並初始化,且構建一個消息循環機制用於處理UI交互。代碼第6-8行,創建了一個應用的主線程ActivityThread並且調用attach方法來初始化。進入attach方法:
ActivityThread#attach
private void attach(boolean system) {
//整個應用的Application對象
Application mInitialApplication;
//整個應用的後臺管家
Instrumentation mInstrumentation;
................
try {
mInstrumentation = new Instrumentation();
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
//利用ContextImpl創建整個應用的Application對象
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
//調用Application對象的onCreate方法
mInitialApplication.onCreate();
}
................
}
代碼第13行:通過調用LoadedApk#makeApplication方法創建應用程序的Application對象。
LoadedApk#makeApplication
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
//第一次進來mApplication==null條件不滿足,之後創建Activity的時候條件滿足直接返回當前Application對象
if (mApplication != null) {
return mApplication;
}
Application app = null;
try {
//爲Appliaction創建ContextImpl對象
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//調用Instrumentation類中的newApplication方法創建Application
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
//給ContextImpl設置外部引用
appContext.setOuterContext(app);
}
....................
return app;
}
分析:
1.代碼第4-6行:判斷當前應用是否是第一次創建Application對象,如果不是則直接返回Application對象,否則去創建第一個Application對象。目的是確保當前應用之創建了一個全局的Application對象。
2.代碼第13行:創建了一個ContextImpl對象,然後作爲參數用於創建Application對象。
3.代碼第15行:調用Instrumentation類中的newApplication方法來創建Application對象。
4.代碼第18行:將創建好的Application對象賦值給ContextImpl類的mOuterContext成員變量,目的是讓ContextImpl持有外部Application類的引用用於註冊系統服務或者其他方法。
Instrumentation#newApplication
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;
}
分析:以上代碼通過類加載器來創建Application對象,並且調用app.attach(context)方法來初始化Application對象。這裏的context就是上面傳下來的ContextImpl對象了。進入Application源碼
Application#attach
public class Application extends ContextWrapper implements ComponentCallbacks2 {
..................
/**
* @hide
*/
/* package */ final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
...............
}
分析:Application類是繼承自ContextWrapper類,在attach方法中調父類也就是ContextWrapper中的attachBaseContext方法來對ContextWrapper的成員變量mBase賦值成ContextImpl對象,具體可以參考3.2小節。因此Application通過父類ContextWrapper類的成員變量mBase指向了ContextImpl,讓Application類真正實現了其祖父類Context抽象類中的所有抽象方法。
3.4Activity中的Context
當Application創建完成之後,ASM會通過Binder機制通知ActivityThread去創建需要的Activity了。最後會輾轉到ActivityThread類中的performLaunchActivity方法來創建Activity。
ActivityThread#performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
........................
Activity activity = null;
try {
//通過Instrumentation類的newActivity方法來創建一個Activity對象
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
..........................
}
try {
//獲取當前應用的Application對象,該對象的唯一作用就是作爲參數傳遞到Activity裏,
然後在Activity類中可以獲得調用getApplication方法來獲取Application對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
.............................
if (activity != null) {
//爲Activity創建ContextImpl對象
Context appContext = createBaseContextForActivity(r, activity);
//爲Activity賦值初始化
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.voiceInteractor);
...................
//獲取當前應用的主題資源
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
//設置主題
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
//輾轉到Activity,調用Activity的生命週期onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
.............
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
//調用Activity的生命週期onStart方法
activity.performStart();
r.stopped = false;
}
......................
return activity;
}
分析:
- step1 這裏也是通過Instrumentation類的newActivity方法來創建一個Activity對象,跟上面創建Application對象基本類似,這裏就不貼源碼了。
- step2 之後在調用本地方法 createBaseContextForActivity去創建ContextImpl對象,該對象將作爲參數傳遞到Activity#attach方法中。
- step3 調用Activity#attach方法對剛創建好的Activity進行初始化操作。後面會分析Activity#attach方法。
- step4 獲取當前應用的主題資源,然後調用Activity#setTheme方法給剛創建好的Activity對象設置主題。
- setp5 一次調用Activity的生命週期方法onCreate,onStart。
step2
現在來分析下ActivityThread#createBaseContextForActivity方法。
ActivityThread#createBaseContextForActivity
private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
appContext.setOuterContext(activity);
Context baseContext = appContext;
.............
return baseContext;
}
分析:該方法裏面的確創建了一個ContextImpl對象,並且返回該對象。同時也調用了ContextImpl#setOuterConexet方法讓ContextImpl持有外部Activity對象的引用,目的是在ContextImpl類中註冊一些服務,設置主題等都需要外部Activity對象的引用。
setp3
由於Activity沒有重寫構造方法,因此創建出來的Activity並沒有初始化。爲了對Activity初始化,以上代碼調用了Activity#attach方法來進行初始化操作。
Activity#attach
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
............................
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, IVoiceInteractor voiceInteractor) {
//調用父類方法對mBase變量賦值
attachBaseContext(context);
//創建一個Activity的窗口
mWindow = PolicyManager.makeNewWindow(this);
//給Window窗口設置回調事件
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;
//此處注意,將整個應用的Application對象賦值給Activity的mApplication成員變量。
//目的是爲了能在Activity中通過getApplication方法來直接獲取Application對象
mApplication = application;
......................
}
//在Activity中返回當前應用的Application對象
/** Return the application that owns this activity. */
public final Application getApplication() {
return mApplication;
}
.......................
分析:attach方法一進來就調用了父類的attachBaseContext方法將ContextImpl對象註冊到Activity裏面去。由於Activity的父類是ContextThemeWrapper,進入該類查看attachBaseContext方法
ContextThemeWrapper#attachBaseContext
public class ContextThemeWrapper extends ContextWrapper {
............
@Override protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
...........
該方法也很簡單,還是調用父類的attachBaseContext方法註冊ContextImpl,然而該類的父類就是ContextWrapper類。
ContextWrapper#attachBaseContext
public class ContextWrapper extends Context {
..............
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
..........
}
在ContextWrapper類中調用attachBaseContext方法將ContextImpl對象(真正實現Context抽象類裏面各種方法的類)賦值給mBase成員變量,而ContextWrapper只是一個裝飾類,裏面所有方法的實現都是調用mBase的方法。此時mBase方法被賦值成ContextImpl對象,這麼一來Activity的祖父類就實現了裏面的所有Context抽象方法,那麼在Activity中可以調用Context裏面的任何方法了。
3.5 Service中的Context
同樣創建Service也是有ASM通過Binder機制通知ActivityThread類去創建一個Service服務了,最後會輾轉到ActivityThread#handleCreateService方法中來創建一個Service服務。
ActivityThread#handleCreateService
private void handleCreateService(CreateServiceData data) {
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
//通過類加載器創建Service服務
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
}
.............
try {
............
//此處爲Service創建一個ContextImpl對象
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
//同樣爲ContextImpl類設置外部對象,目的還是讓ContextImpl持有外部類的引用
//在ContextImpl類中的許多方法需要使用到外部Context對象引用
context.setOuterContext(service);
................
//獲得當前應用的Applicaton對象,該對象在整個應用中只有一份,是共享的。
Application app = packageInfo.makeApplication(false, mInstrumentation);
//將ContextImpl對象和Application對象作爲attach方法參數來初始化Service
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
//Service初始化完成之後系統自動調用onCreate生命週期方法
service.onCreate();
................
}
分析:
跟以上Activity和Application創建類似,通過類加載器來創建Service對象。然後在創建一個ContextImpl對象,並且爲ContextImpl類設置外部Service對象Context的引用,目的之在ContextImpl類中的許多方法都需要使用到外部Context引用。
其次和Activity一樣調用packageInfo.makeApplication方法去獲得當前應用的Application對象,然後將Application對象和ContextImpl對象作爲Service#attach方法去初始化Service,當Service初始化完成之後,系統調用Service的生命週期方法onCreate方法,該方法是創建Service過程中最早暴露給開發者的,所有開發者在實現自己的Service時可以重寫onCreate方法來進行一些初始化工作。
現在我們來分析下Service#attach方法
Service#attach
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
//默認構造方法調用父類構造方法並且參數爲null,意味着構造方法裏並沒有對Service初始化
public Service() {
super(null);
}
//在Service中返回整個應用的Application對象
/** Return the application that owns this service. */
public final Application getApplication() {
return mApplication;
}
..............
/**
* @hide
*/
public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
//調用父類方法去註冊ContextImpl對象
attachBaseContext(context);
mThread = thread; // NOTE: unused - remove?
mClassName = className;
mToken = token;
//將整個應用的Applicaton對象賦值給Service類的成員變量mApplication
mApplication = application;
mActivityManager = (IActivityManager)activityManager;
}
分析:
Service的父類是ContextWrapper,在attach方法中調用了父類中attachBaseContext方法去註冊CotextImpl對象,這一操作和創建Application一樣,具體參考前面分析。
3.6總結
至此,一個Android應用可能創建Context的地方基本分析結束。所有有關創建Context對象的地方都是在ActivityThread類中,該類就是整個應用的入口,也是整個應用的主線程。每個應用首先會創建一個Application對象,且一個應用只有唯一一個Application對象,之後再根據需求創建Activity或者Service。且在創建Activity或者Service的時候都會持有一份當前應用的Application對象,通過getApplication方法即可獲得。
不管在創建Application,Activity還是Service的時候都會去創建一個ContextImpl對象(真正實現抽象類Context功能的類就是ContextImpl),然後將該對象註冊到對應的Application,Activity,Service中,之後在Application,Activity,Service類中就可以使用Context的所有功能了。所以Application,Activity,Service的祖先都是抽象類Context,相當於Context給了他們身體,讓他們有了一個軀殼有思想,但真正讓他們思想得到執行的類還是ContextImpl類。因此我們可以這麼來理解:抽象類Context給了Application,Activity,Service思想,而ContextImpl類給了他們去執行思想的功能。
有以上分析我們知道,每創建一個Application,Activity還是Service都會創建一個ContextImpl類(真正實現Context類功能)。且一個應用只會創建一個Application對象。因此:一個App中Context的個數=1個Application+Activity的個數+Service的個數。
【轉載請註明出處:http://blog.csdn.net/feiduclear_up CSDN 廢墟的樹】
4.Context應用場景
雖然Application,Activity,Service的祖先都是Context,並不是每個Context對象都是相同的。
- Application:一個應用在創建的時候只會創建一個ActivityThread主線程,而在初始化ActivityThread主線程的時候就會創建一個Application對象。Application是全局的,在Activity和Service裏都可以調用getApplication方法來獲得一個應用的Application對象。Application的父類是ContextWrapper類。
- **Service:**Service父類是ContextWrapper,一個應用每創建一個Service,都會創建一個ContextImpl類去關聯Service。
- **Activity:**Activity父類是ContextThemeWrapper,而ContextThemeWrapper父類是ContextWrapper。ContextThemeWrapper類是其父類的擴張,裏面額外添加了關於主題設置的一些方法。在ActivityThread主線程中創建Activity的時候我們知道,創建完了Activity之後會立馬調用Activity#setTheme設置Activity的主題。
不同Context的應用場景如下圖標
功能 | Application | Service | Activity |
---|---|---|---|
Start an Activity | NO1 | NO1 | YES |
Show a Dialog | NO | NO | YES |
Layout Inflation | YES | YES | YES |
Start a Service | YES | YES | YES |
Bind to a Service | YES | YES | YES |
Send a Broadcast | YES | YES | YES |
Register BroadcastReceiver | YES | YES | YES |
Load Resource Values | YES | YES | YES |
解釋:
- NO1表示Application和Service可以啓動一個Activity,但是需要創建一個新的task。比如你在Application中調用startActivity(intent)時系統會報如下錯誤:
java.lang.RuntimeException: Unable to create application
com.xjp.toucheventdemo.MyApplication: android.util.AndroidRuntimeException: Calling
startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK
flag. Is this really what you want?
意思就是當在Activity以外的環境啓動一個新的Activity的時候需要給Intent添加一個FLAG_ACTIVITY_NEW_TASK標記,該標記的作用就是爲當前需要啓動的Activity創建一個新的task任務隊列。
- 除了Activity可以創建一個Dialog,其他都不可以創建Dialog。比如在Application中創建Dialog會報如下錯誤:
Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
原因是因爲在創建Dialog的時候會使用到Context對象去獲取當前主題信息,但是我們知道Application和Service是繼承自ContextWrapper,沒有實現關於主題的功能,然而Activity是繼承自ContextThemeWrapper,該類是實現了關於主題功能的,因此創建Dialog的時候必須依附於Activity的Context引用。
5.getApplication和getApplicationContext區別
很多人對getApplication方法和getApplicationContext感到疑惑,這兩個方法返回值有什麼不同?什麼場合使用什麼樣的方法?這裏我們從源碼角度告訴大家:這兩個方法返回值都是指向同一個Application對象,僅僅是返回類型和實現方法的地方不同而已。
5.1 getApplication
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
..............
/** Return the application that owns this activity. */
public final Application getApplication() {
return mApplication;
}
...........
getApplication方法實現是在Activity類中,且返回值是當前應用的Application對象。該對象是在ActivityThread類創建Activity時傳遞下來的。具體可以參考3.4小節。
5.2 getApplicationContext
public class ContextWrapper extends Context {
.............
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
.............
}
getApplicationContext方法是在ContextWrapper類中實現,由於mBase變量指向的是ContextImpl對象,因此真正實現的地方是ContextImpl類中
class ContextImpl extends Context {
................
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
..........
}
該方法返回的是Context類型,且返回值也是調用mPackageInfo.getApplication()或者mMainThread.getApplication()。
public final class LoadedApk {
............
Application getApplication() {
return mApplication;
}
..........
}
可以看出返回的是當前應用的Application對象,由於一個應用只有一份LoadedApk對象,此處返回的也是系統的唯一Application對象。
public final class ActivityThread {
.................
public Application getApplication() {
return mInitialApplication;
}
................
}
可以看出返回值也是當前應用的Application對象,由於一個應用只有一個主線程,此處返回的也是系統的唯一Application對象。
因此:getApplication和getApplicationContext方法返回的對象都是指向當前應用的Application對象,是同一個Application對象,僅僅是返回值類型不同而已。
查看源碼會發現getApplication方法是在Activity,Service類中實現的;而getApplicationContext方法是在ContextWrapper類中實現的。也就是getApplication方法是在Context子類中實現的,而getApplicationContext是在父類中實現的,從而導致兩個方法的使用範圍是不一樣的。你可以這麼調用
context.getApplicationContext();
但是卻不可以這麼調用
context.getApplication();
因爲getApplication方法是Context子類中實現的,所以你必須這樣調用
((Activity)context).getApplication();
6.Context內存泄漏問題
在項目中,我們經常會遇到使用單例模式或者靜態static變量,雖然使用靜態類或者靜態變量很方便,但是也潛在很多的內存泄漏問題。
6.1靜態資源導致的內存泄漏
你可能遇到以下這段代碼:
public class MyCustomResource {
//靜態變量drawable
private static Drawable drawable;
private View view;
public MyCustomResource(Context context) {
Resources resources = context.getResources();
drawable = resources.getDrawable(R.drawable.ic_launcher);
view = new View(context);
view.setBackgroundDrawable(drawable);
}
}
請問,這段代碼有什麼問題?乍一看貌似沒啥問題,挺好的啊!其實不然,主要的問題在於view.setBackgroundDrawable方法裏面,我們知道靜態變量在整個應用的內存裏只保存一份,一旦創建就不會釋放該變量的內存,直到整個應用都銷燬纔會釋放static靜態變量的內存。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...........
public void setBackgroundDrawable(Drawable background) {
..........
/**此處的this就是當前View對象,而View對象又是有Context對象獲得
因此,變量background持有View對象的引用,View持有Context的引用,
所有background間接持有Context對象的引用了*/
background.setCallback(this);
.......
}
..........
}
setBackgroundDrawable方法內部調用了background.setCallback(this)回調方法,而該參數this就是View的對象,由於background是一個靜態變量,會一直持有View對象的引用,而然View對象又是由Context對象創建出來的,因此background會間接持有Context的對象的引用,也就意味着如果該Context對應的Activity退出finish掉的時候其實該Activity是不能完全釋放內存的,因爲靜態變量drawable持有該Activity的Context對象的間接引用。從而導致該Activity內存無法回收,導致內存泄漏隱患。因爲Activity就是Context,所有Context的生命週期和Activity是一樣長的,我們希望Activity退出時Context也釋放內存,這樣纔不會導致內存泄漏隱患。那麼以上這段代碼是不安全的。值得注意的是:以上代碼是由於靜態資源drawable持有View對象的引用導致內存泄漏隱患的,並不是由於context.getResource導致內存泄漏,因此如果你想通過context.getApplicaitonContext來獲取getResource是解決不了內存泄漏的。因此,Android系統在在3.0版本之後修改了setBackgroundDrawable內部方法中的 background.setCallback(this);方法,裏面的實現使用了弱引用來持有View對象的引用,從而避免了內存泄漏隱患。所以,以後代碼中避免使用靜態資源,或者使用弱引用來解決相應的問題也是可以的。
6.2 單例模式導致內存泄漏
相信單例模式對開發者很有誘惑力吧!或多或少在項目中都有用過單例模式。你也可能見過一下這段代碼:
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
同樣,以上代碼也存在內存泄漏的隱患。因爲單例模式使用的是靜態類的方式,讓該對象在整個應用的內存中保持一份該對象,從而減少對多次創建對象帶來的資源浪費。同樣的問題:在創建該單例的時候使用了生命週期端的Context對象的引用,如果你是在Application中創建以上單例的話是木有任何問題的。因爲Application的Context生命週期是整個應用,和單例的生命週期一樣,因此不會導致內存泄漏。但是,如果你是在Activity中創建以上單例的話,就會導致和6.1小節一樣的問題—內存泄漏。所以我們同樣可以將代碼修改成如下:
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context.getApplicationContext());
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
6.3 總結
以後在使用Context對象獲取靜態資源,創建單例對象或者靜態方法的時候,請多考慮Context的生命週期,一定要記得不要使用Activity的Context,切記要使用生命週期長的Application的Context對象。但是並不是所有情況使用Application的Context對象,比如第4小節,在創建Dialog,View控件的時候都必須使用Activity的Context對象。
Context總結
- Context是什麼?Context是”運行上下文環境“,從代碼角度看Application,Service,Activity都是Context。
- 所有Context都是在應用的主線程ActivityThread中創建的,由於Application,Service,Activity的祖先都是Context抽象類,所以在創建它們的同時也會爲每一個類創建一個ContextImpl類,ContextImpl是Context的之類,真正實現Context功能方法的類。因此Application,Service,Activity都關聯着一個ContextImpl對象。
- 儘量少用Context對象去獲取靜態變量,靜態方法,以及單例對象。以免導致內存泄漏。
在創建與UI相關的地方,比如創建一個Dialog,或者在代碼中創建一個TextView,都用Activity的Context去創建。然而在引用靜態資源,創建靜態方法,單例模式等情況下,使用生命週期更長的Application的Context纔不會導致內存泄漏。
【轉載請註明出處:http://blog.csdn.net/feiduclear_up CSDN 廢墟的樹】