Context到底是什么,身份证么?
在Android中到处都在使用Context,今天剖析一下Context到底是个什么东西?它存在的意义是什么?
先说结论,Context就像现实生活中身份证明。这个身份证明发放给每个人,帮助他们认清自己,记录了基本的信息,和可以使用这个身份证明可以快速办理的一些事情。
Context在实际的代码中是一个抽象类,定义了一些抽象函数,这些抽象函数就是我们用这个身份证明一条一条可以做的事情。
诞生发放证明
如果它是一个身份证明,那就意味着诞生的那一刻就要办理了。诞生的那一刻在哪里呢?带着这个问题看一些代码。
- 为了更好的理解这个理念,我就尽量的少贴一些代码细节,文中用到的源码路径。
frameworks/base/core/java/android/app/ActivityThread.java
frameworks/base/core/java/android/app/LoadedApk.java
frameworks/base/core/java/android/app/ContextImpl.java
Application
- 进程的启动是一个非常复杂的过程,如果从源头去讲的话,可能篇幅要超过本篇文章长度了。
我们简单的理解每一个Android应用都是一个APK的话,那么启动一个应用就要将APK加载进来。我们简单的理解LoadedApk
就是APK加载到内存中时对应的对象。
下面是LoadedApk
的构造函数
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
CompatibilityInfo compatInfo, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode, boolean registerPackage) {
final int myUid = Process.myUid();
aInfo = adjustNativeLibraryPaths(aInfo);
mActivityThread = activityThread;
mApplicationInfo = aInfo;
mPackageName = aInfo.packageName;
mAppDir = aInfo.sourceDir;
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
mSplitAppDirs = aInfo.splitSourceDirs;
mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
mOverlayDirs = aInfo.resourceDirs;
mSharedLibraries = aInfo.sharedLibraryFiles;
mDataDir = aInfo.dataDir;
mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
mLibDir = aInfo.nativeLibraryDir;
mBaseClassLoader = baseLoader;
mSecurityViolation = securityViolation;
mIncludeCode = includeCode;
mRegisterPackage = registerPackage;
mDisplayAdjustments.setCompatibilityInfo(compatInfo);
}
大家可以看到这些赋值,有很多都是我们平时通过Context.
引用的信息。
创建LoadedApk
对象在下文中。
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
...
//这里是创建的地方。
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
+ ")");
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
if (mSystemThread && "android".equals(aInfo.packageName)) {
packageInfo.installSystemApplicationInfo(aInfo,
getSystemContext().mPackageInfo.getClassLoader());
}
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
}
}
我们看到上门的代码中只是return了一个LoadedApk
对象,还是没有找到Context
的蜘丝马迹,那我们在深入一步看调用getPackageInfo()
的地方
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
mCompatConfiguration = new Configuration(data.config);
mProfiler = new Profiler();
if (data.initProfilerInfo != null) {
mProfiler.profileFile = data.initProfilerInfo.profileFile;
mProfiler.profileFd = data.initProfilerInfo.profileFd;
mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
}
.......
//这里创建了ContextImpl
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
if (!Process.isIsolated()) {
final File cacheDir = appContext.getCacheDir();
if (cacheDir != null) {
// Provide a usable directory for temporary files
System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
} else {
Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property due to missing cache directory");
}
// Use codeCacheDir to store generated/compiled graphics code
final File codeCacheDir = appContext.getCodeCacheDir();
if (codeCacheDir != null) {
setupGraphicsSupport(data.info, codeCacheDir);
} else {
Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory");
}
}
...
try {
...
if (!data.restrictedBackupMode) {
List<ProviderInfo> providers = data.providers;
if (providers != null) {
//重要,记住这里,后面要用!!!
installContentProviders(app, providers);
// For process that contains content providers, we want to
// ensure that the JIT is enabled "at some point".
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}
...
}
大家看到这一段代码,发现了一个ContextImpl
这个东西。好像和Context
有了关系了。
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
那具体是什么关系了。先说关系,后面在展开。如果Context
是一个身份证明的话,ContextImpl
就是一个信息公共处理中心,我们平常使用的身份证明去办事,实际的工作确实在哪里完成的,整个过程对于我们来说是个黑盒的,但是应用诞生的那一刻的信息就存在这里面了。
Android
之中,ContextImpl
继承Context
这个抽象类,这里面具体实现了Context
定义的抽象函数。
那么疑问来了,Context
是4大组件重要的一个身份证明的话,这时候还没到4大组件呢,一个应用充其量是一个Application
啊(一个应用不一定只对应一个,有多进程的情况存在,不展开说明,感兴趣的自行查阅资料),那应该怎么理解呢?带着问题接着看
Activity
Activity
的启动流程够写一篇文章了也不一定能说清楚,这里不展开,直接贴目的地代码。
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
int displayId = Display.DEFAULT_DISPLAY;
try {
displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
} catch (RemoteException e) {
}
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getDisplayAdjustments(id));
baseContext = appContext.createDisplayContext(display);
break;
}
}
}
return baseContext;
}
我们有看到了 ContextImpl
这个东西,不过仔细看,类是一个类,构建函数却变了,这说明办身份证和户口本流程是不一样的,上面是ContextImpl.createAppContext
,这里ContextImpl.createActivityContext
- 补充第二点
ContextImpl
的构造函数是私有的,外部无法通过new
直接拿到对象,它对外提供了三个静态create函数createSystemContext
,createAppContext
,createActivityContext
。
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
Service
private void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
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();
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}
- 关键代码,Service使用的也是
ContextImpl.createAppContext
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();
BroadcastReceiver
广播是一个比较复杂注册流程,有动态注册和静态注册,在高版本中基本上只能动态注册,由于本文不是分析启动流程,就简单的贴一下代码,关注整个流程的请专注流程代码。
private void handleReceiver(ReceiverData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
String component = data.intent.getComponent().getClassName();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
IActivityManager mgr = ActivityManagerNative.getDefault();
BroadcastReceiver receiver;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
data.intent.setExtrasClassLoader(cl);
data.intent.prepareToEnterProcess();
data.setExtrasClassLoader(cl);
receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
} catch (Exception e) {
。。。
}
try {
Application app = packageInfo.makeApplication(false, mInstrumentation);
...
ContextImpl context = (ContextImpl)app.getBaseContext();
sCurrentBroadcastIntent.set(data.intent);
receiver.setPendingResult(data);
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
} catch (Exception e) {
...
}
} finally {
sCurrentBroadcastIntent.set(null);
}
if (receiver.getPendingResult() != null) {
data.finish();
}
}
- 关键代码
这里调用的packageInfo.makeApplication(false, mInstrumentation)
简单理解可以理解成handleBindApplication
,只是它判断当前mApplication
如果存在就return了,不存在才会创建
Application app = packageInfo.makeApplication(false, mInstrumentation);
。。。
ContextImpl context = (ContextImpl)app.getBaseContext();
sCurrentBroadcastIntent.set(data.intent);
receiver.setPendingResult(data);
receiver.onReceive(context.getReceiverRestrictedContext(),data.intent);
ContentProvider
private IActivityManager.ContentProviderHolder installProvider(Context context,
IActivityManager.ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;
......
return retHolder;
}
这个比较奇怪,我们发现竟然是在安装的时候已经从外部传过来一个Context
,这是啥玩意。我们往上看它的实际调用在 handleBindApplication(AppBindData data)
-> installContentProviders(app, providers)
(前文标准了这句代码,有兴趣的回头在看一眼,在Application
中);我们可以理解为在应用启动的时候Application
就去启动了ContentProviders
。
Context的作用
根据前文我们知道Context一共有三个静态构造函数,createSystemContext
是一个特殊构造。这里暂时不说。
- Activity 使用 createActivityContext 创建属于自己的Context
Application
,Service
,BroadcastReceiver
从源头上都是使用createAppContext
来创建属于自己的Context
(有的是使用Application
创建的)。
从下图也能看出Activity的Context和其他各位的有很多区别,具体的在哪里差别呢?清大家带着自己的疑问去源码中遨游吧。
- 网图,不知道源出处。
总结
以上的代码证实了一个问题我们实际使用的每一个Context
,背后都用一个ContextImpl
在支撑,里面有对应的信息,包名等等,ContextImpl
创建的时机就是我们每个组件创建的初始化中系统帮我们录入了。
那么回到代码中我们会发现Activity在继承的时候并没有直接继承Context或者有ContextImpl对象,那是在哪里实现的,是不是有点绕了。
我们这样理解
- Context - 定义了我们使用它可以做的事情,可以引用的函数属性。 相当于先使用用身份证明可以做的事情。
- ContextImpl - 继承了Context,实际做背后很多事情的地方,对公众缺并不透明。
- ContextWrapper - 继承了Context,上面两个一个定义,一个在背后做事,我们走向柜台接待我们就是ContextWrapper,它直接被
Service
和Application
继承。(BroadcastReceiver
和ContentProvider
在创建时候主动调用每个对象的attachInfo()
将Context传给了对应的对象,属于特殊证件,直接发放不走办事大厅)。 - ContextThemeWrapper 继承了ContextWrapper,有实现了一些特殊功能给Activity,相当于现实生活的派出所吧。被Activity继承。
整个流程和现实中也有一丢丢的小区别,在代码中,创建了ContextImpl之后,然后调用具体对象的attach()
函数->attachBaseContext()
同步给ContextWrapper
.程序的流程是非黑即白的。
现实中肯定不会你拿着你的证明说你是谁,你就是谁,具体的办事部门还要自己在去确认一遍信息的。现实比程序复杂。
那么分析这些除了我们正确的理解Context
之外,还有什么用处呢?
1: 我们平常写代码的时候,老手总是说不要用Application
的Context去干乱七八糟的事情。这在现实中就是你总拿你家户口本去办你身份证就能解决的事情,大才小用。违反了程序设置中单一指责原则。还有一种不常见的情况就是我们在一些框架中需要传一个Context对象去绑定对应的生命周期,如果不小心使用了Application中的Context,会照成这些对象反复创建,只有在程序销毁的时候才会统一回收。
2: 还有就是说Context引起的内存泄漏。就相当于别人die,身份证应该回收,你却拿着还在办事。可想而知后果有多么严重。(超出生命周期使用Context,引起内存无法回收)
补充
- 1 文中源代码是6.0的源代码。
- 2 本文只是初略的分析Context的设计思想,形象的让大家理解这个东西。请不要一叶障目。
- 3 文中可能有些地方写的不够深入,或者语句不够通顺,希望大家在看过之后,有任何建议都可以告诉作者。磕头!