Android Context 源码解析

原创 2018年04月16日 23:32:39
本篇文章分为四个章节:一、Context是什么?;二、Context相关类整体结构;三、四大组件Context初始化(&ApplicationContext);四、Context相关问题;

一、Context是什么?

    一个Context意味着一个场景,一个场景就是用户和操作系统交互的一种过程。比如当你打电话时,场景包括电话程序对应的界面,以及隐藏在界面后的数据:当你看短信时,场景包括短信界面,以及隐藏在后面的数据。

    从语义的角度来审视一下Context,Android程序员把“场景”抽象为Context类,用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service),一个应用程序可以认为是一个工作环境,用户在这个工作环境中会切换到不同的场景,这就像一个前台秘书,她可能需要接待客户,可能要打印文件,还可能要接听客户电话,而这些就称之为不同的场景,前台秘书可称之为一个应用程序。

    从代码的角度看,Context就是一个抽象类,Activity类的确是基于Context,而Service类也是基于Context,Activity除了基于Context类以外,还实现了一些其他重要接口,从设计的角度来看,interface仅仅是基于某些功能,而extends才是类的本质,即Activity的本质是一个Context,其所实现的其他接口只是为了扩充Context的功能而已,扩充之后的类称之为一个Activity或者Service。

二、Context相关类整体结构

[caption id="attachment_11" align="aligncenter" width="557"]Android Context架构 Android Context架构[/caption]


    Context类本身是一个纯抽象类。

    为了使用方便,又定义了ContextWrapper类,如其名所言,只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同事ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper的方法都会被转向其所包含的真正的Context对象。

    ContextThemeWrapper类,其内部包含了与主题(Theme)相关的接口,这里所说的主题就是指在AndroidManifest.xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,因为Service是没有界面的后台场景,所以Service直接继承于ContextWrapper。

    ContextImpl类真正实现了Context中所有的函数,应用程序中所调用的各种Context类的方法,其实现均来自于该类。

    由上面的整体结构图也可以得到一个问题的答案:
        一个应用程序中包含多少个Context对象? 

        Context个数 = Service个数+ Activity个数 + 1(Application)

三、四大组件Context初始化(&ApplicationContext)

1、ApplicationContext

    在介绍四大组件Context初始化之前,先介绍下Application(或者说ApplicationContext)的初始化。

    每个应用程序在第一次启动时,都会首先创建一个Application对象,默认的Application是应用程序的包名,用户可以重载默认的Application,方法是在AndroidManifest.xml的Application标签中声明一个新的Application名称,然后在工程代码中添加该名称的类,该类的父类使用Application类即可。

    在ActivityThread中有一个ApplicationThread内部类,Application初始化调用就在这个类的bindApplication()函数中,通过消息发送在ActivityThread的Handler对象(H)中调用handleBindApplication()。 相关代码如下:

Application app = data.info.makeApplication(data.restrictedBackupMode, null);

mInitialApplication = app;
    而makeApplication方法是在LoadedApk.java类里面,代码如下:
public Application makeApplication(boolean forceDefaultAppClass,

                                            Instrumentation instrumentation) {
     if (mApplication != null) {
         return mApplication;
     }

     try {
         ContextImpl appContext = new ContextImpl();
         appContext.init(this, null, mActivityThread);
         app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
         appContext.setOuterContext(app);
     } catch (Exception e) {}

     mActivityThread.mAllApplications.add(app);
     mApplication = app;
     return app;
}
    这边使用到的packageInfo对象其实是LoadedApk的一个实例对象,在ActivityThread中无论是使用getPackageInfo或者getPackageInfoNoCheck最终都是返回一个LoadedApk全局实例,这个实例保存在mPackages弱引用或者mResourcePackages弱引用对象中。
If (includeCode) {
    mPackage.put(aInfo.packageName, new WeakReference<LoadApk>(packageInfo));
} else {
    mResourcePackages.put(aInfo.packageName, new WeakReference<LoadApk>(packageInfo));
}

2、Activity Context

    初始调用也是在ApplicationThread内部类,scheduleLaunchActivity() ,在Handler对象(H)中调用handleLaunchActivity(),接着调用到 performLaunchActivity(),代码如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    Context appContext = createBaseContextForActivity(r, activity);
    CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
    Configuration config = new Configuration(mCompatConfiguration);
    activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config);
}

final void attach(Context context, ActivityThread aThread,

            Instrumentation instr, ...) {

    attachBaseContext(context);//ContextThemeWrapper

}
这边设置Context的attach方法最终是在ContextWrapper attachBaseContext函数中初始化值的设置。

3、Service Context

    初始调用也是在ApplicationThread,scheduleCreateService(),接着调用到handleCreateService(CreateServiceData data),代码如下:

private void handleCreateService(CreateServiceData data) {
        ContextImpl context = new ContextImpl();
        context.init(packageInfo, null, this);
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        context.setOuterContext(service);
        service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault());
        service.onCreate();
        mServices.put(data.token, service);
}

public final void attach(Context context, ActivityThread thread, ...) {
        attachBaseContext(context);
}
    跟Activity的初始化一样,Context也是在ContextWrapper中初始化值的设置。

4、ContentProvider与BroadcastReceiver的Context赋值

1) ContentProvider

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

public interface ComponentCallbacks2 extends ComponentCallbacks {

}

public abstract class ContentProvider implements ComponentCallbacks2 {

    public abstract boolean onCreate();

    public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);

    public abstract Uri insert(Uri uri, ContentValues values);

    public void attachInfo(Context context, ProviderInfo info);

    .. ..

}

2) BroadcastReceiver

public abstract class BroadcastReceiver {
    public abstract void onReceive(Context context, Intent intent);
}
    以上是ContentProvider整个类结构和BroadcastReceiver的简化代码,查看源码ContentProvider是通过attachInfo函数来初始化Context值,而BroadcastReceiver的onReceive()是抽象函数,只是通过这个函数调用直接传入Context值。
    在ActivityThread类中可以看到ContentProvider和BroadcastReceiver的Context赋值。ContentProvider的初始化过程与Activity或Service一样,最终调用到了installProvider(),在这边把ApplicationContext或者Application对象转化为Context参数传入ContentProvider。

    而BroadcastReceiver在接收到广播消息之后才实例化,只是把ApplicationContext当做参数传入。

1) ContentProvider

   private IActivityManager.ContentProviderHolder installProvider(Context context,
            IActivityManager.ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {

      localProvider.attachInfo(c, info);
   }
   //这边的c指向ApplicationContext

2) BroadcastReceiver

   private void handleReceiver(ReceiverData data) {
       ContextImpl context = (ContextImpl)app.getBaseContext();

       sCurrentBroadcastIntent.set(data.intent);

       receiver.setPendingResult(data);

       receiver.onReceive(context.getReceiverRestrictedContext(), data.intent);
   }
    //这边context.getReceiverRestrictedContext()值最终指向ApplicationContext

四、Context相关问题

1、除了Activity之外,其他地方使用startActivity为什么需要加Intent.FLAG_ACTIVITY_NEW_TASK

    Activity类里面有自己的startActivity()实现,除了Activity之外,其他地方使用startActivity的时候其实是使用ContextImpl中的startActivity()方法。
    Activity中startActivity()(简化代码)
@Override
 public void startActivity(Intent intent) {
        this.startActivity(intent, null);
 }


 @Override
 public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            startActivityForResult(intent, -1);
        }
 }

 public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
       if (mParent == null) {
            Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options);
       } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
   }
    ContextImpl中startActivity()(简化代码)
@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?");
        }

        if (MultiWindowProxy.isFeatureSupport()){  
            if (MultiWindowProxy.getInstance() != null 
                        && MultiWindowProxy.getInstance().getFloatingState()) {               
                intent.addFlags(Intent.FLAG_ACTIVITY_FLOATING);
            }                      
        }

        mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intent, -1, options);
    }
    从上面的代码可以看出在ContextImpl中有Intent Flag参数的检查,除了Activity之外,其他地方使用startActivity,如果没有添加Intent.FLAG_ACTIVITY_NEW_TASK参数就会抛出异常。两个地方的startActivity()实现最终都是调用到了execStartActivity().

2、关于Context的内存泄漏

(1)单例模式

public class Singleton {
        private static volatile Singleton mInstance = null;

        public static Singleton getInstance(Context pContext) {
                if(mInstance == null) {
                        synchronized(Singleton.class) {
                                if(mInstance == null){
                                        mInstance = new Singleton(pContext.getApplicationContext());
                                }
                        }
                }
               return mInstance;
        }
       private Singleton (Context pContext) { }
}
    在有使用到Context的单例模式实现中,开发者经常会把当前Activity的Context当做参数传入,这就会导致在Activity退出之后其实对象并没有被销毁,造成内存泄漏。所以如果是单例模式,最好加个保险,获取ApplicationContext作为参数,使得单例对象的生命周期和整个应用的生命周期一致。

(2)静态对象间接引用Activity

    静态对象间接引用Activity.如下面代码的引用关系mDrawable -> ImageView -> MainActivity,导致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);
     }
}
    在查阅一些网上资料的时候,很多地方只是说了下上图的情况,但并没有最根本的问题解释。这边的问题根本是ImageView控件在设置Drawable对象的时候,Drawable有一个Callback设置方法,在低版本的Android平台上,这个Callback是一个强引用,之后的Android版本改成了弱引用的方式:
Drawable :

// 2.0  平台
public final void setCallback(Callback cb){
    mCallback = cb;
}

// 4.2平台
public final void setCallback(Callback cb){
    mCallback = new WeakReference<Callback>(cb);
}
    以上内容参考了《Android内核剖析》及网上资料,基于Android4.4平台源码整理。


更多内容>>阅读原文




版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/air798/article/details/79968513

Android应用Context详解及源码解析

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】1 背景今天突然想起之前在上家公司(做TV与BOX盒子)时有好几个人问过我关于Android的...
  • yanbober
  • yanbober
  • 2015-05-27 23:17:49
  • 16328

Android Glide详解

Glide的基本使用导入库repositories { mavenCentral() // jcenter() works as well because it pulls from Maven ...
  • qq_36488374
  • qq_36488374
  • 2018-04-02 09:35:31
  • 29

Android源码分析-全面理解Context

Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像难以说...
  • singwhatiwanna
  • singwhatiwanna
  • 2014-03-23 02:21:33
  • 22601

PackageManagerService源码分析之入门(一)

1、背景今天要分析的内容相信大家肯定都不会陌生,也许你平时没有直接去调用其方法,但是Android系统却无时无刻不在使用它,它就是我们今天要重点分析的Android核心服务PackageManager...
  • dongxianfei
  • dongxianfei
  • 2016-10-01 17:05:04
  • 790

Glide源码解析(一)

Glide源码解析系列文章基于Glide4.5.0,源码看了很多遍,慢慢理清,文章链接如下: Glide源码解析(一) Glide源码解析(二) Glide源码解析(三) Glide最基本的使...
  • pmx_121212
  • pmx_121212
  • 2018-01-16 18:59:27
  • 211

Android应用AsyncTask处理机制详解及源码分析

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】1 背景Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知...
  • yanbober
  • yanbober
  • 2015-05-28 16:02:37
  • 8706

Android源码设计模式解析与实战

  • 2018年03月29日 09:25
  • 50.32MB
  • 下载

Android Context类结构详解

大家好,  今天给大家介绍下我们在应用开发中最熟悉而陌生的朋友-----Context类 ,说它熟悉,是应为我们在开发中    时刻的在与它打交道,例如:Service、BroadcastRecei...
  • beyond702
  • beyond702
  • 2015-12-18 17:05:37
  • 690

Android中ContextImpl源码分析(二)

1、背景 在前一篇文章中我们分析了Android系统中的Context类及其子类相关关系,以及各种Context子类的创建过程,大家有兴趣的可以去了解一下Android中Context源码分析(一)...
  • dongxianfei
  • dongxianfei
  • 2017-01-24 14:47:23
  • 1964

spring源码学习之五 </context:component-scan>元素处理过程

概述在spring的配置文件中,通常会配置自动扫描包路径下的bean,而元素即用来实现该功能,下面是一个配置简单实例: ...
  • wenjiangchun
  • wenjiangchun
  • 2016-02-15 17:36:49
  • 2577
收藏助手
不良信息举报
您举报文章:Android Context 源码解析
举报原因:
原因补充:

(最多只允许输入30个字)