Context类结构
先来看看这个Context类结构图
Context类本身是一个纯abstact类,它有两个具体的实现子类:ContextImpl和ContextWrapper
ContextWrapper是一个Context的包装类,其构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,调用ContextWrapper方法都会转向其所包含的真正的Context对象。
ContextImpl是Context的具体实现类,Activity,Application,Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。ContextThemeWrapper其内部包含了与主题(Theme)相关的接口。
源码中Application,Activity,Service创建Context的过程
Application,Activity,Service在初始化过程中都会创建ContextImpl,我们分别来看看各种的创建过程:
Application对应的Context
程序第一次启动时,会通过bindApplication中发送H.BIND_APPLICATION消息,然后handleBindBapplication进行ContextImpl创建,该方法中有两处创建了ContextImpl对象,但这两处创建后的执行条件都是在if (data.instrumentationName != null) {中,只有在创建了Android Unit Test工程是,相应的Test程序才会满足这个条件。
如果不是测试工程的话,则调用makeApplication方法
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
在makeApplication方法中
//LoadApk.java
//makeApplication
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
ContextImpl的createAppContext
//ContextImpl.java
//createAppContext
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread,
packageInfo, null, null, false, null, null, Display.INVALID_DISPLAY);
}
所以createAppContext中的packageInfo参数实际上就是data.info,data.info来自handleBindBapplication方法调用,而handleBindBapplication的调用则是来自bindApplication发送消息,下面来看看bindApplication的代码:
767 public final void bindApplication(String processName, ApplicationInfo appInfo,
768 List<ProviderInfo> providers, ComponentName instrumentationName,
769 ProfilerInfo profilerInfo, Bundle instrumentationArgs,
770 IInstrumentationWatcher instrumentationWatcher,
771 IUiAutomationConnection instrumentationUiConnection, int debugMode,
772 boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
773 Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
774 Bundle coreSettings) {
775
···
819
820 AppBindData data = new AppBindData();
821 data.processName = processName;
822 data.appInfo = appInfo;
823 data.providers = providers;
824 data.instrumentationName = instrumentationName;
825 data.instrumentationArgs = instrumentationArgs;
826 data.instrumentationWatcher = instrumentationWatcher;
827 data.instrumentationUiAutomationConnection = instrumentationUiConnection;
828 data.debugMode = debugMode;
829 data.enableOpenGlTrace = enableOpenGlTrace;
830 data.restrictedBackupMode = isRestrictedBackupMode;
831 data.persistent = persistent;
832 data.config = config;
833 data.compatInfo = compatInfo;
834 data.initProfilerInfo = profilerInfo;
835 sendMessage(H.BIND_APPLICATION, data); //发送数据,在handleBindBapplication中接收
836 }
可以看到在bindApplication方法中,会用ApplicationInfo等相关参数构建一个AppBindData的数据类,注意,此时AppBindData的info还是为空,后面在handleBindBapplication中的getPackageInfoNoCheck才会进行赋值。
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
复制代码
可以看到,该方法是根据AppBindData的ApplicationInfo,CompatibilityInfo这两个参数进行创建的
//getPackageInfoNoCheck
1787 public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
1788 CompatibilityInfo compatInfo) {
1789 return getPackageInfo(ai, compatInfo, null, false, true, false);
1790 }
//getPackageInfo
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
1805 ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
1806 boolean registerPackage) {
1807 final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
1808 synchronized (mResourcesManager) {
1809 WeakReference<LoadedApk> ref;
1810 if (differentUser) {
1811 // Caching not supported across users
1812 ref = null;
1813 } else if (includeCode) {
1814 ref = mPackages.get(aInfo.packageName);
1815 } else {
1816 ref = mResourcePackages.get(aInfo.packageName);
1817 }
1818
1819 LoadedApk packageInfo = ref != null ? ref.get() : null;
1820 if (packageInfo == null || (packageInfo.mResources != null
1821 && !packageInfo.mResources.getAssets().isUpToDate())) {
1822 if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
1823 : "Loading resource-only package ") + aInfo.packageName
1824 + " (in " + (mBoundApplication != null
1825 ? mBoundApplication.processName : null)
1826 + ")");
1827 packageInfo =
1828 new LoadedApk(this, aInfo, compatInfo, baseLoader,
1829 securityViolation, includeCode &&
1830 (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
1831
1832 if (mSystemThread && "android".equals(aInfo.packageName)) {
1833 packageInfo.installSystemApplicationInfo(aInfo,
1834 getSystemContext().mPackageInfo.getClassLoader());
1835 }
1836
1837 if (differentUser) {
1838 // Caching not supported across users
1839 } else if (includeCode) {
1840 mPackages.put(aInfo.packageName,
1841 new WeakReference<LoadedApk>(packageInfo));
1842 } else {
1843 mResourcePackages.put(aInfo.packageName,
1844 new WeakReference<LoadedApk>(packageInfo));
1845 }
1846 }
1847 return packageInfo;
1848 }
1849 }
该方法会创建一个ActivityThread类的全局packageInfo(LoadApk)对象。接下来就调用到data.info.makeApplication(data.restrictedBackupMode, null)这个就是LoadApk(packageInfo)的来源
Application中创建Context的过程
Activity对应的Context
启动Activity时,AMS(ActivityManagerService)会通过Binder调用到ActivityThread的scheduleLaunchActivity,在该类内存创建一个ActivityClientRecord对象,然后发送消息sendMessage(H.LAUNCH_ACTIVITY, r)
630 public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
631 ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
632 CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
633 int procState, Bundle state, PersistableBundle persistentState,
634 List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
635 boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
636
637 updateProcessState(procState, false);
638
639 ActivityClientRecord r = new ActivityClientRecord();
640
641 r.token = token;
642 r.ident = ident;
643 r.intent = intent;
644 r.referrer = referrer;
645 r.voiceInteractor = voiceInteractor;
646 r.activityInfo = info;
647 r.compatInfo = compatInfo;
···
659 r.overrideConfig = overrideConfig;
660 updatePendingConfiguration(curConfig);
661 //发送消息,最后调用handleLaunchActivity()
662 sendMessage(H.LAUNCH_ACTIVITY, r);
663 }
handleLaunchActivity中会继续调用到performLaunchActivity,该方法中创建ContextImpl的代码如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
2297 if (r.packageInfo == null) {
2298 r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
2299 Context.CONTEXT_INCLUDE_CODE);
2300 }
···
2344 if (activity != null) {
//创建context
2345 Context appContext = createBaseContextForActivity(r, activity);
···
2350 activity.attach(appContext, this, getInstrumentation(), r.token,
2351 r.ident, app, r.intent, r.activityInfo, title, r.parent,
2352 r.embeddedID, r.lastNonConfigurationInstances, config,
2353 r.referrer, r.voiceInteractor);
可以看到首先为r.activityInfo变量赋值,getPackageInfo方法的执行逻辑基本和getPackageInfoNoCheck相同,然后创建Context,具体的创建Context在createBaseContextForActivity方法中
2425 private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
2426 int displayId = Display.DEFAULT_DISPLAY;
2427 try {
2428 displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
2429 } catch (RemoteException e) {
2430 }
2431
2432 ContextImpl appContext = ContextImpl.createActivityContext(
2433 this, r.packageInfo, displayId, r.overrideConfig);
2434 appContext.setOuterContext(activity);
···
2453 return baseContext;
2454 }
Activity创建Context的过程
Service对应的Context
启动Service时,AMS会通过Binder调用到ActivityThread的scheduleCreateService方法,在该方法中构造CreateServiceData对象
718 public final void scheduleCreateService(IBinder token,
719 ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
720 updateProcessState(processState, false);
721 CreateServiceData s = new CreateServiceData();
722 s.token = token;
723 s.info = info;
724 s.compatInfo = compatInfo;
725
726 sendMessage(H.CREATE_SERVICE, s);
727 }
接下来发送H.CREATE_SERVICE,执行handleCreateService()方法,创建Context对象
2854 LoadedApk packageInfo = getPackageInfoNoCheck(
2855 data.info.applicationInfo, data.compatInfo);
2856 Service service = null;
2867 ···
2871 ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
2872 context.setOuterContext(service);
2873
2874 Application app = packageInfo.makeApplication(false, mInstrumentation);
2875 service.attach(context, this, data.info.name, data.token, app,
2876 ActivityManagerNative.getDefault());
2877 service.onCreate();
这里和之前的Activity创建Context基本相同,赋值代码也是使用了getPackageInfoNoCheck方法,这就意味着Service对应Context内部的mPackageInfo与Activity,Application中是完全相同的。
Service中创建Context的过程
Context之间的关系
从上面的分析可以看出,Application/Activity/Service中创建Context对象的过程基本是相同的,代码结构也很类似,下面是不同Context子类中PackageInfo对象的来源
小结
一个应用程序中包含Context个数 = Service个数+Activity个数+1(Application)+ 其他 ContextImpl 个数
网上也有一些文章提出,Context 个数 = 2 x(Service 个数 + Activity 个数 + Application 个数) + 其他 ContextImpl 个数,这里的乘以2表示,创建Applciation/Service/Activity过程会创建基础对象ContextImpl,Application等是代理对象,但从前面的Context类结构就可以看出,他们最终都继承ContextWrapper对象,而ContextWrapper只是一个包装类,真正的实现是ContextImpl,所以Applciation/Service/Activity的创建过程必然包含创建ContextImpl,Application当然是代理对象,如果只是从纯个数的讨论来说并没什么太大意义。我倾向于Context表示上下文(Application,Activity,Service)的个数,不过ContextImpl还可能ContextImpl.createPackageContext()去读取其他apk的资源,所以这里 + 其他 ContextImpl 个数是合理的。
应用程序中包含多个ContextImpl对象,而其内部变量的mPackageInfo却指向同一个LoadApk对象,这种设计结构一般意味着ContextImpl是一个轻量级类,而LoadApk是重量级类,从我们之前的对ContextImpl也可以看出来,ContextImpl内部的大多数重量级方法内部实现都转向了mPackageInfo(LoadApk)对象对应的方法,即事实上是调用了同一个LoadApk对象,这样从系统效率的角度看也还是合理的。
Context的作用范围
Context作用域ApplicationActivityServiceShow a DialogNOYESNOStart an Activity不推荐YES不推荐Layout Inflation不推荐YES不推荐Start a ServiceYESYESYESSend a BroadcastYESYESYESRegister Broadcast ReceiverYESYESYESLoad Resource ValueYESYESYES
- 如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service同Application。
- 在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。 一句话总结:凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
getApplicaiton和getApplicationContext的区别
我们通过程序调用打印出这两者
//调用
Log.d("jackie","getApplication"+application)
Log.d("jackie","getApplicationContext"+applicationContext)
//打印结果
2020-10-18 22:05:55.323 18590-18590/com.jackie.testapk D/jackie: getApplicationandroid.app.Application@eee7231
2020-10-18 22:05:55.323 18590-18590/com.jackie.testapk D/jackie: getApplicationContextandroid.app.Application@eee7231
可以看到打印出的内存地址都是相同的,看来他们是同一个对象;再来看看这个两个方法的源码
//===============getApplicationContext==========
//ContextWrapper.java
@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}
//================getApplication=================
//Activity
/** Return the application that owns this activity. */
public final Application getApplication() {
return mApplication;
}
//Service
/** Return the application that owns this service. */
public final Application getApplication() {
return mApplication;
}
从前面的分析可以看到Application,Activity,Service的packageInfo是同一个来源,这三种最终都是继承与ContextWrapper。既然这两个方法得到的结果是相同的,那么Android为什么要提供两个功能重复的方法呢?实际上这两个方法在作用域上有比较大的区别。getApplication()方法的语义性非常强,一看就知道是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。如果在BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法了。
Application中真的不能弹出Dialog吗?
首先我们做个测试,用getApplciationContext创建Dialog,然后弹出Dialog。
//vm api 18
public void test1(){
Dialog alert = new Dialog(getApplicationContext());
//alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
alert.setContentView(view);
alert.show();
}
复制代码
然后就可以看到控制台报错日志
10-19 10:19:17.646 6451-6451/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at android.view.ViewRootImpl.setView(ViewRootImpl.java:563)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:269)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.Dialog.show(Dialog.java:281)
at com.jackie.testdialog.MainActivity.test1(MainActivity.java:52)
at com.jackie.testdialog.MainActivity$1.onClick(MainActivity.java:27)
提示我们token为空,无法add到window中。
这里我们需要简要介绍一下,Android中所有的视图都是通过Window来呈现的,不管是Activity,Dialog,还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。
Window有三种类型:
- 应用级别窗口,层级范围1~99,比如Activity。
- 子窗口,不能单独存在,必须依附于特定的父window,层级范围1000~1999,比如Dialog。
- 系统级别窗口,层级范围2000~2999,比如Toast。系统类型的window是需要检查权限的,需要在AndroidManifest中声明。
再看看上面的错误,因为传的是getApplcationContext,所以我们没有token,而这时候要弹出Dialog就报错了。另外,系统Window比较特殊,它可以不需要token。因此在上面的例子中,只需要指定对话框的Window类型为系统类型就可以正常弹出对话框了。
//vm api 18 android 18上测试成功
public void test1(){
Dialog alert = new Dialog(getApplicationContext());
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
alert.setContentView(view);
alert.show();
}
//vm api 29 android29上测试成功
public void test(){
Dialog alert = new Dialog(getApplicationContext());
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
View view = LayoutInflater.from(this).inflate(R.layout.dialog,null);
alert.setContentView(view);
alert.show();
}
同时需要声明权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
总结
至此,我们已经将Context类结构,Context的作用范围等都梳理了一遍,有时候要充分了解一个功能,需要深入源码,手动做实验验证,查找相关文章,才能充分掌握。
作者:伤心的猪大肠
链接:https://juejin.im/post/6885168629948612616
本文暂时没有评论,来添加一个吧(●'◡'●)