dlopen failed與so的命名空間

之前的Android熱更新實(shí)踐里面使用替換默認(rèn)ClassLoader的方式實(shí)現(xiàn)了熱修復(fù),但偶然發(fā)現(xiàn)這種方式在加載某些系統(tǒng)so庫(kù)的時(shí)候出現(xiàn)了問(wèn)題。

背景是我們的某個(gè)功能依賴了我司開(kāi)發(fā)的XXX.so這個(gè)系統(tǒng)so,這個(gè)XXX.so依賴了libandroid_runtime.so,而libandroid_runtime.so又依賴了libandroidicu.so:

image.png

然后在應(yīng)用層調(diào)用System.loadLibrary去加載XXX.so的時(shí)候報(bào)了下面的異常:

03-26 02:27:07.385  3671  3695 E Demo: err : java.lang.UnsatisfiedLinkError: dlopen failed: library "libandroidicu.so" not found: needed by /system/lib64/libandroid_runtime.so in namespace classloader-namespace
03-26 02:27:07.385  3671  3695 E Demo:       at java.lang.Runtime.loadLibrary0(Runtime.java:1077)
03-26 02:27:07.385  3671  3695 E Demo:       at java.lang.Runtime.loadLibrary0(Runtime.java:998)
03-26 02:27:07.385  3671  3695 E Demo:       at java.lang.System.loadLibrary(System.java:1661)

so加載

從報(bào)錯(cuò)來(lái)看是libandroidicu.so在classloader-namespace這個(gè)命名空間里面不可訪問(wèn)。原生庫(kù)的命名空間是安卓7.0引入的東西,目的在于限制native層私有api的訪問(wèn)。

從文檔上并不能直接定位到我們的問(wèn)題,但是從觸發(fā)異常的條件"默認(rèn)的ClassLoader去加載這個(gè)so是沒(méi)有問(wèn)題的,用我們熱修復(fù)的ClassLoader去加載就會(huì)出現(xiàn)異常"來(lái)看,

問(wèn)題原因應(yīng)該就是出在我們熱修復(fù)的ClassLoader的對(duì)應(yīng)的命名空間沒(méi)有權(quán)限去加載libandroidicu.so了。

我們先用find命令看看libandroidicu.so在系統(tǒng)的什么目錄:

console:/ # find . -name libandroidicu.so 2> /dev/null
./apex/com.android.i18n/lib/libandroidicu.so
./apex/com.android.i18n/lib64/libandroidicu.so
./system/apex/com.android.i18n/lib/libandroidicu.so
./system/apex/com.android.i18n/lib64/libandroidicu.so

的確不在/system/lib64下面,應(yīng)用加載不到它是合理的。但我好奇的是默認(rèn)的ClassLoader又是怎么加載到的?

從錯(cuò)誤堆棧的Runtime.loadLibrary0一路往jni追蹤可以發(fā)現(xiàn)so實(shí)際并不是由ClassLoader去加載的,而是通過(guò)ClassLoader找到了對(duì)應(yīng)的NativeLoaderNamespace,然后用NativeLoaderNamespace::Load去加載的:

image.png

具體的代碼調(diào)用如下:


// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:libcore/ojluni/src/main/java/java/lang/Runtime.java
public class Runtime {
    ...
    public void loadLibrary(String libname) {
        loadLibrary0(Reflection.getCallerClass(), libname);
    }
    
    void loadLibrary0(Class<?> fromClass, String libname) {
        ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
        loadLibrary0(classLoader, fromClass, libname);
    }
    
    private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
        ...
        nativeLoad(filename, loader);
        ...
    }

    private static String nativeLoad(String filename, ClassLoader loader) {
        return nativeLoad(filename, loader, null);
    }

    private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);
    ...
}
// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:libcore/ojluni/src/main/native/Runtime.c
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jclass caller)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

static JNINativeMethod gMethods[] = {
  FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"),
  NATIVE_METHOD(Runtime, nativeGc, "()V"),
  NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
  NATIVE_METHOD(Runtime, nativeLoad,
                "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Class;)"
                    "Ljava/lang/String;"),
};

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jclass caller) {
    ...
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         caller,
                                         &error_msg);
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/runtime/jni/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jclass caller_class,
                                  std::string* error_msg) {
    ...
    void* handle = android::OpenNativeLibrary(
      env,
      runtime_->GetTargetSdkVersion(),
      path_str,
      class_loader,
      (caller_location.empty() ? nullptr : caller_location.c_str()),
      library_path.get(),
      &needs_native_bridge,
      &nativeloader_error_msg);
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/native_loader.cpp
LibraryNamespaces* g_namespaces = new LibraryNamespaces;

void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
                        jobject class_loader, const char* caller_location, jstring library_path,
                        bool* needs_native_bridge, char** error_msg) {
  ...
  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  NativeLoaderNamespace* ns;

  if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
    // This is the case where the classloader was not created by ApplicationLoaders
    // In this case we create an isolated not-shared namespace for it.
    Result<NativeLoaderNamespace*> isolated_ns =
        CreateClassLoaderNamespaceLocked(env,
                                         target_sdk_version,
                                         class_loader,
                                         /*is_shared=*/false,
                                         /*dex_path=*/nullptr,
                                         library_path,
                                         /*permitted_path=*/nullptr,
                                         /*uses_library_list=*/nullptr);
    if (!isolated_ns.ok()) {
      *error_msg = strdup(isolated_ns.error().message().c_str());
      return nullptr;
    } else {
      ns = *isolated_ns;
    }
  }

  return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
  ...
}

void* OpenNativeLibraryInNamespace(NativeLoaderNamespace* ns, const char* path,
                                   bool* needs_native_bridge, char** error_msg) {
  auto handle = ns->Load(path);
  ...
  return handle.ok() ? *handle : nullptr;
}

ld.config.txt可以看到不同的namespace有不同的search paths:

additional.namespaces = com_android_adbd,com_android_art,com_android_conscrypt,com_android_cronet,com_android_i18n,com_android_media,com_android_neuralnetworks,com_android_os_statsd,com_android_resolv,com_android_runtime,com_product_service1,product,rs,sphal,vndk,vndk_product
...
namespace.default.search.paths = /system/${LIB}
namespace.default.search.paths += /system_ext/${LIB}
...
namespace.com_android_i18n.search.paths = /apex/com.android.i18n/${LIB}

public libs

g_namespaces負(fù)責(zé)NativeLoaderNamespace的創(chuàng)建和緩存,NativeLoaderNamespace也是通過(guò)g_namespaces去Create出來(lái)的:

Result<NativeLoaderNamespace*> CreateClassLoaderNamespaceLocked(JNIEnv* env,
                                                                int32_t target_sdk_version,
                                                                jobject class_loader,
                                                                bool is_shared,
                                                                jstring dex_path,
                                                                jstring library_path,
                                                                jstring permitted_path,
                                                                jstring uses_library_list)
    REQUIRES(g_namespaces_mutex) {
  Result<NativeLoaderNamespace*> ns = g_namespaces->Create(env,
                                                           target_sdk_version,
                                                           class_loader,
                                                           is_shared,
                                                           dex_path,
                                                           library_path,
                                                           permitted_path,
                                                           uses_library_list);
  ...
  return ns;
}

我們看看g_namespaces的Create方法創(chuàng)建NativeLoaderNamespace干了些啥:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/library_namespaces.cpp
Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env, uint32_t target_sdk_version,
                                                         jobject class_loader, bool is_shared,
                                                         jstring dex_path_j,
                                                         jstring java_library_path,
                                                         jstring java_permitted_path,
                                                         jstring uses_library_list) {
  ...
  auto app_ns = NativeLoaderNamespace::Create(
      namespace_name, library_path, permitted_path, parent_ns, is_shared,
      target_sdk_version < 24 /* is_exempt_list_enabled */, also_used_as_anonymous);
  ...
  for (const auto&[apex_ns_name, public_libs] : apex_public_libraries()) {
    auto ns = NativeLoaderNamespace::GetExportedNamespace(apex_ns_name, is_bridged);
    // Even if APEX namespace is visible, it may not be available to bridged.
    if (ns.ok()) {
      linked = app_ns->Link(&ns.value(), public_libs);
      if (!linked.ok()) {
        return linked.error();
      }
    }
  }
  ...
  // 緩存并返回app_ns
}

可以看到它在創(chuàng)建出app_ns之后會(huì)遍歷apex_public_libraries去鏈接apex里面的公共庫(kù):

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/public_libraries.cpp
constexpr const char* kApexLibrariesConfigFile = "/linkerconfig/apex.libraries.config.txt";

const std::map<std::string, std::string>& apex_public_libraries() {
  static std::map<std::string, std::string> public_libraries = InitApexLibraries("public");
  return public_libraries;
}

static std::map<std::string, std::string> InitApexLibraries(const std::string& tag) {
  std::string file_content;
  if (!base::ReadFileToString(kApexLibrariesConfigFile, &file_content)) {
    // config is optional
    return {};
  }
  auto config = ParseApexLibrariesConfig(file_content, tag);
  if (!config.ok()) {
    LOG_ALWAYS_FATAL("%s: %s", kApexLibrariesConfigFile, config.error().message().c_str());
    return {};
  }
  return *config;
}

在實(shí)機(jī)的/linkerconfig/apex.libraries.config.txt下可以看到:

jni com_android_appsearch libicing.so
public com_android_art libnativehelper.so
jni com_android_btservices libbluetooth_jni.so
jni com_android_conscrypt libjavacrypto.so
public com_android_i18n libicui18n.so:libicuuc.so:libicu.so
public com_android_neuralnetworks libneuralnetworks.so
jni com_android_os_statsd libstats_jni.so
jni com_android_tethering libframework-connectivity-jni.so:libframework-connectivity-tiramisu-jni.so:libandroid_net_connectivity_com_android_net_module_util_jni.so:libservice-connectivity.so
jni com_android_uwb libuwb_uci_jni_rust.so

com_android_i18n的libicui18n.so、libicuuc.solibicu.so是公共的可以直接訪問(wèn)會(huì)被鏈接到app_ns允許訪問(wèn)。而報(bào)錯(cuò)的libandroidicu.so的確沒(méi)有在public里面,所以不會(huì)被鏈接,于是無(wú)法訪問(wèn)。

shared libs

實(shí)際上除了這個(gè)public libs配置之外,還有個(gè)shared libs的配置可以用于配置NativeLoaderNamespace對(duì)哪些NativeLoaderNamespace暴露哪些so。

詳細(xì)的規(guī)則可以參考鏈接器命名空間的文檔

具體的原理我們可以繼續(xù)往下追一層看看NativeLoaderNamespace::Create是怎么創(chuàng)建app_ns的:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/native_loader_namespace.cpp
Result<NativeLoaderNamespace> NativeLoaderNamespace::Create(
    const std::string& name, const std::string& search_paths, const std::string& permitted_paths,
    const NativeLoaderNamespace* parent, bool is_shared, bool is_exempt_list_enabled,
    bool also_used_as_anonymous) {
  ...
  // All namespaces for apps are isolated
  uint64_t type = ANDROID_NAMESPACE_TYPE_ISOLATED;

  ...
  if (is_shared) {
    type |= ANDROID_NAMESPACE_TYPE_SHARED;
  }
  ...
  android_namespace_t* raw =
    android_create_namespace(name.c_str(), nullptr, search_paths.c_str(), type,
                              permitted_paths.c_str(), effective_parent.ToRawAndroidNamespace());
  if (raw != nullptr) {
    return NativeLoaderNamespace(name, raw);
  }
  ...
}

可以看到is_shared為true的時(shí)候會(huì)給type添加一個(gè)ANDROID_NAMESPACE_TYPE_SHARED的flag,這個(gè)flag最終在create_namespace會(huì)判斷:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:bionic/linker/linker.cpp
static android_namespace_t* g_anonymous_namespace = &g_default_namespace;
...
android_namespace_t* create_namespace(const void* caller_addr,
                                      const char* name,
                                      const char* ld_library_path,
                                      const char* default_library_path,
                                      uint64_t type,
                                      const char* permitted_when_isolated_path,
                                      android_namespace_t* parent_namespace) {
  if (parent_namespace == nullptr) {
    // if parent_namespace is nullptr -> set it to the caller namespace
    soinfo* caller_soinfo = find_containing_library(caller_addr);

    parent_namespace = caller_soinfo != nullptr ?
                       caller_soinfo->get_primary_namespace() :
                       g_anonymous_namespace;
  }

  ProtectedDataGuard guard;
  std::vector<std::string> ld_library_paths;
  std::vector<std::string> default_library_paths;
  std::vector<std::string> permitted_paths;
  ...
  android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();
  ...
  if ((type & ANDROID_NAMESPACE_TYPE_SHARED) != 0) {
    // append parent namespace paths.
    std::copy(parent_namespace->get_ld_library_paths().begin(),
              parent_namespace->get_ld_library_paths().end(),
              back_inserter(ld_library_paths));

    std::copy(parent_namespace->get_default_library_paths().begin(),
              parent_namespace->get_default_library_paths().end(),
              back_inserter(default_library_paths));

    std::copy(parent_namespace->get_permitted_paths().begin(),
              parent_namespace->get_permitted_paths().end(),
              back_inserter(permitted_paths));

    // If shared - clone the parent namespace
    add_soinfos_to_namespace(parent_namespace->soinfo_list(), ns);
    // and copy parent namespace links
    for (auto& link : parent_namespace->linked_namespaces()) {
      ns->add_linked_namespace(link.linked_namespace(), link.shared_lib_sonames(),
                               link.allow_all_shared_libs());
    }
  } else {
    // If not shared - copy only the shared group
    add_soinfos_to_namespace(parent_namespace->get_shared_group(), ns);
  }
  
  ns->set_ld_library_paths(std::move(ld_library_paths));
  ns->set_default_library_paths(std::move(default_library_paths));
  ns->set_permitted_paths(std::move(permitted_paths));
  ...
}

可以看到添加了ANDROID_NAMESPACE_TYPE_SHARED這個(gè)flag的話會(huì)添加父namespace的所有so和鏈接父namespace連接過(guò)的namespace。而如果為false則只會(huì)添加父namespace的shared so。

系統(tǒng)默認(rèn)創(chuàng)建的Classloader對(duì)應(yīng)的namespace的parent_namespace為null,于是會(huì)把parent_namespace設(shè)置成默認(rèn)的"default"這個(gè)namespace。

而從/linkerconfig/ld.config.txt里面可以看到"default"這個(gè)namespace是鏈接了com_android_i18n這個(gè)namespace,可以從里面訪問(wèn)libandroidicu.so:

...
namespace.default.links = com_android_adbd,com_android_i18n,default,com_android_art,com_android_resolv,com_android_tethering,com_android_neural
namespace.default.link.com_android_adbd.shared_libs = libadb_pairing_auth.so
namespace.default.link.com_android_adbd.shared_libs += libadb_pairing_connection.so
namespace.default.link.com_android_adbd.shared_libs += libadb_pairing_server.so
namespace.default.link.com_android_i18n.shared_libs = libandroidicu.so
namespace.default.link.com_android_i18n.shared_libs += libicu.so
namespace.default.link.com_android_i18n.shared_libs += libicui18n.so
namespace.default.link.com_android_i18n.shared_libs += libicuuc.so
...

所以默認(rèn)的ClassLoader創(chuàng)建NativeLoaderNamespace的時(shí)候is_shared是true可以加載到libandroidicu.so

而我們自定義的ClassLoader創(chuàng)建NativeLoaderNamespace的時(shí)候is_shared是false沒(méi)有繼承"default"這個(gè)parent_namepsace的links配置無(wú)法加載libandroidicu.so

另外也可以看到默認(rèn)的Classloader對(duì)應(yīng)的namespace會(huì)連接com_android_i18n這個(gè)命名空間兩次

第一次在create\_namespace函數(shù)里由于ANDROID_NAMESPACE_TYPE_SHARED繼承了defalut這個(gè)parent_namespace的links配置能訪問(wèn)/linkerconfig/ld.config.txt里的namespace.default.link.com_android_i18n.shared_libs配置的shared so,

第二次則是在LibraryNamespaces::Create里面賭錢/linkerconfig/apex.libraries.config.txtpublic com_android_i18n配置鏈接com_android_i18n的public so。

鏈接器命名空間這個(gè)文檔里也有提到:

此屬性與 public.libraries.txt 文件在底層實(shí)現(xiàn)上是相同的。這兩種機(jī)制都通過(guò)使用庫(kù)名稱過(guò)濾器指定鏈接的方式來(lái)控制導(dǎo)入的共享庫(kù)。

is shared

從前面CreateClassLoaderNamespaceLocked的傳參來(lái)看我們的自定義Classloader創(chuàng)建namespace的時(shí)候is_shared的確為false:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/native_loader.cpp
LibraryNamespaces* g_namespaces = new LibraryNamespaces;

void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
                        jobject class_loader, const char* caller_location, jstring library_path,
                        bool* needs_native_bridge, char** error_msg) {
  ...
  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  NativeLoaderNamespace* ns;

  if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
    // This is the case where the classloader was not created by ApplicationLoaders
    // In this case we create an isolated not-shared namespace for it.
    Result<NativeLoaderNamespace*> isolated_ns =
        CreateClassLoaderNamespaceLocked(env,
                                         target_sdk_version,
                                         class_loader,
                                         /*is_shared=*/false,
                                         /*dex_path=*/nullptr,
                                         library_path,
                                         /*permitted_path=*/nullptr,
                                         /*uses_library_list=*/nullptr);
    if (!isolated_ns.ok()) {
      *error_msg = strdup(isolated_ns.error().message().c_str());
      return nullptr;
    } else {
      ns = *isolated_ns;
    }
  }

  return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
  ...
}

那默認(rèn)的ClassLoader的is_shared是怎么設(shè)置成true的呢?從LoadedApk代碼里面可以看到系統(tǒng)app最終就會(huì)調(diào)用ClassLoaderFactory.createClassLoader在里面創(chuàng)建Classloader的同時(shí)調(diào)用createClassloaderNamespace創(chuàng)建namespace,傳入的is_shared為isBundledApp即true:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:frameworks/base/core/java/android/app/LoadedApk.java
public ClassLoader getClassLoader() {
    synchronized (mLock) {
        if (mClassLoader == null) {
            createOrUpdateClassLoaderLocked(null /*addedPaths*/);
        }
        return mClassLoader;
    }
}

private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
    ...
    boolean isBundledApp = mApplicationInfo.isSystemApp()
            && !mApplicationInfo.isUpdatedSystemApp();

    ...
    mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
        zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
        libraryPermittedPath, mBaseClassLoader,
        mApplicationInfo.classLoaderName, sharedLibraries.first, nativeSharedLibraries,
        sharedLibraries.second);

    mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:frameworks/base/core/java/android/app/ApplicationLoaders.java
ClassLoader getClassLoaderWithSharedLibraries(
        String zip, int targetSdkVersion, boolean isBundled,
        String librarySearchPath, String libraryPermittedPath,
        ClassLoader parent, String classLoaderName,
        List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
        List<ClassLoader> sharedLibrariesLoadedAfterApp) {
    // For normal usage the cache key used is the same as the zip path.
    return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                          libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
                          nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
}


ClassLoader getClassLoaderWithSharedLibraries(
        String zip, int targetSdkVersion, boolean isBundled,
        String librarySearchPath, String libraryPermittedPath,
        ClassLoader parent, String classLoaderName,
        List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
        List<ClassLoader> sharedLibrariesLoadedAfterApp) {
    // For normal usage the cache key used is the same as the zip path.
    return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                          libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
                          nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
}

rivate ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                   String librarySearchPath, String libraryPermittedPath,
                                   ClassLoader parent, String cacheKey,
                                   String classLoaderName, List<ClassLoader> sharedLibraries,
                                   List<String> nativeSharedLibraries,
                                   List<ClassLoader> sharedLibrariesLoadedAfterApp) {
    ...
    ClassLoader classloader = ClassLoaderFactory.createClassLoader(
                        zip,  librarySearchPath, libraryPermittedPath, parent,
                        targetSdkVersion, isBundled, classLoaderName, sharedLibraries,
                        nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
    ...
    return loader;
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java
public static ClassLoader createClassLoader(String dexPath,
    String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
    int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,
    List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
    List<ClassLoader> sharedLibrariesAfter) {

final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
        classLoaderName, sharedLibraries, sharedLibrariesAfter);
  ...
  String errorMessage = createClassloaderNamespace(classLoader,
                                                   targetSdkVersion,
                                                   librarySearchPath,
                                                   libraryPermittedPath,
                                                   isNamespaceShared,
                                                   dexPath,
                                                   sonameList);
  ...
  return classLoader;
}

解決方案

方法1

由于ClassLoaderFactory.createClassloaderNamespace是private的不能在外部調(diào)用,所以解決自定義classloader找不到libandroidicu.so的方法就是不要自己直接new ClassLoader,而是調(diào)用ClassLoaderFactory.createClassLoader去創(chuàng)建,傳入isNamespaceShared為true。

方法2

由于系統(tǒng)默認(rèn)的classloader對(duì)應(yīng)的namespace已經(jīng)加載了libandroid_runtime.so,如果將我們自定義的classloader的父classloader設(shè)置成系統(tǒng)默認(rèn)的classloder,則自定義classloader對(duì)應(yīng)的namespace的parent_namespace也會(huì)指向默認(rèn)classloader的namespace。

然后這個(gè)namespace已經(jīng)加載了libandroid_runtime.so,于是在后面的add_soinfos_to_namespace(parent_namespace->get_shared_group(), ns);里面就能直接使用已經(jīng)加載好的shared so libandroid_runtime.so:

image.png

使用setprop debug.ld.all dlopen,dlerror命令打開(kāi)全部linker打印也可以看到libandroid_runtime.so Already loaded的日志:

03-29 04:36:10.024 6046 6069 D linker : find_library_internal(ns=classloader-namespace, task=libandroid_runtime.so): Already loaded (by sona me): /system/lib64/libandroid_runtime.so`

但是當(dāng)父classloader設(shè)置成系統(tǒng)默認(rèn)的classloader之后由于雙親委托的機(jī)制,會(huì)先從父classloader去加載class,達(dá)不到熱修復(fù)的需求。

于是我們需要修改自定義classloader的loadClass打破雙親委托機(jī)制,先自己去加載class,加載不到再讓父classloder去加載:

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    //先從自己查找,找不到再?gòu)母竎lassloader查找,實(shí)現(xiàn)熱修復(fù)
    Class<?> c = null;
    try {
        c = findClass(name);
    } catch (ClassNotFoundException e) {
        // ignore
    }
    if (c == null) {
        c = getParent().loadClass(name);
    }
    return c;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,514評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,373評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,975評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,743評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,199評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,414評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,951評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,780評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,218評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,649評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,889評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,673評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容