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方法传过来的参数有很多:

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

        是的,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;

                }

}

         这部分代码很简单,解析<library>标签,并把结果写入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资源吗?

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值