你对Context了解多少呢

如需转载请评论或简信,并注明出处,未经允许不得转载

目录

前言

Android应用进程的创建 — Activity的启动流程中我们发现,ApplicationActivity都是由系统创建的,它们并不能像其他java类一样,由应用层通过new操作创建出对象,且它们都继承自Context,那么大家有没有想过,这个Context到底有什么作用呢?它在Android应用中扮演了一个怎么样的角色呢?

Context继承关系

这里整理了一张Context的继承关系类图,从这个图中可以看出,Context是一个接口,ContextImpContextWrapper都是其实现类,我们常用的ActivityServiceApplication都直接或间接继承自ContextWrapper

通过这张图,我们可以整理出几个问题:

  1. 一个应用程序有几个Context

  2. 为什么ActivityServiceApplication都继承自ContextContext的作用是什么呢?

  3. 为什么Activity需要继承自ContextThemeWrapper,而ServiceApplication直接继承自ContextWrapper呢?

  4. 为什么ContextWrapper中存在一个ContextImp类型的变量mBase,且同时它又实现了Context呢?

问题分析

问题一

一个应用程序有几个Context?

ApplicationActivityService都继承自Context,而应用有几个进程,就会存在几个Application对象

Context个数 = Activity个数 + Service个数 + 进程个数

问题二

为什么Activity、Service、Application都继承自Context,Context的作用是什么呢?

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 {
    
    // 四大组件相关
    public abstract void startActivity(@RequiresPermission Intent intent);
    public abstract void sendBroadcast(@RequiresPermission Intent intent);
    public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
                                            IntentFilter filter);
    public abstract void unregisterReceiver(BroadcastReceiver receiver);
    public abstract ComponentName startService(Intent service);
    public abstract boolean stopService(Intent service);
    public abstract boolean bindService(@RequiresPermission Intent service,
            @NonNull ServiceConnection conn, @BindServiceFlags int flags);
    public abstract void unbindService(@NonNull ServiceConnection conn);
    public abstract ContentResolver getContentResolver();
    
    // 获取系统/应用资源
    public abstract AssetManager getAssets();
    public abstract Resources getResources();
    public abstract PackageManager getPackageManager();
    public abstract Context getApplicationContext();
    public abstract ClassLoader getClassLoader();
    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { ... }
    
    public final String getString(@StringRes int resId) { ... }
    public final int getColor(@ColorRes int id) { ... }
    public final Drawable getDrawable(@DrawableRes int id) { ... }
    public abstract Resources.Theme getTheme();
    public abstract void setTheme(@StyleRes int resid);
    public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { ... }
    
    // 获取应用相关信息
    public abstract ApplicationInfo getApplicationInfo();
    public abstract String getPackageName();
    public abstract Looper getMainLooper();
    public abstract int checkPermission(@NonNull String permission, int pid, int uid);
    
    // 文件相关
    public abstract File getSharedPreferencesPath(String name);
    public abstract File getDataDir();
    public abstract boolean deleteFile(String name);
    public abstract File getExternalFilesDir(@Nullable String type);
    public abstract File getCacheDir();
    ...
    public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
    public abstract boolean deleteSharedPreferences(String name);
    
    // 数据库相关
    public abstract SQLiteDatabase openOrCreateDatabase(...);
    public abstract boolean deleteDatabase(String name);
    public abstract File getDatabasePath(String name);
    ...
    
    // 其它
    public void registerComponentCallbacks(ComponentCallbacks callback) { ... }
    public void unregisterComponentCallbacks(ComponentCallbacks callback) { ... }
    ...
}

public interface ComponentCallbacks {
    void onConfigurationChanged(Configuration newConfig);
    void onLowMemory();
}

结合代码可以看出,Context就像是应用的大管家,正是因为有了Context,各种应用组件才有意义,他们才能访问系统服务,系统资源

Context的作用总结为如下几个方面:

  1. 四大组件的交互,包括启动 ActivityBroadcastService,获取 ContentResolver
  2. 获取系统/应用资源,包括 AssetManagerPackageManagerResourcesSystem Service 以及 colorstringdrawable
  3. 文件,包括获取缓存文件夹、删除文件、SharedPreference 相关等
  4. 数据库(SQLite)相关,包括打开数据库、删除数据库、获取数据库路径等
  5. 其它辅助功能,比如设置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生

问题三

为什么Activity需要继承自ContextThemeWrapper,而Service和Application直接继承自ContextWrapper呢?

下面来看一下ContextThemeWrapper的源码

public class ContextThemeWrapper extends ContextWrapper {
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;
    private Configuration mOverrideConfiguration;
    private Resources mResources;

    public ContextThemeWrapper() {
        super(null);
    }

    public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
        super(base);
        mThemeResource = themeResId;
    }

    public ContextThemeWrapper(Context base, Resources.Theme theme) {
        super(base);
        mTheme = theme;
    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }

    public void applyOverrideConfiguration(Configuration overrideConfiguration) {...}

    public Configuration getOverrideConfiguration() {...}

    @Override
    public AssetManager getAssets() {...}

    @Override
    public Resources getResources() {...}

    private Resources getResourcesInternal() {...}

    @Override
    public void setTheme(int resid) {...}

    @Override
    public int getThemeResId() {...}

    @Override
    public Resources.Theme getTheme() {...}

    @Override
    public Object getSystemService(String name) {...}

    protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {

    private void initializeTheme() {...}
}

ContextThemeWrapper类,从它的命名就可以看出,其内部包含了与Theme相关的接口,当然,只有Activity才需要主题,ServiceApplication是不需要主题的,因为Service是没有界面的后台场景,所以ServiceApplication直接继承于ContextWrapper

问题四

为什么ContextWrapper中存在一个ContextImp类型的变量mBase,且同时它又实现了ContextWrapper呢?

下面来看ContextWrapper的代码

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    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静态代理类,所有的操作都是通过内部成员 mBase 完成的,而mBase就是ContextImp对象。为什么要这么设计呢?如果Application直接继承ContextImp会不会有什么问题呢?

一般情况下,使用代理而不直接使用某个对象,目的可能有两个:

  1. 定制自己的行为
  2. 不影响原对象

其中 ServcieApplication 的父类 ContextWrapper 完全没有自定义的行为,而 Activity 的父类 ContextThemeWrapper 则自定义了 Resource 以及 Theme 的相关行为,因此:

  1. 对于 ServiceApplication 而言,不直接继承ContextImp,是担心用户修改了ContextImp而导致错误的发生
  2. 对于 Activity 而言,除了担心用户的修改之外,ContextImpActivity 本身对于 Reource 以及 Theme 的相关行为是不同的

其他使用Context注意点

  1. 在单例模式中使用Context要注意内存泄漏问题

我们知道,单例模式的生命周期是和应用的生命周期保持一致的,所以在单例模式中使用Context,不能使用Activity Context,需要使用Application Context

  1. 创建dialog需要使用Activtiy Context

果我们使用Application的Context,或者说Token可以不是Activity的Token,那么用户可能已经跳转到别的应用的Activity界面了,但我们却可以在别人的界面上弹出我们的Dialog,想想就觉得很危险

具体可以参考:https://www.jianshu.com/p/628ac6b68c15

  1. Activity的this和getBaseContext()有什么区别?

Activity就是继承Context的,所以this是返回Activity自己

getBaseContext()返回的是ContextWrapper里面的mBase

  1. getApplication()和getApplicationContext()有什么区别?

都是返回Applicatoin对象,但是他们的作用域不一样

getApplicatoin()ActivityService里面特有的,其他地方不能用

BroadcastReceiveronReceive()中的第一个参数,拿到的Context对象是不能调用getApplication()的只能调getApplicationContext()

  1. Application构造方法中调用父类方法会发生闪退(如getResource()等)

Application的创建过程主要是下面三步

//1.创建ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//2.创建Application
Application app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
//3.调用Application#onCreate()
app.onCreate();

Instrumentation#newApplication()

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}

Application#attach()

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

Application#attachBaseContext()

protected void attachBaseContext(Context base) {
    if (mBase != null) {
        throw new IllegalStateException("Base context already set");
    }
    mBase = base;
}

从上面的代码中可以看出,在Application对象刚被创建的时候,其内部的mBase变量是空的,直到执行attachBaseContext()后,mBase才会有值,之后才会调用Application#onCreate()。所以在Application中使用Context接口中的相关方法,可以在onCreate()里面调用,而不能在构造方法中调用

总结

Context是一个抽象类,我们开发过程中几乎每天都要和它打交道,但是我相信很多人都说不出他是个什么东西,很多只是知道它叫做上下文。其实当我们觉得一个东西很抽象很难理解的时候,无外乎就是看一下它的创建过程以及它具备哪些方法

这里做一个比喻,对于我们应用层开发来说,Activity就像是一个”皇帝“,Activity可以做很多很多的事情,但是Context就像是他手中的权利,如果没有ContextActivity其实只是一个”普通人“(普通java类)而已

我们很多人往往把Activity理解成它继承了Context,是的没错,它确实继承自Context,但我认为,把Activity理解成它代理了Context,会更贴合实际意义一些

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345