Android Context详解
我们最常用的Activity,Service,Application都是Context的子类。所以知道Context的具体实现是非常有必要的。
Context体系结构
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 |
|
第二重要的是LoadedApk mPackageInfo 这个变量,因为他被引用的次数是第二多的,要么通过mPackageInfo获取一些内容,要么通过他做判断。当然,这些功能也都是LoadedApk这个类封装的,通过他的名字也就知道他是干嘛的,这个类可以管理运行的.apk文件。这个类的详细说明也可以单独写一篇文章。
1 |
|
Context的不同获取方法的区别
context的获取可以通过以下方法:
getContext()
getBaseContxet()
getApplication
getApplicationContext
这几个对于新手来说,非常的迷惑。到底有说明区别?
先说说getApplication和getApplicationContext的区别。如果你在Activity里面打印这个两个方法的话,得到对象内存地址是一样的,也就是这两个方法获取的对象是一样的?实际上,这两个获取到的对象就是同一个对象。只是可以使用的范围是不一样的,getApplication这个方法只有Activity和Service才有,要想在别的地方获取Application就只能通过getApplicationContext获取。也就是他们几乎是等价的。唯一的区别就是有没有getApplication这个方法给你用而已。
getContext(),getBaseContxet(),getApplicationContext
这三个有什么区别?
首先Activity,Service和Application都有getBaseContxet(),getApplicationContext()这两个方法。但没有getContext方法。在Fragment中,才有getContext方法。而Fragment的getContext方法获取的对象是他的寄主对象,也就是Activity。如果查看getContext源码,在一系列包装后,实际上获取的就是他所在的Activity对象。
1 | public class MainActivity extends AppCompatActivity |
1 | getBaseContext():androidx.appcompat.view.ContextThemeWrapper@72a2522 |
在输出中,我们可以看到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方法调用之前初始化的。