You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android应用中JAR库访问自身内部资源的可行性咨询

Android依赖库读取自身JAR资源是否可行?

我有一个能从自身JAR资源中读取随机文件的JAR库,将其作为依赖添加至Android应用后,运行时该库无法读取自身资源(资源仅供库内部使用,无需Android应用调用)。此前查阅的相关回答均称无法实现,但这些回答距今至少已有10年,Android系统已发生诸多变化,请问当前是否可行?

以下是库访问资源的代码实现:

public static List<String> getAllResourcesFromFolder(String path) throws IOException {
        List<String> filenames = new ArrayList();
        URL dirURL = ResourcesUtils.class.getClassLoader().getResource(path);
        if (dirURL != null && dirURL.getProtocol().equals("jar")) {
            JarURLConnection jarConn = (JarURLConnection)dirURL.openConnection();
            JarFile jarFile = jarConn.getJarFile();
            Enumeration<JarEntry> entries = jarFile.entries();

            while(entries.hasMoreElements()) {
                String name = ((JarEntry)entries.nextElement()).getName();
                if (name.startsWith(path) && !name.equals(path + "/")) {
                    String entry = name.substring(path.length() + 1);
                    int checkSubdir = entry.indexOf("/");
                    if (checkSubdir < 0) {
                        filenames.add(entry);
                    }
                }
            }
        } else if (dirURL != null) {
            try {
                return Arrays.asList((new File(dirURL.toURI())).list());
            } catch (NullPointerException | URISyntaxException var9) {
                Exception e = var9;
                throw new IOException("Unable to convert URL to URI", e);
            }
        }

        return filenames;
    }

解答

当前是可以实现的,但现有代码在Android环境下存在适配问题,需针对性调整:

问题分析

  1. 类加载器差异:Android类加载器与标准JVM不同,ClassLoader.getResource(path)无法稳定定位库内资源——因为Android会将依赖库打包进APK(本质为ZIP文件),而非独立JAR。
  2. JarFile获取限制:Android 7.0+(API 24)及更高版本中,直接获取JarFile可能因APK优化(如拆分、DEX优化)出现权限或路径解析问题。

调整方案

方案1:基于类加载器的适配实现

针对库内部资源读取,可通过遍历类加载器返回的资源枚举来实现,避免直接依赖JarFile的路径解析:

public static List<String> getAllResourcesFromFolder(String path) throws IOException {
    List<String> filenames = new ArrayList<>();
    // 统一路径格式,去除开头斜杠
    if (path.startsWith("/")) {
        path = path.substring(1);
    }
    // 遍历类加载器下的所有匹配资源
    Enumeration<URL> resources = ResourcesUtils.class.getClassLoader().getResources(path);
    while (resources.hasMoreElements()) {
        URL url = resources.nextElement();
        if ("jar".equals(url.getProtocol())) {
            try (JarURLConnection connection = (JarURLConnection) url.openConnection()) {
                try (JarFile jarFile = connection.getJarFile()) {
                    Enumeration<JarEntry> entries = jarFile.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String entryName = entry.getName();
                        // 匹配目标路径下的直接子文件(排除目录和子目录内的文件)
                        if (entryName.startsWith(path) && !entry.isDirectory()) {
                            String relativePath = entryName.substring(path.length());
                            if (relativePath.startsWith("/")) {
                                relativePath = relativePath.substring(1);
                            }
                            if (!relativePath.contains("/")) {
                                filenames.add(relativePath);
                            }
                        }
                    }
                }
            }
        }
    }
    return filenames;
}

方案2:直接遍历APK文件(更稳定)

对于Android 8.0+(API 26),可通过Context获取APK路径,直接将APK作为ZIP文件遍历,不受类加载器限制:

public static List<String> getAllResourcesFromFolder(Context context, String path) throws IOException {
    List<String> filenames = new ArrayList<>();
    // 统一路径格式,去除开头斜杠
    if (path.startsWith("/")) {
        path = path.substring(1);
    }
    // 获取APK文件路径
    String apkPath = context.getPackageCodePath();
    try (ZipFile zipFile = new ZipFile(apkPath)) {
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            String entryName = entry.getName();
            // 匹配库中资源的完整路径(依赖库打包后资源位于BOOT-INF/classes/下)
            String targetPrefix = "BOOT-INF/classes/" + path;
            if (entryName.startsWith(targetPrefix) && !entry.isDirectory()) {
                String relativePath = entryName.substring(targetPrefix.length());
                if (relativePath.startsWith("/")) {
                    relativePath = relativePath.substring(1);
                }
                if (!relativePath.contains("/")) {
                    filenames.add(relativePath);
                }
            }
        }
    }
    return filenames;
}

此方法需传入Context,可通过库内部获取Application上下文的方式实现(如静态持有),稳定性更高。

额外注意事项

  • 资源打包路径:确保库中资源放在src/main/resources/[目标路径]下,打包后路径与代码中传入的path一致。
  • Gradle配置:Android库模块默认会将src/main/resources下的文件打包进AAR的classes.jar中,无需额外配置。

内容的提问来源于stack exchange,提问作者Skepller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:46:00