Android Context详解

我们最常用的Activity,Service,Application都是Context的子类。所以知道Context的具体实现是非常有必要的。

Context体系结构

image

Context本身是一个抽象类。他的实现类是ContextImpl。而ContextWrapper是一个包装类(装饰设计模式)。在我们用IDE查看Context继承关系的时候,我们是不能直接看到ContextImpl这个类的,通过这种手段让程序员不能直接看到ContextImpl这个类,而只能看到ContextWrapper这个类。但如果我们真的要想弄明白Context原理的话,ContextImpl才是最重要的,而ContextWrapper只是个外壳而已。

ContextWrapper的子类我们非常熟悉了,Application,Service,ContextThemeWrapper。前两个就不用多说了,ContextThemeWrapper其实我们也不用太关心,有需要的时候可以看看,就是和主题相关的,他的子类Activity也不用多说。所以其实最核心的类就是ContextImpl类。

Context类是一个抽象类,差不多有5000多行,而且几乎都是没有实现的抽象方法,并且差不多4000多行都是注释。真正的内容可能1000行不到。所以Context类几乎就是一个接口说明文档。而ContextImpl也才2700多行。而且很多都是get方法。ContextImpl类继承了抽象类Context并且实现所有的抽象方法,并且自身还实现了很多get,create,check开头的方法,即使这样,也才2700行代码。要理解Context,核心也就在这2700行代码了(很多是get方法),而且还有Context这个文档说明类。

在这2700行代码里面,最重要的是 ActivityThread mMainThread;这个全局变量。他被用的地方最多。
我们最常看到的getApplicationContext,startActivity,getMainLooper都是在这里实现,实现方法也非常的简单。这都是ActivityThread封装好的,具体内容需要看ActivityThread这个类。这里不展开,会写一篇文章专门讨论。其中绝大部分使用和广播有光,如果专门需要研究广播的时候可以看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public Looper getMainLooper() {
return mMainThread.getLooper();
}

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

@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();

mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}

//差不多有10个类似方法
@Override
public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {

ActivityManager.getService().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, receiverPermissions, appOp, null, false, false,
getUserId());

}

第二重要的是LoadedApk mPackageInfo 这个变量,因为他被引用的次数是第二多的,要么通过mPackageInfo获取一些内容,要么通过他做判断。当然,这些功能也都是LoadedApk这个类封装的,通过他的名字也就知道他是干嘛的,这个类可以管理运行的.apk文件。这个类的详细说明也可以单独写一篇文章。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
@Override
public String getPackageName() {
if (mPackageInfo != null) {
return mPackageInfo.getPackageName();
}
// No mPackageInfo means this is a Context for the system itself,
// and this here is its name.
return "android";
}
@Override
public ApplicationInfo getApplicationInfo() {
if (mPackageInfo != null) {
return mPackageInfo.getApplicationInfo();
}
throw new RuntimeException("Not supported in system context");
}

@Override
public String getPackageResourcePath() {
if (mPackageInfo != null) {
return mPackageInfo.getResDir();
}
throw new RuntimeException("Not supported in system context");
}

//差不多有10个
void sendOrderedBroadcast() {
//多处用mPackageInfo做判断
}

Context的不同获取方法的区别

context的获取可以通过以下方法:

  • getContext()

  • getBaseContxet()

  • getApplication

  • getApplicationContext

这几个对于新手来说,非常的迷惑。到底有说明区别?

先说说getApplicationgetApplicationContext的区别。如果你在Activity里面打印这个两个方法的话,得到对象内存地址是一样的,也就是这两个方法获取的对象是一样的?实际上,这两个获取到的对象就是同一个对象。只是可以使用的范围是不一样的,getApplication这个方法只有Activity和Service才有,要想在别的地方获取Application就只能通过getApplicationContext获取。也就是他们几乎是等价的。唯一的区别就是有没有getApplication这个方法给你用而已。

getContext(),getBaseContxet(),getApplicationContext
这三个有什么区别?
首先Activity,Service和Application都有getBaseContxet(),getApplicationContext()这两个方法。但没有getContext方法。在Fragment中,才有getContext方法。而Fragment的getContext方法获取的对象是他的寄主对象,也就是Activity。如果查看getContext源码,在一系列包装后,实际上获取的就是他所在的Activity对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends AppCompatActivity 

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Log.d("MainActivity", "getBaseContext():" + getBaseContext());//ContextImpl对象
Log.d("MainActivity", "getApplicationContext():" + getApplicationContext());
Log.d("MainActivity", "getApplication():" + getApplication());
Log.d("MainActivity", "this:" + this);//Activity对象
}


public class BlankFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BlankFragment", "getContext():" + getContext());
}
}
1
2
3
4
5
getBaseContext():androidx.appcompat.view.ContextThemeWrapper@72a2522
getApplicationContext():android.app.Application@e8e5bb3
getApplication():android.app.Application@e8e5bb3
this:com.example.viewpagerdemo.MainActivity@b0be2f
getContext():com.example.viewpagerdemo.MainActivity@b0be2f

在输出中,我们可以看到getContext和MainActivity的this是同一个对象,getApplicationContext和getApplication获取到的也是同一个对象,也是符合我们的证明的。

正确使用Context

一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的,所以我们总结出使用Context的正确姿势:
1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有Activity的引用。
3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。

ContextImpl实例什么时候生成

ContextImpl实例生成对应着mBase的赋值过程:
在启动Activity时,在ActivityThread内部通过handleLaunchActivity()方法一系列调用,在通过Instrucmentation创建完Activity后,会先调用Activity的attach()方法,会传入已创建好的ContextImpl对象,在Attach()方法内部会先调用attachBaseContext(context)方法,会将ContextImpl通过super.attachBaseContext(context)一步一步最后赋值给ContextWrapper的mBase,接下来再调用activity的onCreate()。

ContentProvider里的Context初始化

ContentProvider本身不是Context ,但是它有一个成员变量 mContext ,是通过构造函数传入的。mContext初始化对应着ContentProvider创建时机。
应用创建Application是通过调用 ActivityThread.handleBindApplication方法,这个方法的相关流程有:
创建 Application
初始化 Application的Context
调用installContentProviders()并传入刚创建好的context来创建ContentProvider
调用Application.onCreate()
ContentProvider的Context是在Applicaiton创建之后,但是 onCreate方法调用之前初始化的。