将so打包到jar中的方法

http://tommwq.tech/blog/2020/11/10/197

在Java 8中, System.loadLibrary 会调用 ClassLoader.loadLibrary0 加载native库,后者从静态成员 usr_paths 中搜索。基于这一点,我们可以将so打包在jar中。在运行时,把so复制到临时目录,然后修改 ClassLoader.usr_paths ,再调用 System.loadLibrary 完成so的加载。

Listing 1: 核心代码

public class SoWrapper {

    private static final Set loadedLibraries = new ConcurrentSkipListSet();
    private File temporaryDirectory = null;

    private static  T[] append(T[] array, T element) {
        List list = new ArrayList<>(Arrays.asList(array));
        list.add(element);
        return list.toArray(array);
    }

    public static String getDynamicLibraryPath() {
        return String.format("%s%d",
                OperatingSystem.getShortName(),
                OperatingSystem.is32bit() ? 32 : 64);
    }

    public static String getDynamicLibraryFileName(String dynamicLibraryFile) {
        return getDynamicLibraryPath() + "/" + dynamicLibraryFile;
    }

    public SoWrapper() throws IOException {
        temporaryDirectory = Files.createTempDirectory("tmp").toFile();
        temporaryDirectory.createNewFile();
        temporaryDirectory.deleteOnExit();
    }

    public void loadLibrary(String library) throws IOException, NoSuchFieldException, IllegalAccessException {
        if (loadedLibraries.contains(library)) {
            return;
        }

        String dynamicLibraryFilename = OperatingSystem.getDynamicLibraryPrefix() + library + OperatingSystem.getDynamicLibrarySuffix();
        File dynamicLibraryFile = new File(temporaryDirectory, dynamicLibraryFilename);

        try (InputStream inputStream = getClass().getClassLoader()
                .getResourceAsStream(getDynamicLibraryFileName(dynamicLibraryFilename))) {
            Files.copy(inputStream, dynamicLibraryFile.toPath());
        }

        Field field = ClassLoader.class.getDeclaredField("usr_paths");
        boolean originalAccessible = field.isAccessible();
        field.setAccessible(true);
        String[] usrPaths = (String[]) field.get(null);
        String[] newUsrPaths = append(usrPaths, temporaryDirectory.getAbsolutePath());
        field.set(null, newUsrPaths);
        field.setAccessible(originalAccessible);

        System.loadLibrary(library);

        loadedLibraries.add(library);
    }
}

32位JVM是无法加载64位native库的,通用还有跨平台的问题。因此需要在jar包中保存多个平台不同架构的native库,再加载时根据JVM平台进行选择。这里采用的方法是将库保存在OSOS{ARCH}(比如windows64、linux32)目录下。

Listing 2: 使用示例

SoWrapper soWrapper = new SoWrapper();

soWrapper.loadLibrary("foo");
soWrapper.loadLibrary("bar");

Listing 3: 辅助类

public class OperatingSystem {
    public static String getOperatingSystem() {
        return System.getProperty("os.name");
    }

    public static boolean isWindows() {
        return getOperatingSystem().toLowerCase().contains("windows");
    }

    public static boolean isLinux() {
        return getOperatingSystem().toLowerCase().contains("linux");
    }

    public static boolean isMac() {
        return getOperatingSystem().toLowerCase().contains("mac");
    }

    public static String getArch() {
        return System.getProperty("os.arch");
    }

    public static boolean is32bit() {
        return getArch().contains("86");
    }

    public static boolean is64bit() {
        return getArch().contains("64");
    }

    public static String getShortName() {
        if (isWindows()) {
            return "windows";
        } else if (isLinux()) {
            return "linux";
        } else if (isMac()) {
            return "mac";
        }
        return "";
    }

    public static String getDynamicLibrarySuffix() {
        switch (getShortName()) {
            case "windows":
                return ".dll";
            case "linux":
                return ".so";
            case "mac":
                return ".dylib";
            default:
                return "";
        }
    }

    public static String getDynamicLibraryPrefix() {
        switch (getShortName()) {
            case "linux":
                return "lib";
            default:
                return "";
        }
    }
}

你可能感兴趣的:(java)