Context尚猿,ContextImpl等的一些理解

版權(quán)聲明:本文為博主原創(chuàng)文章逼蒙,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議澈蝙,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明吓坚。
本文鏈接:http://www.reibang.com/p/674682b1976f

背景:我們經(jīng)常在Activity中會(huì)得到一些系統(tǒng)服務(wù)或者得到Resources,比如ACTIVITY_SERVICE灯荧,常常獲取的方式有好幾種礁击,如下:

//得到ACTIVITY_SERVICE
getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
this.getSystemService(Context.ACTIVITY_SERVICE);
getBaseContext().getSystemService(Context.ACTIVITY_SERVICE);
//得到Resources
getResources();
getBaseContext().getResources();
getApplicationContext().getResources();

這時(shí)候就有一種疑問(wèn),最后返回的是同一個(gè)對(duì)象嗎,如果不是哆窿,又會(huì)有什么差異链烈。
要想弄清上面的問(wèn)題,先得明白Context更耻,ContextImpl测垛,ContextWrapper捏膨,ContextThemeWrapper秧均,Activity,Application等等的關(guān)系号涯。

public abstract class Context {
        public abstract Resources getResources();
        public abstract Object getSystemService(@ServiceName @NonNull String name);
}

class ContextImpl extends Context {
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
    @Override
    public Resources getResources() {
        return mResources;
    }
}

public class ContextWrapper extends Context {
    Context mBase;
    @Override
    public Resources getResources() {
        return mBase.getResources();
    }
    @Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }
}

public class ContextThemeWrapper extends ContextWrapper {
    @Override
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }
}

public class Activity extends ContextThemeWrapper{
}

從上面的代碼可以知道目胡,Context是一個(gè)抽象類(lèi),它的很多方法链快,比如getResources誉己,getSystemService等都是抽象方法。ContextWrapper類(lèi)和ContextImpl類(lèi)都繼承了Context域蜗,但是他們的實(shí)現(xiàn)方法不一樣巨双。ContextWrapper中主要是調(diào)用mBase對(duì)應(yīng)的方法,而mBase也是一個(gè)Context對(duì)象霉祸。
其實(shí)通過(guò)研讀ActivityThread的performLaunchActivity(參考http://www.reibang.com/p/76f94c6452c0)我們不難明白筑累,Activity中對(duì)應(yīng)的Context實(shí)例對(duì)象是其實(shí)都是ContextImpl對(duì)象,ContextImpl也是真正實(shí)現(xiàn)Context的唯一對(duì)象丝蹭。且ContextWrapper中的mBase對(duì)象實(shí)際上也是ContextImpl慢宗。ContextWrapper顧名思義,它只是Context的一個(gè)包裹奔穿,最終調(diào)用到的也是ContextImpl中的實(shí)現(xiàn)镜沽。而Activity也繼承了ContextWrapper,所以最后殊途同歸贱田,所有的調(diào)用最終調(diào)用到了ContextImpl中缅茉。
那Application又合Context有什么關(guān)系呢,如下:

public class Application extends ContextWrapper{
}

可以看到Application也繼承ContextWrapper男摧,ContextWrapper繼承Context蔬墩,所以通過(guò)調(diào)用getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);最終調(diào)用到的是Application中的mBase的對(duì)應(yīng)的getSystemService方法,mBase它是Context對(duì)象彩倚,也是ContextImpl實(shí)例筹我,那它是如何設(shè)置為ContextImpl實(shí)例的呢,Application中有一attach函數(shù)帆离,參數(shù)為context蔬蕊,它的調(diào)用是LoadedApk中的makeApplication時(shí)調(diào)用的

    final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
            Application app = null;
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
}

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中最終也是調(diào)用到了ContextImpl中去。
至此,我們明白了getSystemService岸夯,無(wú)論是Activity的直接getSystemService()調(diào)用麻献,還是通過(guò)getBaseContext調(diào)用還是getApplicationContext()調(diào)用,最終都是調(diào)用到了ContextImpl中去猜扮。我們來(lái)看看具體的代碼跟蹤勉吻。
getSystemService代碼跟蹤,Activity中有WINDOW_SERVICE和SEARCH_SERVICE的緩存旅赢,直接返回

@Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }
//super.getSystemService(name)
@Override
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

可以看到最后是調(diào)用到了getBaseContext()的getSystemService齿桃,getBaseContext返回的是mBase對(duì)象,mBase對(duì)象是ContextImpl(Context)對(duì)象煮盼。它是在Activity的attach的時(shí)候設(shè)置的(參考http://www.reibang.com/p/76f94c6452c0

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);
}

所以最終調(diào)用到了ContextImpl的getSystemService短纵。
在看ContextImpl的getSystemService之前,先來(lái)看看通過(guò)Application的調(diào)用

//Activity中
getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);

@Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }

我們知道Activity中的mBase是ContextImpl對(duì)象僵控,那么它的getApplicationContext返回什么對(duì)象呢

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

mPackageInfo是一個(gè)LoadedApk對(duì)象香到,它返回的mApplication對(duì)象就是調(diào)用makeApplication生成的Application對(duì)象。(見(jiàn)上面講解代碼)

private Application mApplication;
Application getApplication() {
        return mApplication;
    }

即也就是調(diào)用到了Application中的getSystemService报破。而Application繼承ContextWrapper悠就,所以又是調(diào)用到了mBase的getSystemService。mBase為ContextImpl對(duì)象充易,所以最終調(diào)用到了ContextImpl中去梗脾。
下面我們來(lái)看看最終ContextImpl的調(diào)用

@Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
//SystemServiceRegistry中的getSystemService
public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

最終就是調(diào)用到了SystemServiceRegistry中注冊(cè)的系統(tǒng)服務(wù)了。具體可以參考SystemServiceRegistry的源代碼蔽氨。這個(gè)就是屬于注冊(cè)系統(tǒng)服務(wù)的范疇了藐唠。非本篇討論范圍。
最后是不是會(huì)有一個(gè)疑問(wèn)鹉究,一個(gè)應(yīng)用中到底會(huì)有多少個(gè)ContextImpl對(duì)象實(shí)例宇立,一個(gè)還是多個(gè)。如果是多個(gè)對(duì)象自赔,如何保證調(diào)用的最后歸一呢妈嘹。多個(gè)對(duì)象豈不是會(huì)占用很多內(nèi)存。
說(shuō)到這些绍妨,就不得不提ContextImpl中比較重要的兩個(gè)成員變量了

final ActivityThread mMainThread;
final LoadedApk mPackageInfo;

同時(shí)润脸,我們看到ContextImpl中有很多創(chuàng)建Context相關(guān)的函數(shù),比如

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);
    }
@Override
    public Context createApplicationContext(ApplicationInfo application, int flags)
            throws NameNotFoundException {
        LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
                flags | CONTEXT_REGISTER_PACKAGE);
        if (pi != null) {
            ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
                    new UserHandle(UserHandle.getUserId(application.uid)), flags,
                    mDisplay, null, Display.INVALID_DISPLAY);
            if (c.mResources != null) {
                return c;
            }
        }

        throw new PackageManager.NameNotFoundException(
                "Application package " + application.packageName + " not found");
    }
@Override
    public Context createPackageContext(String packageName, int flags)
            throws NameNotFoundException {
        return createPackageContextAsUser(packageName, flags,
                mUser != null ? mUser : Process.myUserHandle());
    }
@Override
    public Context createConfigurationContext(Configuration overrideConfiguration) {
        if (overrideConfiguration == null) {
            throw new IllegalArgumentException("overrideConfiguration must not be null");
        }

        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
                mUser, mFlags, mDisplay, overrideConfiguration, Display.INVALID_DISPLAY);
    }
``
可以看到ContextImpl是會(huì)創(chuàng)建很多次的他去,它是一個(gè)輕量級(jí)的對(duì)象毙驯,雖然ContextImpl會(huì)被創(chuàng)建多個(gè),但是它的成員變量mMainThread和mPackageInfo的對(duì)象引用是唯一的灾测。這也是一個(gè)android進(jìn)程里面比較重要的兩個(gè)類(lèi)爆价。也只會(huì)生成唯一的對(duì)象。從中我們也可以看到google工程師設(shè)計(jì)android框架的一些巧妙之處和原理,希望能從中學(xué)習(xí)一二铭段。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骤宣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子序愚,更是在濱河造成了極大的恐慌憔披,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爸吮,死亡現(xiàn)場(chǎng)離奇詭異芬膝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拗胜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)蔗候,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人埂软,你說(shuō)我怎么就攤上這事∪沂拢” “怎么了勘畔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)丽惶。 經(jīng)常有香客問(wèn)我炫七,道長(zhǎng),這世上最難降的妖魔是什么钾唬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任万哪,我火速辦了婚禮,結(jié)果婚禮上抡秆,老公的妹妹穿的比我還像新娘奕巍。我一直安慰自己,他們只是感情好儒士,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布的止。 她就那樣靜靜地躺著,像睡著了一般着撩。 火紅的嫁衣襯著肌膚如雪诅福。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天拖叙,我揣著相機(jī)與錄音氓润,去河邊找鬼。 笑死薯鳍,一個(gè)胖子當(dāng)著我的面吹牛咖气,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼采章,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼运嗜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起悯舟,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤担租,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后抵怎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體奋救,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年反惕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尝艘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姿染,死狀恐怖背亥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悬赏,我是刑警寧澤狡汉,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站闽颇,受9級(jí)特大地震影響盾戴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兵多,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一尖啡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剩膘,春花似錦衅斩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至惫搏,卻和暖如春具温,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筐赔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工铣猩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茴丰。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓达皿,卻偏偏與公主長(zhǎng)得像天吓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子峦椰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354