Android开发中的Context

Posted 碎格子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发中的Context相关的知识,希望对你有一定的参考价值。

作为一个android开发者,不知道你有没有发现,在我们的代码中有个很厉害的角儿–Context,这个角色上能启动Service、Activity,下能Get各种应用资源,所以这个Context到底是个什么“人物”?为什么他拥有这么大的权力能支配四大组件呢?

Context是什么?

Context翻译为:语境; 上下文; 背景; 环境,而我们在开发中说的这个“上下文”到底是什么意思呢?在开发中,我们可以理解为当前对象在程序中所处的一个环境,一个与系统交互的过程。Context提供了一个应用的运行环境,应用通过这个环境才可以完成资源获取、组件交互、启动服务等操作。

Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。

在Android中,一个Activity是Context,一个Service也是一个Context,一个Application也是一个Context。不信?那让我们来看看Context的关系图:

可以从这个关系图中看出,Activity、Service、Application都是Context的子类,而Activity和Service及Application的区别是他继承的是ContextThemeWrapper,而Service和Application继承的是ContextWrapper,光从名字上就可以看出,ContextThemeWrapper跟ContextWrapper的区别是多了Theme,也就是为什么Activity能够设置界面的原因。那有人会问了,楼主前面说的Context能支配四大组件,那BroadcastReceiver和ContentProvider呢?他们并不是Context的子类啊?是的,他们并不是Context的子类,但是他们却持有从其他地方传过去的Context对象。
我们看看源码,看看Context到底是什么东西?

public abstract class Context 
    /**
     * File creation mode: the default mode, where the created file can only
     * be accessed by the calling application (or all applications sharing the
     * same user ID).
     * @see #MODE_WORLD_READABLE
     * @see #MODE_WORLD_WRITEABLE
     */
    public static final int MODE_PRIVATE = 0x0000;
    ...
     /** 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();
    ...

可以从源码看出,Context是一个抽象的类,声明了一些静态常量,以及一些访问当前包资源和启动Service、Activity的抽象方法。而从上面的关系图上来看,Activity和Service及Application都是Context的子类,那是不是他们来实现Context的那些抽象方法呢?通过查看源码发现,Activity、Service、Application以及他们的父类ContextThemeWrapper、ContextWrapper都没有真正地实现Context里的抽象方法,而是选择性地重写了Context的一些抽象方法。

ContextWrapper.java

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();
    
    ...

Context还有另外一个子类–ContextImpl,这个看名字是用来实现Context,那我们看看它的源码:

ContextImpl.java

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context 
    ...
    final ActivityThread mMainThread;
    final LoadedApk mPackageInfo;

    private final IBinder mActivityToken;

    private final UserHandle mUser;

    private final ApplicationContentResolver mContentResolver;

    private final String mBasePackageName;
    private final String mOpPackageName;

    private final ResourcesManager mResourcesManager;
    private final Resources mResources;
    ...
    static ContextImpl getImpl(Context context) 
        Context nextContext;
        while ((context instanceof ContextWrapper) &&
                (nextContext=((ContextWrapper)context).getBaseContext()) != null) 
            context = nextContext;
        
        return (ContextImpl)context;
    

    @Override
    public AssetManager getAssets() 
        return getResources().getAssets();
    

    @Override
    public Resources getResources() 
        return mResources;
    

    @Override
    public PackageManager getPackageManager() 
        if (mPackageManager != null) 
            return mPackageManager;
        

        IPackageManager pm = ActivityThread.getPackageManager();
        if (pm != null) 
            // Doesn't matter if we make more than one instance.
            return (mPackageManager = new ApplicationPackageManager(this, pm));
        

        return null;
    
...

可以看到,抽象方法的真正的实现是在ContextImpl里面,但是这里又有疑问了,看了源码的朋友都会发现,除了Application中的attach方法里

/**
 * @hide
 */
/* package */ final void attach(Context context) 
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    

有用到ContextImpl,在Activity、Service包括ContextWrapper及ContextThemeWrapper中都没有创建ContextImpl的对象,那ContextImpl是怎么和Activity、Service挂钩的呢?这里就要谈到Activity的启动过程了,由于本篇文章只讲Context,所以在这里不会花大量篇幅讲Activity的启动过程,有兴趣的同学可以去看看Activity的启动过程及源码。Service、Activity的实际启动实现是在ActivityThread类里,下面是源码:

ActivityThread.java#handleCreateService

//创建Service
private void handleCreateService(CreateServiceData data) 
        ...
        try 
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
            //可以看到,ContextImpl对象在Service创建时进行关联
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);

            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            service.onCreate();
            ...
    

ActivityThread.java#createBaseContextForActivity

/**
 *  该方法在performLaunchActivity方法中被调用,而performLaunchActivity是启动Activity的方法
 **/
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) 
        ...
        //ContextImpl对象在Activity创建时关联
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;
        ...
        return baseContext;
    

ActivityThread.java#attach

//创建application
private void attach(boolean system) 
            ...
            try 
                mInstrumentation = new Instrumentation();
                //当前Application创建时关联
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                mInitialApplication.onCreate();
             catch (Exception e) 
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            
            ...
    

通过源码我们解答了上面的疑问,ContextImpl对象是在ActivityThread类里创建并且和在Activity、Service、Application创建时进行关联。

Context能做什么

Context的作用很强大,下面用一个表格来看看Context的作用

Context作用ApplicationActivityService
Show DialogNoYesNo
Start Activity-Yes-
Layout Inflation-Yes-
Start ServiceYesYesYes
Send BroadcastYesYesYes
Register Broadcast ReceiverYesYesYes
Load Resource ValuesYesYesYes


可以看到,Activity对Context持有的作用域最广,这是因为Activity继承了ContextThemeWrapper,而ContextThemeWrapper在ContextWrapper的基础上增加了一些对界面的操作,使得Activity使用Context的作用更广。
使用Application和Service的Context进行Start Activity是不推荐的,Application和Service作为的Context是没有所谓的任务栈的,只有Activity作为的Context才有,如果使用非Activity的Context进行Start Activity,会报错

android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

需要标记FLAG_ACTIVITY_NEW_TASK,让启动Activity时给它创建一个新的任务栈,并且Activity作为SingleTask启动才能解决。
使用Application和Service的Context来Inflate Layout也是不推荐的,因为最终设置的样式是系统默认的样式,而自定义的样式在这里是无效的。

Activity、Application、Service使用Context有区别吗

答案是肯定的,在大多数情况下,Activity、Service、Application的Context功能都是通用的,因为Context的方法具体实现是ContextImpl,但是就如上面说的,一些关于UI的操作最好用Activity作为Context来处理,而除此之外,他们使用方法并没有什么区别。

getApplication和getApplicationContext有什么区别吗?

先看看这两个方法实现的源码:

ContextImpl.java#getApplicationContext

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

ActivityThread.java#attach

ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                mInitialApplication.onCreate();

ActivityThread.java#getApplication

public Application getApplication() 
        return mInitialApplication;
    

可以看出,这两个方法返回的Application都是由包的信息(PackageInfo)来决定的,而一个应用只有一个包信息,所以在能正常获取到包信息的情况下,这两个方法获取到的是同一个对象,只是类型不一样。对于ActivityThread创建的Activity和Service,能调用getApplication,而对于BroadcastReceiver,只能用他持有的Context调用getApplicationContext获取Application实例。

Context引发的内存泄露

Context并不能随便乱用,用的不好有可能会引起内存泄露的问题,下面就示例两种错误的引用方式。

错误的单例模式

public class Singleton 
    private static Singleton instance;
    private Context mContext;

    private Singleton(Context context) 
        this.mContext = context;
    

    public static Singleton getInstance(Context context) 
        if (instance == null) 
            instance = new Singleton(context);
        
        return instance;
    

这是一个非线程安全的单例模式,instance作为静态对象,生命周期要长于普通的对象,假如一个Activity去getInstance传入this获得instance对象,常驻内存的Singleton一直持有你传入的Activity对象,即使Activity被销毁掉,但它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。

View持有Activity引用

public class MainActivity extends Activity 
    private static Drawable mDrawable;

    @Override
    protected void onCreate(Bundle saveInstanceState) 
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        ImageView iv = new ImageView(this);
        mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
        iv.setImageDrawable(mDrawable);
    

这个静态的Drawable对象间接引用了MainActivity的mContext,当MainActivity被销毁时,mDrawable还持有MainActivity的引用,也不能被GC掉,所以造成内存泄漏。
总结下来就是使用了长周期的静态类,使得短周期的被销毁后长周期的还持有短周期的引用,从而导致内存泄漏。

使用Context的正确姿势

1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有到Activity的引用。(比如静态对象持有Activity,当Activity销毁时,静态对象不能释放引用)
3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

以上是关于Android开发中的Context的主要内容,如果未能解决你的问题,请参考以下文章

android 开发学习3

Android开发之Android Context,上下文(Activity Context, Application Context)

android如何传context

android中thisgetAppliaction()context的区别。

android中thisgetAppliaction()context的区别。

当大厂螺丝钉,还是二线公司的“角儿”?