Android资源管理中的SharedLibrary和Dynamic Reference-------之Framework的处理(三)

前面我们已经编译好了资源共享库lib-out.apk(包名:com.google.android.test.shared_library),已经引用这个共享库的应用app-out.apk(包名:com.google.android.test.lib_client),剩下的就是安装、运行起来了。这个自然不必说,但是运行的时候我们的这个App使如何加载我们的资源共享库的呢?还记不记得我们在上期的两个疑问,还有一个期待中的车祸现场?

简要说一下应用的启动过程
我们知道,当init进程起来后,它会解析init.****.rc文件,进行许多操作,比如挂载分区、权限控制等,还会启动许多服务和进程,比如service_manager、media_server、Zygote等。其中Zygote进程是system_server以及我们所有Android应用程序进程的父进程。它的代码就在frameworks/base/cmds/app_process下,我们这里不做过多介绍,后面会有文章专门讨论。Zygote进程起来后,会创建虚拟机,然后加载ZygoteInit.java,执行其main函数,然后就进入java世界,fork出systemServer,然后它就会创建socket,作为服务端等待并处理来自systemServer的fork出其它应用进程的请求。

    当我们的App进程被fork出来后,它会调用ActivityTread的main方法,在ActivityThread的main方法里,它会通过AttachApplication这个binder调用把自己的IApplicationThread接口传给systemServer(或者说AMS),这样就建立起了我们的应用进程和AMS的双向通信机制(跨进程的,一方是应用进程,一方是systemServer进程)。AMS在处理我们的应用进程的AttachApplication请求时,会再次通过IApplicationThread回调应用进程的bindApplication方法,并且会带上一大堆的参数。我们的应用进程则会根据这些参数,设置自己的进程名,创建Application,并执行它的onCreate方法,这就进入到我们熟悉的流程了。

从bindApplication的一个参数说起
从AMS的bindApplication方法传过来的参数有很多:

        public final void bindApplication(String processName, ApplicationInfo appInfo,
                ProviderInfoList providerList, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges) {

我们现在只关注第二个,也就是ApplicationInfo。不知道大家是否还对《Android资源管理中的SharedLibrary和Dynamic Reference-------之资源共享库(一)》中介绍的ApplicationInfo有印象。我们一起回忆一下:

    /**
     * Full path to the base APK for this application.
     */
    public String sourceDir;
    /**
     * Full paths to the locations of extra resource packages (runtime overlays)
     * this application uses. This field is only used if there are extra resource
     * packages, otherwise it is null.
     *
     * {@hide}
     */
    @UnsupportedAppUsage
    public String[] resourceDirs;
        /**
     * Paths to all shared libraries this application is linked against.  This
     * field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
     * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
     * the structure.
     */
    public String[] sharedLibraryFiles;
    /**
     * Full path to the directory where native JNI libraries are stored.
     */
    public String nativeLibraryDir;

    是的,ApplicatonInfo的sharedLibraryFiles变量将会是我们的资源共享库包com.google.android.test.shared_library的路径。

ApplicatonInfo.sharedLibraryFiles的值从哪儿来

    既然这个sharedLibraryFiles就是我们的App引用的资源库的路径,那么系统是怎么获取到它的呢?其实这个很简单,包相关的信息都是PMS负责解析和保存的,这个肯定也不例外。我们在我们的App的AndroidManifest.xml里使用了uses-library标签,那么PMS肯定能解析到。我们简单跟一下:

private final boolean attachApplicationLocked(int pid) {
          .........

          ApplicationInfo appInfo = app.instrumentationInfo != null
                    ? app.instrumentationInfo : app.info;

          .........

          thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());

}

    这里的app是ProcessRecord的实例,而ProcessRecord在构造的时候会传入一个ApplicationInfo实例。这个ApplicationInfo实例是这么来的呢:
在这里插入图片描述
在这里插入图片描述

ActivityThread.java的实现:
在这里插入图片描述

     直接从ServiceManager里获取代理了,对应的实现类为PackageManagerService,也就是
PMS,跟进去:
在这里插入图片描述

这个比较简单,进入PackageParser.java。顾名思义,这个类就是用来解析包信息的:

    public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            PackageUserState state, int userId) {
        if (p == null) return null;
        if (!checkUseInstalledOrHidden(flags, state)) {
            return null;
        }
        if (!copyNeeded(flags, p, state, null, userId)
                && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 0
                        || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
            // In this case it is safe to directly modify the internal ApplicationInfo state:
            // - CompatibilityMode is global state, so will be the same for every call.
            // - We only come in to here if the app should reported as installed; this is the
            // default state, and we will do a copy otherwise.
            // - The enable state will always be reported the same for the application across
            // calls; the only exception is for the UNTIL_USED mode, and in that case we will
            // be doing a copy.
            updateApplicationInfo(p.applicationInfo, flags, state);
            return p.applicationInfo;
        }

        // Make shallow copy so we can store the metadata/libraries safely
        ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
        if (userId != 0) {
            ai.uid = UserHandle.getUid(userId, ai.uid);
            ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName);
        }
        if ((flags & PackageManager.GET_META_DATA) != 0) {
            ai.metaData = p.mAppMetaData;
        }
        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
            ai.sharedLibraryFiles = p.usesLibraryFiles;
        }
        if (state.stopped) {
            ai.flags |= ApplicationInfo.FLAG_STOPPED;
        } else {
            ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
        }
        updateApplicationInfo(ai, flags, state);
        return ai;
    }

我们终于看到了,sharedLibraryFiles来自于PackageParser里的包信息。那PackageParser又是如何得到的呢?当然是解析AndroidManifest.xml得到的了,我们这次直接看它的parseBaseApk方法:

private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {
         //解析包名

         //解析version name

         //解析version code

         //解析sharedUId信息

 

          //解析每一个元素

          while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                 .........

                  //终于解析到 uses-library了,好亲切啊

            } else if (tagName.equals("uses-library")) {
                sa = res.obtainAttributes(attrs,
                        com.android.internal.R.styleable.AndroidManifestUsesLibrary);

                // Note: don't allow this value to be a reference to a resource
                // that may change.
                String lname = sa.getNonResourceString(
                        com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
                boolean req = sa.getBoolean(
                        com.android.internal.R.styleable.AndroidManifestUsesLibrary_required,
                        true);

                sa.recycle();

                if (lname != null) {
                    lname = lname.intern();
                    if (req) {
                        //这个是必须依赖的库,没有的话,我们安装的时候就会失败

                        //对应  android:required="true"
                        owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, lname);
                    } else {
                        //这个是可选的依赖库,安装时不做检查,但是如果运行时找不到,还会出错

                        //对应android:required="false"
                        owner.usesOptionalLibraries = ArrayUtils.add(
                                owner.usesOptionalLibraries, lname);
                    }
                }

                XmlUtils.skipCurrentTag(parser);

            }

                 .........

           }

}

     终于解析完了,但还不完全,我们说了ApplicationInfo.sharedLibraryFiles是资源库的路径,但是此时我们看到PackageParser.Package中存储的却是:
这个属性的值:也就是我们使用的库的名字,这对不上啊!

其实,PMS给我们来了一个小魔法:
在这里插入图片描述
在这里插入图片描述

    SharedLibraryEntry类中,path表示我们的共享库的路径,apk则是共享库的包名。mSharedLibraries则是一个Map,它的key就是共享库的名字。我们看updateSharedLibrariesLPw和addSharedLibraryLPw方法:它会遍历我们的App必须依赖和可选依赖的所有库,并取出他们的名字(注意是库的名字,不是包名),然后根据库的名字,拿到对应的SharedLibraryEntry,再从SharedLibraryEntry中根据包名,找到对应的Package对象,再取出真正的路径,并把这个路径放到一个临时数组usesLibraryFiles中。最后,用这个临时数组替换掉Package对象中对应的数组!

    不过,现在有个问题,mSharedLibraries这个Map中的元素哪儿来的呢?肯定是PMS扫描包的时候,从一个一个的共享库包中添加过来的。其实它有两处来源,一处是在PMS构造的时候:

public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
            ..........

            ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
            for (int i=0; i<libConfig.size(); i++) {
                mSharedLibraries.put(libConfig.keyAt(i),
                        new SharedLibraryEntry(libConfig.valueAt(i), null));
            }

             ..........

}

    我们看到,它会从SystemConfig里读取,不过SystemConfig里的数据,似乎只有库的名字,和路径,包名是null:new SharedLibraryEntry(libConfig.valueAt(i), null)。我们看看SystemConfig是怎么实现的:

//frameworks/base/services/core/java/com/android/server/SystemConfig.java

SystemConfig() {
        // Read configuration from system
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "sysconfig"), false);
        // Read configuration from the old permissions dir
        readPermissions(Environment.buildPath(
                Environment.getRootDirectory(), "etc", "permissions"), false);
        // Only read features from OEM config
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "sysconfig"), true);
        readPermissions(Environment.buildPath(
                Environment.getOemDirectory(), "etc", "permissions"), true);
    }

构造的时候从/etc和/oem/etc目录的permissions和sysconfig目录读取配置文件:

void readPermissions(File libraryDir, boolean onlyFeatures) {
      .........

       for (File f : libraryDir.listFiles()) {
            // We'll read platform.xml last
            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                platformFile = f;
                continue;
            }

            if (!f.getPath().endsWith(".xml")) {
                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
                continue;
            }
            if (!f.canRead()) {
                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
                continue;
            }

            readPermissionsFromXml(f, onlyFeatures);
        }

        if (platformFile != null) {
            readPermissionsFromXml(platformFile, onlyFeatures);
        }

}

解析之:

private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
        ...........

        XmlPullParser parser = Xml.newPullParser();

        ..........

        

         else if ("library".equals(name) && !onlyFeatures) {
                    String lname = parser.getAttributeValue(null, "name");
                    String lfile = parser.getAttributeValue(null, "file");
                    if (lname == null) {
                        Slog.w(TAG, "<library> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (lfile == null) {
                        Slog.w(TAG, "<library> without file in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
                        mSharedLibraries.put(lname, lfile);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                }

}

    这部分代码很简单,解析标签,并把结果写入mSharedLibraries,我们就不多做说明,大家可以看一下/etc/permissions/platform.xml里的内容,有个直观的了解:
在这里插入图片描述

    果然,只有库的名字和路径。从这里我们看到,我们可以通过这样的配置文件来添加一个资源共享库,但这需要系统权限,这种方法适合各个手机厂商。
    PMS的mSharedLibraries还有另外一处来源,就是在扫描各个包的时候,我们就不做详细介绍了,有兴趣的同学可以自己到PMS源码里查看。

系统用ApplicatonInfo.sharedLibraryFiles做了啥

    现在我们已经完全知道了ApplicatonInfo.sharedLibraryFiles的来龙,那么去脉呢?Framework都用它做了什么呢?我们继续回到App启动时的bindApplication方法,当然这是个binder方法,会从AMS那边调用到我们的App进程。前面我们讲到这时候应用进程会设置自己的进程名,创建Base Context,创建Application,并执行它的onCreate方法等等。这里我们重点看一下Base Context的创建:

//frameworks/base/core/java/android/app/ContextImpl.java

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);
}

    Context创建的时候会传入两个非常重要的参数:mainThread和packageInfo。其实,Context我们平常都会经常使用,并叫它上下文,但这是一个非常模糊的概念,不同的人对Context的理解可能有所不同,我个人认为主要可以从三个方面来理解:

  1. 它内部有ActivityThread的实例。而ActivityThread及其内部Binder接口IApplicationThread是我们的应用进程和SystemServer双向交互的桥梁,也就是说Context会代表我们的应用进程SystemServer交互,接受SystemServer的调度。比如我们的Application、四大组件的生命周期管理,都离不开这个交互调度的桥梁。
  2. 它的内部有LoadedApk的实例。LoadedApk虽然还有其它作用,但在Context里面,它的作用主要体现为包信息载体。比如我们的应用包名、版本、使用的库等等信息。
  3. 它的内部有我们的资源。资源的加载,引用,都是通过它来完成的。

    这里我们先简要说一下资源的封装:ContextImpl—>Resources----->AssetManager。AssetManager是Resources的一个成员,Resources又是ContextImpl的一个成员。ContextImpl在构造的时候,会去加载资源,会走到LoadedApk的getResources方法:

public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
                    mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
        }
        return mResources;

 }

我们接下来看看mApplicationInfo.sharedLibraryFiles 最终传给了谁:

//ActivityThread.java

/**
     * Creates the top level resources for the given package.
     */
    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
            String[] libDirs, int displayId, Configuration overrideConfiguration,
            LoadedApk pkgInfo) {
        return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
                displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
    }

传给了ResourcesManager:

public Resources getTopLevelResources(String resDir, String[] splitResDirs,
            String[] overlayDirs, String[] libDirs, int displayId,
            Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
             Resources r;

            //先去查缓存,如果已经加载过,直接返回

           ..............

            //创建AssetManager,真正的资源管理类对象

            AssetManager assets = new AssetManager();

            //我们的应用本身也是一个资源包,应该把它的路径加入到AssetManager

            if (resDir != null) {
                   if (assets.addAssetPath(resDir) == 0) {
                          return null;
                   }
            }

           ...............

            //加入所有引用的资源共享库路径

            if (libDirs != null) {
                   for (String libDir : libDirs) {
                         if (assets.addAssetPath(libDir) == 0) {
                                Slog.w(TAG, "Asset path '" + libDir +
                                      "' does not exist or contains no resources.");
                          }
                     }
             }

            .................

            //创建Resources类对象

            r = new Resources(assets, dm, config, compatInfo, token);

            return r;

}

    到此,我们已经十分熟悉了,熟悉的Resources类,我们的已经把资源库加入到它的AssetManager里面去了,但是我们能够通过Resources.getString(),拿到资源库里的string资源吗?

原文链接:https://blog.csdn.net/dayong198866/article/details/95457830

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值