阅读 966

Android中的Context总结分析

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对象的来源

类名远程数据类本地数据类型赋值方式
ApplicationApplicationInfoAppBindDatagetPackageInfoNoCheck()
ActivityActivityInfoActivityClientRecordgetPackageInfo()
ServiceServiceInfoCreateServiceDatagetPackageInfoNoCheck()
小结

一个应用程序中包含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作用域ApplicationActivityService
Show a DialogNOYESNO
Start an Activity不推荐YES不推荐
Layout Inflation不推荐YES不推荐
Start a ServiceYESYESYES
Send a BroadcastYESYESYES
Register Broadcast ReceiverYESYESYES
Load Resource ValueYESYESYES
  1. 如果我们用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。
  2. 在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. 应用级别窗口,层级范围1~99,比如Activity。
  2. 子窗口,不能单独存在,必须依附于特定的父window,层级范围1000~1999,比如Dialog。
  3. 系统级别窗口,层级范围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的作用范围等都梳理了一遍,有时候要充分了解一个功能,需要深入源码,手动做实验验证,查找相关文章,才能充分掌握。

参考文章

www.jianshu.com/p/51d63a1ff…

Android开发艺术探索

文章分类
Android
文章标签