可可西

Android下动态加载libUnreal.so

Android下动态加载so库

具体有两种方式:

(1)调用 load(String filename) 方法

System.load("/data/data/com.example.mygame/files/libUnreal.so"); // 传递进去的是全路径

(2)调用 loadLibrary(String libname) 方法

System.loadLibrary("Unreal"); // 传递进去的是so文件的名称

 

具体的Android系统代码如下:

// https://android.googlesource.com/platform/libcore/+/15d8280/luni/src/main/java/java/lang/System.java
public final class System {
    // 。。。 。。。
    /**
     * Loads and links the dynamic library that is identified through the
     * specified path. This method is similar to {@link #loadLibrary(String)},
     * but it accepts a full path specification whereas {@code loadLibrary} just
     * accepts the name of the library to load.
     *
     * @param pathName
     *            the path of the file to be loaded.
     */
    public static void load(String pathName) {
        Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
    }
    /**
     * Loads and links the library with the specified name. The mapping of the
     * specified library name to the full path for loading the library is
     * implementation-dependent.
     *
     * @param libName
     *            the name of the library to load.
     * @throws UnsatisfiedLinkError
     *             if the library could not be loaded.
     */
    public static void loadLibrary(String libName) {
        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
    }
    
    // 。。。 。。。
}

// https://android.googlesource.com/platform/libcore/+/15d8280/luni/src/main/java/java/lang/Runtime.java
public class Runtime {
    // 。。。 。。。
    /**
     * Loads and links the dynamic library that is identified through the
     * specified path. This method is similar to {@link #loadLibrary(String)},
     * but it accepts a full path specification whereas {@code loadLibrary} just
     * accepts the name of the library to load.
     *
     * @param pathName
     *            the absolute (platform dependent) path to the library to load.
     * @throws UnsatisfiedLinkError
     *             if the library can not be loaded.
     */
    public void load(String pathName) {
        load(pathName, VMStack.getCallingClassLoader());
    }
    /*
     * Loads and links the given library without security checks.
     */
    void load(String pathName, ClassLoader loader) {
        if (pathName == null) {
            throw new NullPointerException("pathName == null");
        }
        String error = doLoad(pathName, loader);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
    }
    /**
     * Loads and links the library with the specified name. The mapping of the
     * specified library name to the full path for loading the library is
     * implementation-dependent.
     *
     * @param libName
     *            the name of the library to load.
     * @throws UnsatisfiedLinkError
     *             if the library can not be loaded.
     */
    public void loadLibrary(String libName) {
        loadLibrary(libName, VMStack.getCallingClassLoader());
    }
    /*
     * Searches for a library, then loads and links it without security checks.
     */
    void loadLibrary(String libraryName, ClassLoader loader) {
        if (loader != null) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                throw new UnsatisfiedLinkError("Couldn't load " + libraryName +
                                               " from loader " + loader +
                                               ": findLibrary returned null");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }
        String filename = System.mapLibraryName(libraryName); // 实现是在System.c里面,返回so的文件名。例如System.mapLibraryName('Unreal') 返回的是libUnreal.so
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : mLibPaths) {
            String candidate = directory + filename;
            candidates.add(candidate);
            if (IoUtils.canOpenReadOnly(candidate)) {
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }
        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }

    // 。。。 。。。
    private String doLoad(String name, ClassLoader loader) {
        // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
        // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
        // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
        // libraries with no dependencies just fine, but an app that has multiple libraries that
        // depend on each other needed to load them in most-dependent-first order.
        // We added API to Android's dynamic linker so we can update the library path used for
        // the currently-running process. We pull the desired path out of the ClassLoader here
        // and pass it to nativeLoad so that it can call the private dynamic linker API.
        // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
        // beginning because multiple apks can run in the same process and third party code can
        // use its own BaseDexClassLoader.
        // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
        // dlopen(3) calls made from a .so's JNI_OnLoad to work too.
        // So, find out what the native library search path is for the ClassLoader in question...
        String ldLibraryPath = null;
        if (loader != null && loader instanceof BaseDexClassLoader) {
            ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
        }
        // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
        // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
        // internal natives.
        synchronized (this) {
            return nativeLoad(name, loader, ldLibraryPath);
        }
    }
    
    // TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
    private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);  // 注:该方法为c++实现
    // 。。。 。。。
}



// https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
// https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
/**
 * Base class for common functionality between various dex-based
 * {@link ClassLoader} implementations.
 */
public class BaseDexClassLoader extends ClassLoader {
    /**
     * Hook for customizing how dex files loads are reported.
     *
     * This enables the framework to monitor the use of dex files. The
     * goal is to simplify the mechanism for optimizing foreign dex files and
     * enable further optimizations of secondary dex files.
     *
     * The reporting happens only when new instances of BaseDexClassLoader
     * are constructed and will be active only after this field is set with
     * {@link BaseDexClassLoader#setReporter}.
     */
    /* @NonNull */ private static volatile Reporter reporter = null;
    @UnsupportedAppUsage
    private final DexPathList pathList;
    
    // 。。。 。。。
    public BaseDexClassLoader(String dexPath,
        String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
        ClassLoader[] sharedLibraryLoadersAfter,
        boolean isTrusted) {
        super(parent);
        // Setup shared libraries before creating the path list. ART relies on the class loader
        // hierarchy being finalized before loading dex files.
        this.sharedLibraryLoaders = sharedLibraryLoaders == null
                ? null
                : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
       
        // 。。。 。。。
    }
    
    public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.sharedLibraryLoaders = null;
        this.sharedLibraryLoadersAfter = null;
        this.pathList = new DexPathList(this, librarySearchPath);
        this.pathList.initByteBufferDexPath(dexFiles);
        // Run background verification after having set 'pathList'.
        this.pathList.maybeRunBackgroundVerification(this);
    }
    
    @SystemApi(client = MODULE_LIBRARIES)
    public void addNativePath(@NonNull Collection<String> libPaths) {
        pathList.addNativePath(libPaths);
    }
    
    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }
    
    /**
     * Returns colon-separated set of directories where libraries should be
     * searched for first, before the standard set of directories.
     *
     * @return colon-separated set of search directories
     *
     * @hide
     */
    @UnsupportedAppUsage
    @SystemApi(client = MODULE_LIBRARIES)
    public @NonNull String getLdLibraryPath() {
        StringBuilder result = new StringBuilder();
        for (File directory : pathList.getNativeLibraryDirectories()) {
            if (result.length() > 0) {
                result.append(':');
            }
            result.append(directory);
        }
        return result.toString();
    }
    
    // 。。。 。。。
}


// https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/DexPathList.java
// https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public final class DexPathList {
    // 。。。 。。。
    /** List of native library path elements. */
    // Some applications rely on this field being an array or we'd use a final list here
    @UnsupportedAppUsage
    /* package visible for testing */ NativeLibraryElement[] nativeLibraryPathElements;
    /** List of application native library directories. */
    @UnsupportedAppUsage
    private final List<File> nativeLibraryDirectories;
    /** List of system native library directories. */
    @UnsupportedAppUsage
    private final List<File> systemNativeLibraryDirectories;
    
    // 。。。 。。。
    private List<File> getAllNativeLibraryDirectories() {
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
        return allNativeLibraryDirectories;
    }
    
    public DexPathList(ClassLoader definingContext, String librarySearchPath) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }
        this.definingContext = definingContext;
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true); // 系统的 "java.library.path" 路径   /system/lib/; /vendor/lib/; /product/lib/
        this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
    }
    
    DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }
        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }
        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }
            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);
        // Native libraries may exist in both the system and
        // application library paths, and we use this search order:
        //
        //   1. This class loader's library path for application libraries (librarySearchPath):
        //   1.1. Native library directories
        //   1.2. Path to libraries in apk-files
        //   2. The VM's library path from the system property for system libraries
        //      also known as java.library.path
        //
        // This order was reversed prior to Gingerbread; see http://b/2933456.
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);  // 系统的 "java.library.path" 路径   /system/lib/; /vendor/lib/; /product/lib/
        this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());
        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }
    
    /**
     * For BaseDexClassLoader.getLdLibraryPath.
     */
    public List<File> getNativeLibraryDirectories() {
        return nativeLibraryDirectories;
    }
    
    public String findLibrary(String libraryName) {
        String fileName = System.mapLibraryName(libraryName);
        for (NativeLibraryElement element : nativeLibraryPathElements) {
            String path = element.findNativeLibrary(fileName);
            if (path != null) {
                return path;
            }
        }
        return null;
    }
    
    /**
     * Adds a collection of library paths from which to load native libraries. Paths can be absolute
     * native library directories (i.e. /data/app/foo/lib/arm64) or apk references (i.e.
     * /data/app/foo/base.apk!/lib/arm64).
     *
     * Note: This method will attempt to dedupe elements.
     * Note: This method replaces the value of {@link #nativeLibraryPathElements}
     */
    @UnsupportedAppUsage
    public void addNativePath(Collection<String> libPaths) {   // 添加新的查找路径  例如:/data/app/com.example.mygame-S72Tv4IGEow5LtbwxPQHpQ==/lib/arm64
        if (libPaths.isEmpty()) {
            return;
        }
        List<File> libFiles = new ArrayList<>(libPaths.size());
        for (String path : libPaths) {
            libFiles.add(new File(path));
        }
        ArrayList<NativeLibraryElement> newPaths =
                new ArrayList<>(nativeLibraryPathElements.length + libPaths.size());
        newPaths.addAll(Arrays.asList(nativeLibraryPathElements));
        for (NativeLibraryElement element : makePathElements(libFiles)) {
            if (!newPaths.contains(element)) {
                newPaths.add(element);
            }
        }
        nativeLibraryPathElements = newPaths.toArray(new NativeLibraryElement[newPaths.size()]);
    }
    
    // 。。。 。。。
}

image

 

在 Java 中,JVM 加载的是 .class 文件,而在 Android 中,Dalvik或ART虚拟机加载的是 dex 文件。

这里的 dex 文件不仅仅指 .dex 后缀的文件,而是指携带 classed.dex 项的任何文件(例如:jar / zip / apk)。

在 Android 中,Java 代码的编译产物是 dex 格式字节码,所以 Android 系统提供了 BaseDexClassLoader 类加载器,用于从 dex 文件中加载类。

PathClassLoader & DexClassLoader 是 BaseDexClassLoader 的子类,但它们都没有重写方法,所以主要的逻辑还是在 BaseDexClassLoader。 

 

UE5动态加载libUnreal.so

GameApplication.java

import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;


public class GameApplication extends Application implements LifecycleObserver {
    
    // 。。。 。。。
    private static Context context;


    // 。。。 。。。
    @Override
    public void onCreate() {
        super.onCreate();
        GameApplication.context = getApplicationContext();
        
        // 。。。 。。。。
    }

    public static Context getAppContext() {
        return GameApplication.context;
    }

    // 。。。 。。。
}

 

GameActivity.java

import dalvik.system.BaseDexClassLoader;
import java.util.Collection;
import java.util.Set;

public class GameActivity extends NativeActivity implements SurfaceHolder.Callback2,
                                                            SensorEventListener,
                                                            Logger.ILoggerCallback,

                                                            ComponentCallbacks2
{
    // 。。。 。。。
    @Override
    public ClassLoader getClassLoader() {
        ClassLoader baseClassLoader = super.getClassLoader();

        try {
            BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) baseClassLoader;
            if (dexClassLoader.findLibrary("Unreal") == null) {
                Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");
                pathListField.setAccessible(true);

                Object pathListObj = pathListField.get(dexClassLoader);

                Method addNativePathMethod = pathListObj.getClass().getMethod("addNativePath", Collection.class);

                String filesDir = getApplicationContext().getFilesDir().getAbsolutePath();
                Log.verbose("Adding '" + filesDir + "' to GameActivity BaseDexClassLoader");
                Collection<String> paths = Arrays.asList(filesDir);
                addNativePathMethod.invoke(pathListObj, paths);
            }
        } catch (Exception e) {
            Log.warn("Failed to add native library path due to " + e);
        }

        return baseClassLoader;
    }
    
    // 。。。 。。。
    static
    {
        // We need to decide on enabling memory tracing before loading native code.
        // It's not trivial to get a full command line before GameActivity is constructed, so fallback to a sentinel file.
        try
        {
            Context context = GameApplication.getAppContext();
            Bundle bundle = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData;
            boolean bShipping =
                bundle.containsKey("com.epicgames.unreal.GameActivity.BuildConfiguration") &&
                bundle.getString("com.epicgames.unreal.GameActivity.BuildConfiguration").equals("Shipping");
            if (!bShipping)
            { 
                boolean bEnableFullMemoryTracing = new File(GameApplication.getAppContext().getFilesDir() + "/UEEnableMemoryTracing.txt").exists();
                boolean bEnableLightlMemoryTracing = new File(GameApplication.getAppContext().getFilesDir() + "/UEEnableMemoryTracingLight.txt").exists();
                
                if (bEnableFullMemoryTracing)
                {
                    Os.setenv("UEEnableMemoryTracing", "full", true);
                }
                else if (bEnableLightlMemoryTracing)
                {
                    Os.setenv("UEEnableMemoryTracing", "light", true);
                }
            }
        }
        catch (ErrnoException | NameNotFoundException e)
        {
            Log.debug(e.toString());
            Log.debug("Cannot set \"UEEnableMemoryTracing\" environment variable");
        }

        if (!isUnderTest())
        {
        System.load(GameApplication.getAppContext().getFilesDir().getAbsolutePath() + "/libUnreal.so");

        }
    }
}

注1:上面从/data/data/com.example.mygame/files/libUnreal.so外部路径加载so文件,需要保证包中不要打入libUnreal.so,否则在JNI层还是会优先加载包中的libUnreal.so导致崩溃

#00 0x000000000fcb099c /data/app/~~SQe_fczr1HZkR1FQUrh3yg==/com.tencent.mf.uam-o1lQw3oYq0yWYpV2OuG_qw==/lib/arm64/libUnreal.so [BuildId:7ae7636d6f836b996106f4bfa0c814ecd601fb76]
GetEnv
C:/Users/Lenovo/AppData/Local/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include/jni.h:1062:14
AndroidJavaEnv::GetJavaEnv(bool)
D://UnrealEngine/Engine/Source/Runtime/Core/Private/Android/AndroidJavaEnv.cpp:165:37
#01 0x0000000006f7e658 /data/app/~~SQe_fczr1HZkR1FQUrh3yg==/com.tencent.mf.uam-o1lQw3oYq0yWYpV2OuG_qw==/lib/arm64/libUnreal.so [BuildId:7ae7636d6f836b996106f4bfa0c814ecd601fb76]
GetJavaEnv
D://UnrealEngine/Engine/Source/Runtime/ApplicationCore/Public/Android/AndroidApplication.h:66:10
AndroidMain(android_app*)
D://UnrealEngine/Engine/Source/Runtime/Launch/Private/Android/LaunchAndroid.cpp:442:2
#02 0x0000000006f84b80 /data/app/~~SQe_fczr1HZkR1FQUrh3yg==/com.tencent.mf.uam-o1lQw3oYq0yWYpV2OuG_qw==/lib/arm64/libUnreal.so [BuildId:7ae7636d6f836b996106f4bfa0c814ecd601fb76]
android_main
D://UnrealEngine/Engine/Source/Runtime/Launch/Private/Android/LaunchAndroid.cpp:857:2
#03 0x0000000006fb98a0 /data/app/~~SQe_fczr1HZkR1FQUrh3yg==/com.example.mygame-o1lQw3oYq0yWYpV2OuG_qw==/lib/arm64/libUnreal.so [BuildId:7ae7636d6f836b996106f4bfa0c814ecd601fb76]
android_app_entry
C:/Users/Lenovo/AppData/Local/Android/Sdk/ndk/21.4.7075529/sources/android/native_app_glue/android_native_app_glue.c:233:5
#04 0x00000000000ebe70 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+264) [BuildId:a85ef5fb74e0282813cd1edb10854577]
#05 0x000000000008b7f0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) [BuildId:a85ef5fb74e0282813cd1edb10854577]

 

注2:libUnreal.so要放在App的沙盒目录且设置为可执行,否则可能由于权限问题加载失败。可使用代码为libUnreal.so设置可执行权限

// 方式一:
File file = new File("/data/data/com.example.mygame/files/libUnreal.so");
boolean success = file.setExecutable(true); // 为所有用户添加可执行权限
// 如果只为“所有者”添加权限,使用 file.setExecutable(true, true); // 第1个参数:是否可执行; 第2个参数(可选):是否只对所有者设置(true 为只对 owner,false 为所有用户)

// 方式二:
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.*;

Path path = Paths.get("/data/data/com.example.mygame/files/libUnreal.so");
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(path);
// 添加可执行权限
perms.add(PosixFilePermission.OWNER_EXECUTE);
perms.add(PosixFilePermission.GROUP_EXECUTE);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
// 可按需添加其他权限
Files.setPosixFilePermissions(path, perms);

// 方式三:
Path path = Paths.get("/data/data/com.example.mygame/files/libUnreal.so");
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-xr-x");
Files.setPosixFilePermissions(path, perms);

注3:打包脚本要去掉-archive参数    // 会在Binaries\Android目录中生成剔除libUnreal.so的apk包

"cmd.exe" /c ""D:/UnrealEngine/Engine/Build/BatchFiles/RunUAT.bat"  -ScriptsForProject="D:/MyGame/MyGame.uproject" Turnkey -command=VerifySdk -platform=Android -UpdateIfNeeded -EditorIO -EditorIOPort=52576  -project="D:/MyGame/MyGame.uproject" BuildCookRun -nop4 -utf8output -nocompileeditor -skipbuildeditor -cook  -project="D:/MyGame/MyGame.uproject"  -unrealexe="D:\UnrealEngine\Engine\Binaries\Win64\UnrealEditor-Win64-DebugGame-Cmd.exe" -platform=Android  -cookflavor=ASTC -stage -archive -package -build -pak -iostore -compressed -prereqs -archivedirectory="D:/MyGame/Achived" -clientconfig=Development" -nocompile -nocompileuat

 

参考

调试Android项目 -- 减少迭代时间(bDontBundleLibrariesInAPK设置为true)

细说So动态库的加载流程

浅谈ELF可执行文件的启动流程

posted on 2025-10-26 11:34  可可西  阅读(6)  评论(0)    收藏  举报

导航