Android Context 是什麼?

【轉載請註明出處: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;
    }

分析:

  1. step1 這裏也是通過Instrumentation類的newActivity方法來創建一個Activity對象,跟上面創建Application對象基本類似,這裏就不貼源碼了。
  2. step2 之後在調用本地方法 createBaseContextForActivity去創建ContextImpl對象,該對象將作爲參數傳遞到Activity#attach方法中。
  3. step3 調用Activity#attach方法對剛創建好的Activity進行初始化操作。後面會分析Activity#attach方法。
  4. step4 獲取當前應用的主題資源,然後調用Activity#setTheme方法給剛創建好的Activity對象設置主題。
  5. 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總結

  1. Context是什麼?Context是”運行上下文環境“,從代碼角度看Application,Service,Activity都是Context。
  2. 所有Context都是在應用的主線程ActivityThread中創建的,由於Application,Service,Activity的祖先都是Context抽象類,所以在創建它們的同時也會爲每一個類創建一個ContextImpl類,ContextImpl是Context的之類,真正實現Context功能方法的類。因此Application,Service,Activity都關聯着一個ContextImpl對象。
  3. 儘量少用Context對象去獲取靜態變量,靜態方法,以及單例對象。以免導致內存泄漏。
  4. 在創建與UI相關的地方,比如創建一個Dialog,或者在代碼中創建一個TextView,都用Activity的Context去創建。然而在引用靜態資源,創建靜態方法,單例模式等情況下,使用生命週期更長的Application的Context纔不會導致內存泄漏。

    【轉載請註明出處:http://blog.csdn.net/feiduclear_up CSDN 廢墟的樹】

發佈了74 篇原創文章 · 獲贊 372 · 訪問量 75萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章