Android Studio에서 커스텀 framework.jar 활용하기

Android Studio 3.x 이상 환경에서 시스템 프레임워크의 내부 API를 활용하는 방법을 정리합니다. 기존 자료들은 구버전 IDE 기준이라 실제 적용 시 문제가 발생하는 경우가 많습니다.

1단계: framework 아티팩트 빌드

시스템 앱 개발 중 @hide 어노테이션이 붙은 메서드나 커스텀 ROM에서 추가된 인터페이스에 접근하려면 framework 소스를 직접 빌드해야 합니다.

Debug 빌드:

out/target/product/[제품명]/system/framework/framework.jar

User 빌드 (버전별 경로):

Android 버전산출물 경로
N / Oout/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
P / Qout/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에 포함되지 않은 것을 확인할 수 있습니다. 런타임에는 기기의 시스템 이미지에 있는 프레임워크가 사용됩니다.

태그: Android Studio Gradle AOSP framework.jar compileOnly

7월 28일 02:08에 게시됨