安卓应用开发如何使用系统Native库

关于安卓系统中应用如何使用系统依赖库

Views: 14
0 0
Read Time:2 Minute, 47 Second

最近在开发一个Android应用的过程中遇到了一个问题,就是在集成第三方C++开源项目时该开源项目依赖于系统原生的OpenCL实现,通常而言厂商都会将OpenCL实现以共享库的形式集成到系统中供外部使用。

尽管我已经事先确认了系统中存在该共享库,但在应用启动阶段还是报错表示无法找到该依赖库。通过一番排查,终于发现了该问题所在。

简单来说,在Android12版本之后,如果应用需要使用Android系统原生提供的库,需要在App的AndroidManifest.xml中<Application>中添加相应的权限配置,如下所示:

<uses-native-library android:name="libOpenCL.so" android:required="false"/>
<uses-native-library android:name="libOpenCL-car.so" android:required="false"/>
<uses-native-library android:name="libOpenCL-pixel.so" android:required="false"/>

通过上述声明,在应用安装时就会向系统表明该应用将会使用系统提供的libOpenCL.so,libOpenCL-car.so以及libOpenCL-pixel.so,后面的android:required用于标识该应用对该依赖库的依赖程度:如果值为true,系统内不存在该依赖库时则无法进行安装,如果值为false,系统内不存在该依赖库时也可以进行安装。

按照上述方法进行处理后,发现还是无法正常使用该依赖库。通过后续搜索,我在这个Link中找到相应的解释:针对/vendor/lib,/vendor/lib64以及/system/lib,/system/lib64这两个目录下的共享库,如果需要对应用开放,我们应该在/vendor/etc/public.libraries.txt和/system/etc/public.libraries.txt进行声明。由于我需要的libOpenCL.so库在/vendor/lib64/目录下,我们需要在/vendor/etc/public.libraries.txt进行添加,在添加之后即可以被应用使用。

那么,类似于/vendor/etc/public.libraries.txt以及/system/etc/public.libraries.txt这两个文件,到底是如何工作的呢。

这里我们需要深入安卓系统的ART,了解安卓系统是如何加载这些依赖库并实现接口调用的。

在安卓系统中,负责加载依赖库并实现接口调用的模块为libnativeloader,其源码位于${ANDROID_BUILD_TOP}/art/libnativeloader。

机制概述

libnativeloader负责在ART中加载Native共享库,这些共享库有两种来源:一是APK打包时JNI接口引入的Native库,二是系统自身开放出来的Native共享库。

最典型的使用案例就是当我们在App代码内通过System.loadLibrary(“library-name”)加载共享库,当调用该方法时,ART 将调用委托给 libnativeloader 库,并传递指向调用该方法的类加载器(Class Loader)的引用,也就是该库的调用方。然后,libnativeloader 根据类加载器查找与之关联的链接器命名空间,并尝试从该命名空间加载请求的库。

而链接器命名空间在 APK 被加载到系统中时创建,并且与加载该 APK 的类加载器关联。链接器命名空间的配置确保只有嵌入在 APK 中的 JNI 库可以从该命名空间访问,从而防止 APK 加载其他 APK 的 JNI 库。

链接器命名空间的配置也取决于 APK 的其他特征,例如 APK 是否属于系统应用,如果非系统应用或者预装应用,即从应用市场安装的应用或者通过其他方式安装的应用,只有列在 /system/etc/public.libraries.txt 或者/vendor/etc/ public.libraries.txt中的公共本地库可以从平台被访问,而对于系统应用或者预装应用 ,所有在 /system/lib64,/vendor/lib64下的库都可以访问。

libnativeloader除了用于在ART中加载共享库,还负责抽象两种动态链接的接口:libdl.so以及libnativebridge.so,前者负责无需翻译的动态链接,比如x86_64架构的应用运行在x86_64的PC上时,通过libdl.so直接进行加载链接;后者则负责需要进行翻译的动态链接,比如arm64架构的应用运行在x86_64的PC上时,通过libnativebridge.so进行加载链接。

libnativeloader的源码组成主要包含native_loader.cpp、native_loader_namespace.cpp、public_libraries.cpp、library_namespaces.cpp四个源码。native_loader.cpp实现了libnativeloader的公共接口,属于对library_namespaces.cpp和native_loader_namespace.cpp的封装。library_namespcaes.cpp实现了LibraryNamespaces的单例,负责创建与配置链接器的命名空间。native_loader_namespaces.cpp实现了NativeLoaderNameSpace类。而publoc_libraries.cpp负责读取各个分区下的*.txt文件,用于决定公共可见的native库。

调用流程

这里我们来看看具体的调用流程:

art/runtime/runtime.cc->Runtime::Start()
art/runtime/jni/java_vm_ext.cc->JavaVMExt::LoadNativeLibrary
art/libnativeloader/native_loader.cpp->OpenNativeLibrary
art/libnativeloader/native_loader.cpp->CreateClassLoaderNamespaceLocked
art/libnativeloader/library_namespaces.cpp->LibraryNamespaces::Create

我们看看具体是怎么实现的,这里我们直接看LibraryNamespaces类中的Create方法:

Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env,
                                                         uint32_t target_sdk_version,
                                                         jobject class_loader,
                                                         ApiDomain api_domain,
                                                         bool is_shared,
                                                         const std::string& dex_path,
                                                         jstring library_path_j,
                                                         jstring permitted_path_j,
//获取系统system分区下公开可被使用的库
std::string system_exposed_libraries = default_public_libraries();
.....

//获取系统vendor分区下公开可被使用的库
 const std::string vendor_libs =
      filter_public_libraries(target_sdk_version, uses_libraries, vendor_public_libraries());

//获取系统product分区1下公开可被使用的库
 const std::string product_libs =
      filter_public_libraries(target_sdk_version, uses_libraries, product_public_libraries());
....

}

跟随着调用链路,我们可以看到一系列类似于Init*PublicLibraries的函数,也就是读取各个分区下的public_libraries.txt文件进行解析,以 /system/etc/public.libraries.txt的解析为例:

static std::string InitDefaultPublicLibraries(bool for_preload) {
  //root_dir->/system
  std::string config_file = root_dir() + kDefaultPublicLibrariesFile;
  Result<std::vector<std::string>> sonames =
      ReadConfig(config_file, [&for_preload](const struct ConfigEntry& entry) -> Result<bool> {
        if (for_preload) {
          return !entry.nopreload;
        } else {
          return true;
        }
      });
  if (!sonames.ok()) {
    LOG_ALWAYS_FATAL("%s", sonames.error().message().c_str());
    return "";
  }

  // If this is for preloading libs, don't remove the libs from APEXes.
  if (!for_preload) {
    // Remove the public libs provided by apexes because these libs are available
    // from apex namespaces.
    for (const auto& [_, library_list] : apex_public_libraries()) {
      std::vector<std::string> public_libs = base::Split(library_list, ":");
      sonames->erase(std::remove_if(sonames->begin(),
                                    sonames->end(),
                                    [&public_libs](const std::string& v) {
                                      return std::find(public_libs.begin(), public_libs.end(), v) !=
                                             public_libs.end();
                                    }),
                     sonames->end());
    }
  }

  std::string libs = android::base::Join(*sonames, ':');
  ALOGD("InitDefaultPublicLibraries for_preload=%d: %s", for_preload, libs.c_str());
  return libs;
}

这个函数中的root_dir()得到/system,而kDefaultPublicLibrariesFile的值为/etc/public.libraries.txt,也就是读取/system/etc/public.libraries.txt,通过解析后将这些依赖库加载到对应的链接器空间,这样应用就可以使用了。

Happy
Happy
100 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
FranzKafka95
FranzKafka95

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

Articles: 92

Leave a Reply

Your email address will not be published. Required fields are marked *

en_USEN