“文章相关内容已授权『郭霖』公众号发布
”
前言
这是context系列文章的第三部分,也是最后一部分。前面已经讲了什么是context以及context的创建流程。限于篇幅把四大组件中的广播和内容提供器的context获取流程放在了这篇文章。广播和内容提供器并不是context家族里的一员,所以他们本身并不是context,因而他们的context肯定是直接或间接从Application、Activity或者Service获取。然后对context的设计进行了讨论,从更高的角度看context,能够帮助我们看到context的本质,也能帮助我们更好地理解并使用context。
“这是context系列文章的第三部分,限于篇幅把context文章分为三部分:
第一部分:主要讲解什么是context以及关于context家族和相关实现类。Android全面解析之Context机制(一) : context认知
第二部分:主要讲不同Context的创建流程,包括Application、Activity、Service。Android全面解析之context机制(二): context创建流程
第三部分:也是最终篇,讲解剩下两个四大组件的context来源以及从更高的角度理解context。读者可前往主页选择感兴趣的部分阅读,也可以完整阅读系统化学习context。
”
Broadcast的context获取流程
Broadcast和上面的组件不同,他并不是继承自Context,所以他的Context是需要通过Application、Activity或者Service来给予。我们一般使用广播的context是在接收器中,如:
class MyClass :BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
TODO("use context")
}
}
那么onReceive的context对象是从哪里来的呢?同样我们先看广播接收器的注册流程:
同样,详细的广播相关工作流程可以阅读Android广播Broadcast的注册与广播源码过程详解(基于api29)这篇文章了解。因为在创建Receiver的时候并没有传入context,所以我们需要追踪他的注册流程,看看在哪里获取了context。我们先看到ContextImpl的registerReceiver
方法:
ContextImpl.class(api29)
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
// 注意参数
return registerReceiverInternal(receiver, getUserId(),
filter, broadcastPermission, scheduler, getOuterContext(), 0);
}
registerReceiver方法最终会来到这个重载方法,我们可以注意到,这里有个getOuterContext
,这个是什么?还记得Activity的context创建过程吗?这个方法获取的就是activity本身。我们继续看下去:
ContextImpl.class(api29)
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context, int flags) {
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
...
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
}
...
}
...
}
这里利用context创建了ReceiverDispatcher,我们继续深入看:
LoadedApk.class(api29)
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
Context context, Handler handler,
Instrumentation instrumentation, boolean registered) {
synchronized (mReceivers) {
LoadedApk.ReceiverDispatcher rd = null;
...
if (rd == null) {
rd = new ReceiverDispatcher(r, context, handler,
instrumentation, registered);
...
}
...
}
}
ReceiverDispatcher.class(api29)
ReceiverDispatcher(..., Context context,...) {
...
mContext = context;
...
}
这里确实把receiver和context创建了ReceiverDispatcher,嗯?怎么没有给Receiver?其实这涉及到广播的内部设计结构。Receiver是没有跨进程通信能力的,而广播需要AMS的调控,所以必须有一个可以跟AMS沟通的对象,这个对象是InnerReceiver,而ReceiverDispatcher就是负责维护他们两个的联系,如下图:
而onReceive方法也是由ReceiverDispatcher回调的,最后我们再看到回调onReceive的那部分代码:
ReceiverDispatcher.java/Args.class;
public final Runnable getRunnable() {
return () -> {
...;
try {
...;
// 可以看到这里回调了receiver的方法,这样整个接收广播的流程就走完了。
receiver.onReceive(mContext, intent);
}
}
}
Args是Receiver的内部类,mContext就是在创建ReceiverDispatcher时传入的对象,到这里我们就知道这个对象确实是Activity了。
但是,,不一定每个都是Activity。在源码中我们知道是通过getOuterContext
来获取context,如果是通过别的context注册广播,那么对应的对象也就不同了,只是我们一般都是在Activity中创建广播,所以这个context一般是activity对象。
ContentProvider的context获取流程
ContextProvider我们用的就比较少了,内容提供器主要是用于应用间内容共享的。虽然ContentProvider是由系统创建的,但是他本身并不属于Context家族体系内,所以他的context也是从其他获取的。老样子,先看ContentProvider的创建流程:
咦?这不是Application创建的流程图吗?是的,ContentProvider是伴随着应用启动被创建的,来看一张更加详细的流程图:
我们把目光聚集到ContentProvider的创建上,也就是installContentProviders
方法。同样,详细的ContentProvider工作流程可以访问Android中ContentProvider的启动与请求源码流程详解(基于api29)这篇文章。installContentProviders
是在handleBindApplication中被调用的,我们看到调用这个方法的地方:
private void handleBindApplication(AppBindData data) {
try {
// 创建Application
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
// 安装ContentProvider
installContentProviders(app, data.providers);
}
}
}
可以看到这里传入了application对象,我们继续看下去:
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
...
ContentProviderHolder cph = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
...
}
...
}
这里调用了installProvider,继续往下看:
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
...
// 这里c最终是由context构造的
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
}
...
try {
// 创建ContentProvider
final java.lang.ClassLoader cl = c.getClassLoader();
LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
...
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();
...
// 把context设置给ContentProvider
localProvider.attachInfo(c, info);
}
...
}
...
}
这里最重要的一行代码是localProvider.attachInfo(c, info);
,在这里把context设置给了ContentProvider,我们再深入一点看看:
ContentProvider.class(api29)
public void attachInfo(Context context, ProviderInfo info) {
attachInfo(context, info, false);
}
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
...
if (mContext == null) {
mContext = context;
...
}
...
}
这里确实把context赋值给了ContentProvider的内部变量mContext,这样ContentProvider就可以使用Context了。而这个context正是一开始传进来的Application。
从源码设计角度看Context
到这里关于Context的知识也讲得差不多了。研究Framework层知识,不能只停留在他是什么,有什么作用即可。Framework层他是一个整体,构成了android这个庞大的体系,还需要看Context,在其中扮演着什么样的角色,解决了什么样的问题。在window机制中我讲到window的存在是为了解决屏幕上view的显示逻辑与触摸反馈问题,在Hanlder机制中我写到整个android程序都是基于Handler机制来驱动执行的,而Context呢?
Android系统是一个完整的生态,他搭建了一个环境,让各种程序可以运行在上面。而任何一个程序,想要运行在这个环境上,必须得到系统的允许,也就是软件安装。安卓与电脑不同的是,他不是任意一个程序就可以直接访问到系统的资源。我们在window上可以写一个java程序,然后直接开启一个文件流就可以读取和修改文件了。而Android没这么简单,他任意一个程序的运行都必须经过系统的调控。也就是,即时程序获得允许(安装在手机上了),程序本身要运行,还得是系统来控制程序运行,程序无法自发地执行在Android环境中。我们通过源码可以知道程序的main方法,仅仅只是开启了线程的Looper循环,而后续的一切,都必须等待AMS来控制。
那应用程序自己硬要执行可不可以?可以,但是没卵用。想要获得系统资源,如启动四大组件、读取布局文件、读写数据库、调用系统柜摄像头等等,都必须要通过Context,而context必须要通过AMS来获取。这就区分了一个程序是一个普通的Java程序,还是android程序。
Context承受的两大重要职责是:身份权限、程序访问系统的接口。一个Java类,如果没有context那么就是一个普通的Java类,而当他获得context那么他就可以称之为一个组件了,因为它获得了访问系统的权限,他不再是一个普通的身份,是属于android“公民”了。而“公民”并不是无法无天,系统也可以通过context来封装以及限制程序的权限。要想弹出一个通知,你必须通过这个api,用户关闭你的通知权限,你就别想通过第二条路来弹出通知了。同时 程序也无需知道底层到底是如何实现,只管调用api即可。四大组件为何称为四大组件,因为他们生来就有了context,特别是activity和service,包括Application。而我们写的一切程序,都必须间接或者直接从其中获取context。
总而言之,context就是负责区分android内外程序的一个机制,限制程序访问系统资源的权限。
最后
这系列文章从什么是context开始介绍,再针对context的不同子类进行解析,最后结合源码深入地讲解了context的创建过程。最后再谈了我对context的设计理解。
关于context想说的就已经说完了。虽然这些内容日常很少用得到,但是非常有助于我们对Android整个系统框架的理解。而当我们对系统有更加深入的理解后,写出来的程序也就会更加健壮。
希望文章对你有帮助。
“全文到此,原创不易,觉得有帮助可以点赞收藏评论转发。 笔者才疏学浅,有任何想法欢迎评论区交流指正。 如需转载请私信交流。
另外欢迎光临笔者的个人博客:传送门
”