Android Studio 3.x 이상 환경에서 시스템 프레임워크의 내부 API를 활용하는 방법을 정리합니다. 기존 자료들은 구버전 IDE 기준이라 실제 적용 시 문제가 발생하는 경우가 많습니다.
1단계: framework 아티팩트 빌드
시스템 앱 개발 중 @hide 어노테이션이 붙은 메서드나 커스텀 ROM에서 추가된 인터페이스에 접근하려면 framework 소스를 직접 빌드해야 합니다.Debug 빌드:
out/target/product/[제품명]/system/framework/framework.jar
User 빌드 (버전별 경로):
| Android 버전 | 산출물 경로 |
|---|---|
| N / O | out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar |
| P / Q | out/soong/.intermediates/frameworks/base/framework/android_common/combined/framework.jar |
| R 이상 | out/soong/.intermediates/frameworks/base/framework-minus-apex/android_common/combined/framework-minus-apex.jar |
2단계: 프로젝트 통합
2.1 아티팩트 배치
생성된 JAR 파일을 앱 모듈의 libs/ 디렉터리에 복사합니다.
2.2 Gradle 의존성 설정
모듈 수준 build.gradle에 저장소 선언:
repositories {
flatDir {
dirs 'libs'
}
}
의존성 블록에 compileOnly로 등록합니다. 이는 빌드 시에만 참조하고 APK에 포함하지 않기 위함입니다.
dependencies {
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
}
다른 JAR과 혼용 시 선택적으로 지정:
compileOnly files('libs/framework.jar')
implementation files('libs/another-lib.jar')
2.3 부트스트랩 클래스패치 재정의
Android SDK의 기본 프레임워크보다 우선순위를 부여하기 위해 컴파일 옵션을 조정합니다.
gradle.projectsEvaluated {
tasks.withType(JavaCompile).configureEach {
def originalBootClasspath = options.bootstrapClasspath?.files ?: []
def prioritizedJars = []
// 상대경로 또는 절대경로로 지정
prioritizedJars.add(file("libs/framework.jar"))
prioritizedJars.addAll(originalBootClasspath)
options.bootstrapClasspath = files(prioritizedJars)
}
}
2.4 IDE 모듈 설정 자동화
Android Studio의 내부 모듈 파일(.iml)을 빌드 전에 수정하여 SDK 참조 순서를 조정합니다.
preBuild {
doLast {
def moduleIml = file("../.idea/modules/${project.name}/${project.group}.${project.name}.iml")
if (!moduleIml.exists()) {
moduleIml = file("../.idea/modules/${project.name}.iml")
}
try {
def xmlParser = new XmlParser(false, true)
def rootNode = xmlParser.parse(moduleIml)
def moduleComponent = rootNode.component.find { it.'@name' == 'NewModuleRootManager' }
// 기존 JDK 노드 제거
def jdkEntry = moduleComponent.orderEntry.find { it.'@type' == 'jdk' }
if (jdkEntry) {
moduleComponent.remove(jdkEntry)
}
// 새로운 SDK 참조를 마지막에 추가 (우선순위 최하)
def apiLevel = android.compileSdkVersion.replace("android-", "")
def sdkName = "Android API ${apiLevel} Platform"
moduleComponent.appendNode('orderEntry', [
'type': 'jdk',
'jdkName': sdkName,
'jdkType': 'Android SDK'
])
groovy.xml.XmlUtil.serialize(rootNode, new FileWriter(moduleIml))
} catch (Exception ex) {
println "IML 수정 실패: ${ex.message}"
}
}
}
이 설정으로 <orderEntry type="jdk" ...> 엔트리가 의존성 목록의 마지막으로 이동하여 커스텀 framework.jar가 먼저 로드됩니다.
검증 방법
빌드 후 Build → Analyze APK로 확인하면 framework.jar가 classes.dex에 포함되지 않은 것을 확인할 수 있습니다. 런타임에는 기기의 시스템 이미지에 있는 프레임워크가 사용됩니다.