[Android][踩坑]Android Studio导入core-libart.jar

前言

如果这篇文章可以解决你的问题,那么请无视本文章

本文章适用于使用JDK 1.8以上的构建环境

验证环境

Windows 11

Android Studio 2024.2.2

JAVA 17

AGP 8.8.0

Gradle 8.10.2

结论先行

Build.gradle添加如下编译参数(updateCompilerArgs)即可:

groovy 复制代码
plugins {
    id("com.android.application")
}

android {
    ...
    gradle.projectsEvaluated {
        tasks.named("compileDebugJavaWithJavac") {
            //此处需要显式声明javac的task依赖resource编译完成
            dependsOn("processDebugResources")
            //如果只依赖framework.jar,只需要添加这一行
            classpath = updateClasspath(classpath)
            //如果只依赖core-libart.jar,只需要添加这一行
            updateCompilerArgs(options.compilerArgs)
        }

        tasks.named("compileReleaseJavaWithJavac") {
            //同上,release与debug分别声明对应的task依赖
            dependsOn("processReleaseResources")
            //如果只依赖framework.jar,只需要添加这一行
            classpath = updateClasspath(classpath)
            //如果只依赖core-libart.jar,只需要添加这一行
            updateCompilerArgs(options.compilerArgs)
        }
    }
}

// 新增一个函数,以便多次调用
def updateCompilerArgs(List<String> compilerArgs) {
    compilerArgs.add("--patch-module")
    //路径需要根据实际情况调整
    compilerArgs.add("java.base=${project.rootDir}/libs/core-libart.jar")
}

// 新增一个函数,以便多次调用
def updateClasspath(FileCollection classpathSet) {
    List<File> newFileList =  new ArrayList<>()
    //路径需要根据实际情况调整
    newFileList.addAll(Arrays.asList(
            "${project.rootDir}/libs/core-libart.jar",
            "${project.rootDir}/libs/framework.jar",
    ))
    File sdkFile = null
    for (File f : classpathSet.getFiles()) {
        //将android.jar放到-classpath参数末尾即可
        if ("android.jar" == f.getName()) {
            sdkFile = f
        } else {
            newFileList.add(f);
        }
    }
    if (sdkFile != null) {
        newFileList.add(sdkFile)
    }

    return files(newFileList.toArray())
}

dependencies {
    ...
    //compileOnly还是需要添加
    compileOnly files('../libs/framework.jar')
    compileOnly files('../libs/core-libart.jar')
}

完整经历

新建一个Android Project,移除androidx等不必要的依赖,确保是个最精简的Android Project(因为后面要排查编译参数,依赖过多会导致命令行特别长,不利于分析)

然后我使用下面这几行代码来代表几种情况:

  1. 引用一个Android SDK中的android.jar里不存在,但core-libart.jar中包含的类,例如dalvik.system.VMRuntime
  2. 引用一个Android SDK中的android.jar存在的类,但是其中某个不存在或者hide注解修饰的方法,例如DexFile.getDexFileOptimizationInfo()
java 复制代码
package com.gwm.demo4androidwjava;

import android.app.Activity;
import android.os.Build;
import android.os.Bundle;

import dalvik.system.DexFile;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            DexFile dexFile = new DexFile("test");
            // 访问一个android.jar中不存在的类
            String instructionSet = dalvik.system.VMRuntime.getInstructionSet(Build.CPU_ABI);
            // 访问一个android.jar中存在的类,但是其方法不存在或者为hide
            DexFile.OptimizationInfo optInfo = dexFile.getDexFileOptimizationInfo(
                            "test", instructionSet);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

编译,毫无疑问两处都会报错:

bash 复制代码
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:17: error: cannot find symbol
            String instructionSet = dalvik.system.VMRuntime.getInstructionSet(Build.CPU_ABI);
                                                 ^
  symbol:   class VMRuntime
  location: package dalvik.system
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:18: error: cannot find symbol
            DexFile.OptimizationInfo optInfo = dexFile.getDexFileOptimizationInfo(
                                                      ^
  symbol:   method getDexFileOptimizationInfo(String,String)
  location: variable dexFile of type DexFile
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:
uses or overrides a deprecated API.
Recompile with -Xlint:deprecation for details.

从AOSP中编译一个core-libart.jar,并拷贝到该Android Project下app模块内的libs目录中:

然后按照这篇文章中添加对framework.jar的依赖的方式,修改build.gradle:

groovy 复制代码
plugins {
    alias(libs.plugins.android.application)
}

android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
    }


    gradle.projectsEvaluated {
        tasks.getByName("compileDebugJavaWithJavac") {
            //经过验证发现,修改classpath会导致编译task列表顺序紊乱,从而报错提示找不到R.class相关内容;
            //因此此处需要显式声明javac的task依赖resource编译完成
            dependsOn("processDebugResources")
            classpath = reorderClasspath(classpath.getFiles())
        }

        tasks.getByName("compileReleaseJavaWithJavac") {
            //同上,release与debug分别声明对应的task依赖
            dependsOn("processReleaseResources")
            classpath = reorderClasspath(classpath.getFiles())
        }
    }
}

// 新增一个函数,以便多次调用
def reorderClasspath(Set<File> classpathSet) {
    List<File> newFileList =  new ArrayList<>()
    newFileList.addAll(Arrays.asList(
            "./libs/core-libart.jar",
    ))
    File sdkFile = null
    for (File f : classpathSet) {
        //将android.jar放到-classpath参数末尾即可
        if ("android.jar" == f.getName()) {
            sdkFile = f
        } else {
            newFileList.add(f);
        }
    }
    if (sdkFile != null) {
        newFileList.add(sdkFile)
    }

    return files(
            newFileList.toArray()
    )
}

dependencies {
	...
	compileOnly files('libs/core-libart.jar')
}

编译,依然报错,报错信息一模一样:

bash 复制代码
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:17: error: cannot find symbol
            String instructionSet = dalvik.system.VMRuntime.getInstructionSet(Build.CPU_ABI);
                                                 ^
  symbol:   class VMRuntime
  location: package dalvik.system
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:18: error: cannot find symbol
            DexFile.OptimizationInfo optInfo = dexFile.getDexFileOptimizationInfo(
                                                      ^
  symbol:   method getDexFileOptimizationInfo(String,String)
  location: variable dexFile of type DexFile
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:
uses or overrides a deprecated API.
Recompile with -Xlint:deprecation for details.

由于失败阶段依然为compileDebugJavaWithJavac,而新版本Android Studio(或许是新版本Gradle导致?)无法使用--debug参数重新run一遍,于是使用命令行调试这个任务:

bash 复制代码
.\gradlew.bat app:compileDebugJavaWithJavac --debug

查找关键字Compiler arguments,得到这段数据(格式化后展现):

bash 复制代码
Compiler arguments: \
    -source 11 \
    -target 11 \
    -d \
    C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes \
    -encoding UTF-8 \
    -g \
    -sourcepath "" \
    -proc:none \
    -s C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\generated\ap_generated_sources\debug\out \
    -XDuseUnsharedTable=true \
    -classpath \
        C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\libs\core-libart.jar;D:\dev_tools\android-sdk\build-tools\35.0.0\core-lambda-stubs.jar;\
      	C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\processDebugResources\R.jar;\
      	D:\dev_tools\android-sdk\platforms\android-36\android.jar \
    -XDstringConcat=inline \
    --system C:\Users\HP\.gradle\caches\8.10.2\transforms\57f8b045b26807d762a56357fc05473c\transformed\output\jdkImage \
    C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java

可以看到,classpath中core-libart.ja已经在android.jar前面了,按照framework.jar的导入经验,这样应该是可以生效的(事实也确实如此,如果此处是用framework.jar的API,导入的是framework.jar,到这一步应该已经编译通过了)

于是将上述参数转换成javac命令行,并添加-verbose显示详细编译信息:

bash 复制代码
javac -source 11 -target 11 -d C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes -encoding UTF-8 -g -sourcepath "" -proc:none -s C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\generated\ap_generated_sources\debug\out -XDuseUnsharedTable=true -classpath "C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\libs\core-libart.jar;D:\dev_tools\android-sdk\build-tools\35.0.0\core-lambda-stubs.jar;C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\processDebugResources\R.jar;D:\dev_tools\android-sdk\platforms\android-36\android.jar" -XDstringConcat=inline --system C:\Users\HP\.gradle\caches\8.10.2\transforms\57f8b045b26807d762a56357fc05473c\transformed\output\jdkImage C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java -verbose

三个修改:

  1. -classpath参数后面需要添加引号;
  2. 命令行前加javac
  3. 命令行末尾加-verbose

由于这是个最小化的Android Project,整个编译命令输出并不多:

bash 复制代码
[parsing started SimpleFileObject[C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java]]
[parsing completed 15ms]
[loading /modules/java.base/module-info.class]
[search path for source files: ]
[search path for class files: D:\dev_tools\jdk-21.0.4\lib\modules,C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\libs\core-libart.jar,D:\dev_tools\android-sdk\build-tools\35.0.0\core-lambda-stubs.jar,C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\processDebugResources\R.jar,D:\dev_tools\android-sdk\platforms\android-36\android.jar]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/app/Activity.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/os/Build.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/os/Bundle.class)]
# DexFile.class并不是从android.jar中加载的
[loading /modules/java.base/dalvik/system/DexFile.class]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/ComponentCallbacks2.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/ComponentCallbacks.class)]
[loading /modules/java.base/java/lang/Object.class]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/KeyEvent.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/KeyEvent$Callback.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/os/Parcelable.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/InputEvent.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/LayoutInflater.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/LayoutInflater$Factory2.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/LayoutInflater$Factory.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/View.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/View$OnCreateContextMenuListener.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/accessibility/AccessibilityEventSource.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/graphics/drawable/Drawable.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/graphics/drawable/Drawable$Callback.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/Window.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/Window$Callback.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/ContextThemeWrapper.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/ContextWrapper.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/Context.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/ViewDebug.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/ViewDebug$ExportedProperty.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/ViewDebug$IntToString.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/view/ViewDebug$FlagToString.class)]
[loading /modules/java.base/java/lang/Override.class]
[loading /modules/java.base/java/lang/annotation/Annotation.class]
[loading /modules/java.base/java/lang/annotation/Retention.class]
[loading /modules/java.base/java/lang/annotation/RetentionPolicy.class]
[loading /modules/java.base/java/lang/annotation/Target.class]
[loading /modules/java.base/java/lang/annotation/ElementType.class]
[checking com.gwm.demo4androidwjava.MainActivity]
[loading /modules/java.base/java/io/Serializable.class]
[loading /modules/java.base/java/util/List.class]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/IntentSender.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/IntentSender$SendIntentException.class)]
[loading /modules/java.base/java/lang/Error.class]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/util/AndroidException.class)]
[loading /modules/java.base/java/lang/Exception.class]
[loading /modules/java.base/java/lang/Throwable.class]
[loading /modules/java.base/java/lang/RuntimeException.class]
[loading /modules/java.base/java/io/IOException.class]
[loading /modules/java.base/java/io/FileNotFoundException.class]
[loading /modules/java.base/java/lang/Class.class]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/pm/PackageManager.class)]
[loading D:\dev_tools\android-sdk\platforms\android-36\android.jar(/android/content/pm/PackageManager$NameNotFoundException.class)]
[loading /modules/java.base/java/lang/AutoCloseable.class]
[loading C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\processDebugResources\R.jar(/com/gwm/demo4androidwjava/R.class)]
[loading C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\processDebugResources\R.jar(/com/gwm/demo4androidwjava/R$layout.class)]
[loading /modules/java.base/java/lang/String.class]
[loading /modules/java.base/java/io/File.class]
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:17: error: cannot find symbol
            String instructionSet = dalvik.system.VMRuntime.getInstructionSet(Build.CPU_ABI);
                                                 ^
  symbol:   class VMRuntime
  location: package dalvik.system
# DexFile$OptimizationInfo.class也并不是从android.jar中加载的
[loading /modules/java.base/dalvik/system/DexFile$OptimizationInfo.class]
C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java:18: error: cannot find symbol
            DexFile.OptimizationInfo optInfo = dexFile.getDexFileOptimizationInfo(
                                                      ^
  symbol:   method getDexFileOptimizationInfo(String,String)
  location: variable dexFile of type DexFile
[total 419ms]
Note: C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
2 errors

可以看出,在编译DexFileDexFile.OptimizationInfo时,javac并没有去android.jar中加载class,而是加载的/modules/java.base/

因此,看起来,编译的时候,javac除了classpath的定义外,还有一部分依赖是在此之外的规则在作用;

排查下整个编译的命令行参数,可以确定是``--system```这一参数所致:

bash 复制代码
--system C:\Users\HP\.gradle\caches\8.10.2\transforms\57f8b045b26807d762a56357fc05473c\transformed\output\jdkImage

该目录下有一个release文件,其内容正是定义了名为java.base的module:

bash 复制代码
JAVA_VERSION="21"
MODULES="java.base"

根据官方文档来看,这是一个JAVA9以后引入的一个模块化编译的机制,--system用于加载一个全局的编译环境,而这里面就包括JAVA基础库的支持;正因如此,当我们想指定使用自己的core-libart.jar中的API时,会由于优先级问题,无法找到;

既然现在的难题是JAVA9以后的模块化编译机制引入的,那么解决也需要依赖这个机制;

原理很简单,我们通过--patch-module参数来给这个java.base模块"打补丁",把我们core-libart.jar打到java.base中去,即可解决问题;

快速验证,在之前的javac命令行末尾添加--patch-module参数:

bash 复制代码
javac -source 11 -target 11 -d C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes -encoding UTF-8 -g -sourcepath "" -proc:none -s C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\generated\ap_generated_sources\debug\out -XDuseUnsharedTable=true -classpath "C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\libs\core-libart.jar;D:\dev_tools\android-sdk\build-tools\35.0.0\core-lambda-stubs.jar;C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\build\intermediates\compile_and_runtime_not_namespaced_r_class_jar\debug\processDebugResources\R.jar;D:\dev_tools\android-sdk\platforms\android-36\android.jar" -XDstringConcat=inline --system C:\Users\HP\.gradle\caches\8.10.2\transforms\57f8b045b26807d762a56357fc05473c\transformed\output\jdkImage C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\src\main\java\com\gwm\demo4androidwjava\MainActivity.java -verbose --patch-module java.base=C:\Users\HP\AndroidStudioProjects\Demo4AndroidWJAVA\app\libs\core-libart.jar

编译通过。

接下来考虑如何把这个参数通过build.gradle传递出去即可:

groovy 复制代码
    gradle.projectsEvaluated {
        tasks.getByName("compileDebugJavaWithJavac") {
					 ...
            options.compilerArgs.add("--patch-module")
            options.compilerArgs.add("java.base=${project.rootDir}/app/libs/core-libart.jar")
        }

        tasks.getByName("compileReleaseJavaWithJavac") {
					 ...
            classpath = reorderClasspath(classpath.getFiles())
            options.compilerArgs.add("--patch-module")
            options.compilerArgs.add("java.base=${project.rootDir}/app/libs/core-libart.jar")
        }

编译同样通过,使用.\gradlew.bat app:compileDebugJavaWithJavac --debug可以看到参数也是添加成功了的;

相关推荐
q***13342 小时前
使用 java -jar 命令启动 Spring Boot 应用时,指定特定的配置文件的几种实现方式
java·spring boot·jar
q***R3082 小时前
Kotlin注解处理
android·开发语言·kotlin
Digitally2 小时前
如何将文件从三星平板传输到电脑
android
CHINAHEAO3 小时前
Bagisto单独将后台设置成中文
android
E***U9453 小时前
React Native开发
android·react native·react.js
4***99744 小时前
Kotlin序列处理
android·开发语言·kotlin
t***D2644 小时前
Kotlin在服务端开发中的生态建设
android·开发语言·kotlin
玲珑Felone4 小时前
flutter 状态管理--InheritedWidget、Provider原理解析
android·flutter·ios
BoomHe4 小时前
车载应用配置系统签名
android·android studio