查看原文
其他

AndroidManifest中uses-library怎么起作用的?

鸿洋
2024-12-13

The following article is from 牛晓伟 Author 牛晓伟

前言
阅读该篇之前,建议先阅读下面的系列文章:
Android深入理解包管理--PackageManagerService和它的“小伙伴”
Android深入理解包管理--记录存储模块

本文摘要

这是包管理系列的第三篇文章,本文同样采用自述的方式来介绍共享库模块。共享库模块是PackageManagerService服务的其中一个模块,通过本文您将了解共享库是啥?共享库模块是如何管理共享库的以及共享库管理模块在PackageManagerService中发挥了哪些作用?共享库如何被其他apk使用的。(文中代码基于Android13)

本文大纲

1. 共享库是啥?
2. 共享库模块又是个啥?
3. 共享库如何被使用
4. 总结
1享库是啥?

大家好,我是共享库模块,在介绍我之前,我需要先把我的管理对象共享库介绍给大家,只有先了解了它才能对我有一个深刻的认识。
共享库首先它是一个库,库大家肯定非常熟悉了,库可以是一个jar文件 (jar包代表java库) 也可以是一个so文件 (so是二进制可执行文件代表native库),而它的形容词共享代表该库是可以被多个程序使用的。共享库也就是可以被多个程序使用的库。在Android中共享库除了上面的意思之外,还要再加一条就是共享库是由系统提供的可以被多个程序使用的系统库,在Android中共享库可以是一个jar、so、apk文件。

1.1 使用共享库

在开发App的时候,使用一些第三方库的话,需要在build.gradle文件中把这些库引入或者直接把对应的jar/so文件放入工程中,而共享库的使用是在AndroidManifest.xml文件中使用uses-library标签 (使用java库)或者uses-native-library标签 (使用native库) 标签,如下例子:
<uses-library android:name="com.google.android.maps"
            android:required="false" />


<uses-library android:name="com.here.android"
            android:required="false" />


其中android:name是共享库的名字,对于java共享库是共享库的包名,对于native共享库是共享库的文件名。android:required的值是boolean值,true代表该共享库是必须的,如果设备上不存在该共享库则该Apk不会被安装;false代表共享库不是必须的,如果设备上不存在该共享库改Apk也会被安装。
如上例子,该apk使用了name为com.google.android.mapscom.here.android这两个库。

1.2 声明共享库

声明共享库的意思就是告诉系统,我这个apk是可以作为一个共享库被别的程序使用的。只有系统Apk才可以声明共享库,为啥有这样的规定?主要原因是前面提到过在Android中共享库中的库是指由系统提供的库,普通Apk声明的库不可能是系统的库。声明共享库非常简单在AndroidManifest.xml文件中使用library标签,如下例子:
<library android:name="android.ext.shared" />


上面例子该apk会变为一个共享库,该共享库的name为android.ext.shared

1.3 特殊的共享库

大家应该对framework.jar熟悉吧,不熟悉也没关系,那我提醒下大家,在开发App的时候,是不是会用到Activity、Context、Service等这些类,而这些类就是被打包在framework.jar中的。
framework.jar其实是一个非常特殊的共享库,怎么个特殊法呢,请看下面:
  1. 首先不需要配置就可以直接使用framework.jar中的类,大家可以仔细想想在开发App的时候是不是没有在AndroidManifest.xml文件中使用uses-library标签来引入该库,但是却可以使用它包含的类。这要得益于zygote进程的预加载机制,zygote进程会把framework.jar中的类提前预加载到ClassLoader,这样App进程被fork成功后,App进程与zygote进程共用一个ClassLoader。
  2. 其次framework.jar中的各种类被加载后在所有进程中都共享一份内存,这也得益于zygote进程的预加载机制。而其他共享库可不是共享一份内存。
  3. 最后framework.jar是最大的共享库,它包含的类可是最多最多的。
framework.jar正如其名,它包含的可是所有App都会用到的类,它是一个通用的共享库。正因如此framework.jar共享库被zygote进程提前预加载,这样可以加快App启动速度,同时也节省内存。
而像其他的一些非通用的共享库(比如android.test.base库),肯定是不需要zygote进程提前预加载的。假如加载了它们,但是并不是所有App都会用到,这样的加载岂不是白白浪费了大量珍贵的内存。因此针对其他非通用的共享库,按需使用即可。

1.4 小结

在Android中共享库是指由系统提供的可以被多个程序使用的系统库,而共享库的使用是在AndroidManifest.xml文件中使用uses-library标签 (使用java库)或者uses-native-library标签,系统Apk是可以通过library标签声明共享库的,framework.jar是一个特殊的共享库。
以上就是关于共享库的介绍,那现在咱们进入正题,来介绍下我。
2共享库模块是啥?

先来看一PackageManagerService是如何划分模块的,以及模块之间的联系,如下图:

我是共享库模块,我主要服务于PackageManagerService服务,凡是和共享库有关的事情都由我负责,我受Apk管理模块的影响,Apk的扫描/删除/安装/更新都会影响到我。
我所做的事情主要是管理共享库和提供共享库服务,而我把这些事情交给了SharedLibrariesImpl类,那就从这两方面来具体介绍下我吧。

2.1 管理共享库

管理共享库就是把所有共享库收集起来,对它们进行管理,而这里的共享库主要指内置共享库、声明的共享库、静态共享库 (静态共享库不属于本篇重点,故不赘述)。

2.1.1 内置共享库

内置共享库顾名思义就是已经提前内置在系统中的共享库,内置共享库的信息是存储在各种xml文件中的,SystemConfig类会在systemserver进程启动的时候从这些文件中把内置共享库信息读取出来,在PackageManagerService的构造方法中,SharedLibrariesImpl会把这些共享库信息收集起来,内置共享库信息只要收集起来是不会在发生变化的。

2.1.2 声明的共享库

而声明的共享库指的是系统Apk声明的共享库。声明的共享库信息会在PackageManagerService把所有Apk都扫描完毕后,才会从扫描的Apk信息中把声明的共享库信息收集起来。声明的共享库信息收集起来后是会发生变化的,比如某个共享库被移除了、共享库版本信息升级了等。

2.1.3 如何存储共享库

同样先看下图:

所有的共享库是以key value的字典形式存储的,key值是共享库的名字 (不管哪种类型的共享库都必须有一个名字),而value值是不同版本的共享库,不同版本的共享库也是以key value的字典形式存储的,只不过key值是版本对应的整数值,value对应的是SharedLibraryInfo,SharedLibraryInfo存储了共享库的基本信息如包名、类型 (内置、静态等)、共享库文件路径、依赖等。
下面是对应的代码,请自行取阅:
//SharedLibrariesImpl类

//mSharedLibraries存储了所有的共享库,key值是共享库的名字,value是共享库的信息
private final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> mSharedLibraries;

2.1.4 管理共享库

当某个系统Apk声明了新的共享库时,我共享库模块会把它加入mSharedLibraries (该属性在上面代码片段中);当某个系统Apk移除了某个共享库时,共享库模块会把它从mSharedLibraries中删除;当某个系统Apk声明的共享库信息发生变化的时候,比如共享库版本升级,共享库模块会把对应的共享库信息进行更新。

2.2 提供共享库服务

我收集了这么多的共享库,不管是内置的还是静态的还是声明的共享库信息,收集起来的作用就是供共享库使用者使用的。还记得如何使用共享库吗?是在AndroidManifest.xml文件中使用uses-library或者uses-native-library标签 标签,如下例子:
<uses-library android:name="com.google.android.maps"
            android:required="false" />


<uses-library android:name="com.here.android"
            android:required="false" />


提供共享服务也就是把共享库的使用者使用的共享库信息收集起来,并交给它,看了这段话是不是很绕,那就举个例子如上面的Apk中使用了名字为com.google.android.mapscom.here.android的两个共享库,当PackageManagerService解析到这些信息后,会告知我某个Apk需要com.google.android.mapscom.here.android的两个共享库信息,而我会根据名字、版本信息等去查询相应的共享库信息,查询到相应共享库后,会把这些共享库信息交给该Apk。

2.3 小结

共享库模块是PackageManagerService服务的一个模块,它的主要工作是管理共享库和提供共享库服务,管理共享库也就是会把内置共享库、声明的共享库、静态共享库都收集起来进行管理。提供共享库服务也就是为共享库使用者提供共享库的查询服务。

3共享库如何被使用

这一小节是共享库内容的延伸,共享库如何被使用指的是共享库使用者从共享库模块拿到共享库信息,并且使用共享库的过程,为了方便大家理解,请看下图

那就结合上图来介绍下共享库被使用的过程。

3.1 App进程请求ApplicationInfo信息

不管是通过startActivity的方式还是通过启动service的方式启动一个App,当这个App进程不存在的时候,systemserver进程会通过socket告知zygote进程需要孵化某个App进程了,当这个App进程被孵化后,它的ActivityThread的main方法会被执行,当一切都准备就绪后,当前App进程要想继续运行,它需要知道它的apk文件路径、共享库文件路径、包名等等非常关键的数据,而这些数据是需要从ActivityManagerService获取到。attachApplication方法通过binder通信进入了ActivityManagerService服务。

3.2 ActivityManagerService返回ApplicationInfo信息

请求到了ActivityManagerService服务,根据uid (app唯一id)和pid (进程id)可以获取到相应的ApplicationInfo信息,而这个ApplicationInfo对象的各种属性其实已经在之前准备好了,ApplicationInfo中的sharedLibraryFilessharedLibraryInfos属性是存储共享库信息的。
下面代码是生成ApplicationInfo的代码,请自行取阅:

PackageInfoUtils类

public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
            @PackageManager.ApplicationInfoFlagsBits long flags,
            @NonNull PackageUserStateInternal state, @UserIdInt int userId,
            @Nullable PackageStateInternal pkgSetting) {
        省略代码······

        ApplicationInfo info = PackageInfoWithoutStateUtils.generateApplicationInfoUnchecked(pkg,
                flags, state, userId, false /* assignUserFields */);

        initForUser(info, pkg, userId);

        if (pkgSetting != null) {
            // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
            PackageStateUnserialized pkgState = pkgSetting.getTransientState();
            info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();

            //pkgState.getUsesLibraryFiles()和pkgState.getUsesLibraryInfos()获取的是共享库的信息,而这些信息在扫描完毕apk后就有值了
            List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
            List<SharedLibraryInfo> usesLibraryInfos = pkgState.getUsesLibraryInfos();
            //存储共享库信息则
            info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
                    ? null : usesLibraryFiles.toArray(new String[0]);

            info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
            省略代码······
        }

        省略代码······
        return info;
    }

3.3 App进程拿到ApplicationInfo信息

App进程拿到ApplicationInfo信息后,会通知LoadedApk,该类会把ApplicationInfo中的apk路径、native lib路径、共享库文件路径等信息交给PathClassLoader,这样当前App进程就可以使用共享库中的类了。
下面是相关代码,请自行取阅:

//PathClassLoader 类

public class PathClassLoader extends BaseDexClassLoader {
}

public class BaseDexClassLoader extends ClassLoader {
  //共享库相关的ClassLoader
  protected final ClassLoader[] sharedLibraryLoaders;
  protected final ClassLoader[] sharedLibraryLoadersAfter;

  protected Class<?> findClass(String name) throws ClassNotFoundException {
        //查找某个类时,先从共享库对应的ClassLoader中查找,找到则返回,否则从当前apk中查找
        if (sharedLibraryLoaders != null) {
            for (ClassLoader loader : sharedLibraryLoaders) {
                try {
                    return loader.loadClass(name);
                } catch (ClassNotFoundException ignored) {
                }
            }
        }
        // Check whether the class in question is present in the dexPath that
        // this classloader operates on.
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c != null) {
            return c;
        }
        // Now, check whether the class is present in the "after" shared libraries.
        if (sharedLibraryLoadersAfter != null) {
            for (ClassLoader loader : sharedLibraryLoadersAfter) {
                try {
                    return loader.loadClass(name);
                } catch (ClassNotFoundException ignored) {
                }
            }
        }
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
}

3.4 小结

总结下共享库被使用的过程:(以下基于某个Apk使用了某个共享库)
  1. 共享库模块会根据共享库名字、版本信息等查询到对应的共享库信息,并且把共享库信息交给对应的Apk (其实保存在PackageSetting对象)。
  2. 当App进程启动后,会从ActivityManagerService拿到对应的ApplicationInfo信息,而ApplicationInfo存储了apk路径、包名、apk版本号、共享库信息等等非常关键的信息。
  3. App进程会把ApplicationInfo中的apk路径、native lib路径、共享库文件路径等信息交给PathClassLoader,PathClassLoader的父类BaseDexClassLoader会持有关于共享库相关的ClassLoader。
  4. 在查找某个类时,会先从共享库对应的ClassLoader查找,找到了返回;否则从BaseDexClassLoader中查找。

用一句简单的话总结就是:共享库文件会被加载到App进程的PathClassLoader中,这样App进程就可以找到共享库的类了。从此也可以看出除framework.jar外的共享库是在每个App进程都占用自己的内存空间,没有共享内存。

4总结


本文介绍了共享库,共享库就是由系统提供的可以被多个程序使用的系统库。而共享库模块是服务于PackageManagerService服务的,它的作用就是管理所有的共享库,并且为共享库使用者提供共享库查询服务。最后介绍了共享库是如何被使用的。


最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

Android | 扩大View点击区域的几种方式
进阶篇|大厂常用的启动优化有哪些?
鸿蒙中是如何实现UI自动刷新的?


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

继续滑动看下一个
鸿洋
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存