Context到底是什么,身份证么?

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函数 createSystemContextcreateAppContextcreateActivityContext
        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_role.png

  • 网图,不知道源出处。

总结

以上的代码证实了一个问题我们实际使用的每一个Context,背后都用一个ContextImpl在支撑,里面有对应的信息,包名等等,ContextImpl创建的时机就是我们每个组件创建的初始化中系统帮我们录入了。

那么回到代码中我们会发现Activity在继承的时候并没有直接继承Context或者有ContextImpl对象,那是在哪里实现的,是不是有点绕了。

我们这样理解

  • Context - 定义了我们使用它可以做的事情,可以引用的函数属性。 相当于先使用用身份证明可以做的事情。
  • ContextImpl - 继承了Context,实际做背后很多事情的地方,对公众缺并不透明。
  • ContextWrapper - 继承了Context,上面两个一个定义,一个在背后做事,我们走向柜台接待我们就是ContextWrapper,它直接被ServiceApplication继承。(BroadcastReceiverContentProvider在创建时候主动调用每个对象的attachInfo()将Context传给了对应的对象,属于特殊证件,直接发放不走办事大厅)。
  • ContextThemeWrapper 继承了ContextWrapper,有实现了一些特殊功能给Activity,相当于现实生活的派出所吧。被Activity继承。

Context.png 整个流程和现实中也有一丢丢的小区别,在代码中,创建了ContextImpl之后,然后调用具体对象的attach()函数->attachBaseContext()同步给ContextWrapper.程序的流程是非黑即白的。

现实中肯定不会你拿着你的证明说你是谁,你就是谁,具体的办事部门还要自己在去确认一遍信息的。现实比程序复杂。

那么分析这些除了我们正确的理解Context之外,还有什么用处呢?

1: 我们平常写代码的时候,老手总是说不要用Application的Context去干乱七八糟的事情。这在现实中就是你总拿你家户口本去办你身份证就能解决的事情,大才小用。违反了程序设置中单一指责原则。还有一种不常见的情况就是我们在一些框架中需要传一个Context对象去绑定对应的生命周期,如果不小心使用了Application中的Context,会照成这些对象反复创建,只有在程序销毁的时候才会统一回收。

2: 还有就是说Context引起的内存泄漏。就相当于别人die,身份证应该回收,你却拿着还在办事。可想而知后果有多么严重。(超出生命周期使用Context,引起内存无法回收)

补充

  • 1 文中源代码是6.0的源代码。
  • 2 本文只是初略的分析Context的设计思想,形象的让大家理解这个东西。请不要一叶障目。
  • 3 文中可能有些地方写的不够深入,或者语句不够通顺,希望大家在看过之后,有任何建议都可以告诉作者。磕头!

猜你喜欢

转载自juejin.im/post/7046280396752814093