Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android應用Context詳解及源碼解析

Android應用Context詳解及源碼解析

編輯:關於Android編程

 

1 背景

今天突然想起之前在上家公司(做TV與BOX盒子)時有好幾個人問過我關於Android的Context到底是啥的問題,所以就馬上要誕生這篇文章。我們平時在開發App應用程序時一直都在使用Context(別說你沒用過,訪問當前應用的資源、啟動一個activity等都用到了Context),但是很少有人關注過這玩意到底是啥,也很少有人知道getApplication與getApplicationContext方法有啥區別,以及一個App到底有多少個Context等等的細節。

更為致命的是Context使用不當還會造成內存洩漏。所以說完全有必要拿出來單獨分析分析(基於Android 5.1.1 (API 22)源碼分析)。

 

2 Context基本信息

2-1 Context概念

先看下源碼Context類基本情況,如下:

/**
 * 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 {
    ......
}

從源碼注釋可以看見,Context提供了關於應用環境全局信息的接口。它是一個抽象類,它的執行被Android系統所提供。它允許獲取以應用為特征的資源和類型,是一個統領一些資源(應用程序環境變量等)的上下文。

看見上面的Class OverView了嗎?翻譯就是說,它描述一個應用程序環境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現類;通過它我們可以獲取應用程序的資源和類(包括應用級別操作,如啟動Activity,發廣播,接受Intent等)。

既然上面Context是一個抽象類,那麼肯定有他的實現類咯,我們在Context的源碼中通過IDE可以查看到他的子類如下:

這裡寫圖片描述

嚇尿了,737個子類,經過粗略浏覽這些子類名字和查閱資料發現,這些子類無非就下面一些主要的繼承關系。這737個類都是如下關系圖的直接或者間接子類而已。如下是主要的繼承關系:

這裡寫圖片描述

從這裡可以發現,Service和Application的類繼承類似,Activity繼承ContextThemeWrapper。這是因為Activity有主題(Activity提供UI顯示,所以需要主題),而Service是沒有界面的服務。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPsv50tTLtaOsztLDx7TT1eLVxdb30qq52M+1zbzI68rWwLS31s72Q29udGV4dM/gudjUtMLroaM8L3A+DQo8aDMgaWQ9"2-2-context之間關系源碼概述">2-2 Context之間關系源碼概述

有了上述通過IDE查看的大致關系和圖譜之後我們在源碼中來仔細看下這些繼承關系。

先來看下Context類源碼注釋:

/**
 * 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 {
    ......
}

看見沒有,抽象類Context ,提供了一組通用的API。

再來看看Context的實現類ContextImpl源碼注釋:

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context {
    private Context mOuterContext;
    ......
}

該類實現了Context類的所有功能。

再來看看Context的包裝類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;
    }
    ......
}

該類的構造函數包含了一個真正的Context引用(ContextImpl對象),然後就變成了ContextImpl的裝飾著模式。

再來看看ContextWrapper的子類ContextThemeWrapper源碼注釋:

/**
 * A ContextWrapper that allows you to modify the theme from what is in the 
 * wrapped context. 
 */
public class ContextThemeWrapper extends ContextWrapper {
    ......
}

該類內部包含了主題Theme相關的接口,即android:theme屬性指定的。

再來看看Activity、Service、Application類的繼承關系源碼:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
    ......
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
    ......
}
public class Application extends ContextWrapper implements ComponentCallbacks2 {
    ......
}

看見沒有?他們完全符合上面我們繪制的結構圖與概述。

2-3 解決應用Context個數疑惑

有了上面的Context繼承關系驗證與分析之後我們來看下一個應用程序到底有多個Context?

Android應用程序只有四大組件,而其中兩大組件都繼承自Context,另外每個應用程序還有一個全局的Application對象。所以在我們了解了上面繼承關系之後我們就可以計算出來Context總數,如下:

APP Context總數 = Application數(1) + Activity數(Customer) + Service數(Customer);

到此,我們也明確了Context是啥,繼承關系是啥樣,應用中Context個數是多少的問題。接下來就有必要繼續深入分析這些Context都是怎麼來的。

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請注明出處,尊重分享成果】

3 各種Context在ActivityThread中實例化過程源碼分析

在開始分析之前還是和《Android異步消息處理機制詳解及源碼分析》的3-1-2小節及《Android應用setContentView與LayoutInflater加載解析機制源碼分析》的2-6小節一樣直接先給出關於Activity啟動的一些概念,後面會寫文章分析這一過程。

Context的實現是ContextImpl,Activity與Application和Service的創建都是在ActivityThread中完成的,至於在ActivityThread何時、怎樣調運的關系後面會寫文章分析,這裡先直接給出結論,因為我們分析的重點是Context過程。

3-1 Activity中ContextImpl實例化源碼分析

通過startActivity啟動一個新的Activity時系統會回調ActivityThread的handleLaunchActivity()方法,該方法內部會調用performLaunchActivity()方法去創建一個Activity實例,然後回調Activity的onCreate()等方法。所以Activity的ContextImpl實例化是在ActivityThread類的performLaunchActivity方法中,如下:

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
            //已經創建好新的activity實例
            if (activity != null) {
                //創建一個Context對象
                Context appContext = createBaseContextForActivity(r, activity);
                ......
                //將上面創建的appContext傳入到activity的attach方法
                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);
                ......
            }
        ......
        return activity;
    }

看見上面performLaunchActivity的核心代碼了嗎?通過createBaseContextForActivity(r, activity);創建appContext,然後通過activity.attach設置值。

具體我們先看下createBaseContextForActivity方法源碼,如下:

    private Context createBaseContextForActivity(ActivityClientRecord r,
            final Activity activity) {
        //實質就是new一個ContextImpl對象,調運ContextImpl的有參構造初始化一些參數    
        ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
        //特別特別留意這裡!!!
        //ContextImpl中有一個Context的成員叫mOuterContext,通過這條語句就可將當前新Activity對象賦值到創建的ContextImpl的成員mOuterContext(也就是讓ContextImpl內部持有Activity)。
        appContext.setOuterContext(activity);
        //創建返回值並且賦值
        Context baseContext = appContext;
        ......
        //返回ContextImpl對象
        return baseContext;
    }

再來看看activity.attach,也就是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) {
        //特別特別留意這裡!!!
        //與上面createBaseContextForActivity方法中setOuterContext語句類似,不同的在於:
        //通過ContextThemeWrapper類的attachBaseContext方法,將createBaseContextForActivity中實例化的ContextImpl對象傳入到ContextWrapper類的mBase變量,這樣ContextWrapper(Context子類)類的成員mBase就被實例化為Context的實現類ContextImpl
        attachBaseContext(context);
        ......
    }

通過上面Activity的Context實例化分析再結合上面Context繼承關系可以看出:

Activity通過ContextWrapper的成員mBase來引用了一個ContextImpl對象,這樣,Activity組件以後就可以通過這個ContextImpl對象來執行一些具體的操作(啟動Service等);同時ContextImpl類又通過自己的成員mOuterContext引用了與它關聯的Activity,這樣ContextImpl類也可以操作Activity。

SO,由此說明一個Activity就有一個Context,而且生命周期和Activity類相同(記住這句話,寫應用就可以避免一些低級的內存洩漏問題)。

3-2 Service中ContextImpl實例化源碼分析

寫APP時我們通過startService或者bindService方法創建一個新Service時就會回調ActivityThread類的handleCreateService()方法完成相關數據操作(具體關於ActivityThread調運handleCreateService時機等細節分析與上面Activity雷同,後邊文章會做分析)。具體handleCreateService方法代碼如下:

    private void handleCreateService(CreateServiceData data) {
        ......
        //類似上面Activity的創建,這裡創建service對象實例
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = (Service) cl.loadClass(data.info.name).newInstance();
        } catch (Exception e) {
            ......
        }

        try {
            ......
            //不做過多解釋,創建一個Context對象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            //特別特別留意這裡!!!
            //ContextImpl中有一個Context的成員叫mOuterContext,通過這條語句就可將當前新Service對象賦值到創建的ContextImpl的成員mOuterContext(也就是讓ContextImpl內部持有Service)。
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            //將上面創建的context傳入到service的attach方法
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            service.onCreate();
            ......
        } catch (Exception e) {
            ......
        }
    }

再來看看service.attach,也就是Service中的attach方法,如下:

    public final void attach(
            Context context,
            ActivityThread thread, String className, IBinder token,
            Application application, Object activityManager) {
            //特別特別留意這裡!!!
            //與上面handleCreateService方法中setOuterContext語句類似,不同的在於:
            //通過ContextWrapper類的attachBaseContext方法,將handleCreateService中實例化的ContextImpl對象傳入到ContextWrapper類的mBase變量,這樣ContextWrapper(Context子類)類的成員mBase就被實例化為Context的實現類ContextImpl
        attachBaseContext(context);
        ......
    }

可以看出步驟流程和Activity的類似,只是實現細節略有不同而已。

SO,由此說明一個Service就有一個Context,而且生命周期和Service類相同(記住這句話,寫應用就可以避免一些低級的內存洩漏問題)。

3-3 Application中ContextImpl實例化源碼分析

當我們寫好一個APP以後每次重新啟動時都會首先創建Application對象(每個APP都有一個唯一的全局Application對象,與整個APP的生命周期相同)。創建Application的過程也在ActivityThread類的handleBindApplication()方法完成相關數據操作(具體關於ActivityThread調運handleBindApplication時機等細節分析與上面Activity雷同,後邊文章會做分析)。而ContextImpl的創建是在該方法中調運LoadedApk類的makeApplication方法中實現,LoadedApk類的makeApplication()方法中源代碼如下:

    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        //只有新創建的APP才會走if代碼塊之後的剩余邏輯
        if (mApplication != null) {
            return mApplication;
        }
        //即將創建的Application對象
        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)) {
                initializeJavaContextClassLoader();
            }
            //不做過多解釋,創建一個Context對象
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            //將Context傳入Instrumentation類的newApplication方法
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            //特別特別留意這裡!!!
            //ContextImpl中有一個Context的成員叫mOuterContext,通過這條語句就可將當前新Application對象賦值到創建的ContextImpl的成員mOuterContext(也就是讓ContextImpl內部持有Application)。
            appContext.setOuterContext(app);
        } catch (Exception e) {
            ......
        }
        ......
        return app;
    }

接著看看Instrumentation.newApplication方法。如下源碼:

    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        return newApplication(cl.loadClass(className), context);
    }

繼續看重載兩個參數的newApplication方法,如下:

    static public Application newApplication(Class clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        ......
        //繼續傳遞context
        app.attach(context);
        return app;
    }

繼續看下Application類的attach方法,如下:

final void attach(Context context) {
        //特別特別留意這裡!!!
        //與上面makeApplication方法中setOuterContext語句類似,不同的在於:
        //通過ContextWrapper類的attachBaseContext方法,將makeApplication中實例化的ContextImpl對象傳入到ContextWrapper類的mBase變量,這樣ContextWrapper(Context子類)類的成員mBase就被實例化為Application的實現類ContextImpl
        attachBaseContext(context);
        ......
    }

可以看出步驟流程和Activity的類似,只是實現細節略有不同而已。

SO,由此說明一個Application就有一個Context,而且生命周期和Application類相同(然而一個App只有一個Application,而且與應用生命周期相同)。

4 應用程序APP各種Context訪問資源的唯一性分析

你可能會有疑問,這麼多Context都是不同實例,那麼我們平時寫App時通過context.getResources得到資源是不是就不是同一份呢?下面我們從源碼來分析下,如下:

class ContextImpl extends Context {
    ......
    private final ResourcesManager mResourcesManager;
    private final Resources mResources;
    ......
    @Override
    public Resources getResources() {
        return mResources;
    }
    ......
}

看見沒,有了上面分析我們可以很確定平時寫的App中context.getResources方法獲得的Resources對象就是上面ContextImpl的成員變量mResources。那我們追蹤可以發現mResources的賦值操作如下:

    private ContextImpl(ContextImpl container, ActivityThread mainThread,
            LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
            Display display, Configuration overrideConfiguration) {
        ......
        //單例模式獲取ResourcesManager對象
        mResourcesManager = ResourcesManager.getInstance();
        ......
        //packageInfo對於一個APP來說只有一個,所以resources 是同一份
        Resources resources = packageInfo.getResources(mainThread);
        if (resources != null) {
            if (activityToken != null
                    || displayId != Display.DEFAULT_DISPLAY
                    || overrideConfiguration != null
                    || (compatInfo != null && compatInfo.applicationScale
                            != resources.getCompatibilityInfo().applicationScale)) {
                //mResourcesManager是單例,所以resources是同一份
                resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                        packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                        packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                        overrideConfiguration, compatInfo, activityToken);
            }
        }
        //把resources賦值給mResources
        mResources = resources;
        ......
    }

由此可以看出在設備其他因素不變的情況下我們通過不同的Context實例得到的Resources是同一套資源。

PS一句,同樣的分析方法也可以發現Context類的packageInfo對於一個應用來說也只有一份。感興趣可以自行分析。

5 應用程序APP各種Context使用區分源碼分析

5-1 先來解決getApplication和getApplicationContext的區別

很多人一直區分不開這兩個方法的區別,這裡從源碼來分析一下,如下:

首先來看getApplication方法,你會發現Application與Context都沒有提供該方法,這個方法是哪提供的呢?我們看下Activity與Service中的代碼,可以發下如下:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
    ......
    public final Application getApplication() {
        return mApplication;
    }
    ......
}
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
    ......
    public final Application getApplication() {
        return mApplication;
    }
    ......
}

Activity和Service提供了getApplication,而且返回類型都是Application。這個mApplication都是在各自類的attach方法參數出入的,也就是說這個mApplication都是在ActivityThread中各自實例化時獲取的makeApplication方法返回值。

所以不同的Activity和Service返回的Application均為同一個全局對象。

再來看看getApplicationContext方法,如下:

class ContextImpl extends Context {
    ......
    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }
    ......
}

可以看到getApplicationContext方法是Context的方法,而且返回值是Context類型,返回對象和上面通過Service或者Activity的getApplication返回的是一個對象。

所以說對於客戶化的第三方應用來說兩個方法返回值一樣,只是返回值類型不同,還有就是依附的對象不同而已。

5-2 各種獲取Context方法的差異及開發要點提示

可以看出來,Application的Context生命周期與應用程序完全相同。
Activity或者Service的Context與他們各自類生命周期相同。

所以說對於Context使用不當會引起內存洩漏。

譬如一個單例模式的自定義數據庫管理工具類需要傳入一個Context,而這個數據庫管理對象又需要在Activity中使用,如果我們傳遞Activity的Context就可能造成內存洩漏,所以需要傳遞Application的Context。

6 Context分析總結

到此整個Android應用的Context疑惑就完全解開了,同時也依據源碼分析結果給出了平時開發APP中該注意的內存洩漏問題提示與解決方案。相信通過這一篇你在開發APP時對於Context的使用將不再迷惑。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved