Android Studio에서 custom framework.jar로 Hidden api를 사용하는 방법을 소개합니다.
일반적으로 앱은 안드로이드는 SDK 라이브러리로 컴파일됩니다. 구글은 꾸준히 지원할 API를 SDK에 포함시키기 때문에, SDK로 개발하는 것이 안정적입니다.
만약 SDK에 포함되지 않은 hidden API를 사용하여 앱을 개발하고 싶다면 다음과 같은 방법이 있습니다.
- Reflection을 사용하여 hidden api를 호출
- Custom framework.jar library를 Android studio에서 import하여 개발
리플렉션을 호출하기에는 가독성이 떨어지고 귀찮기 때문에 custom library를 import하여 안드로이드 스튜디오에서 개발하는 것이 편합니다. 물론 Hidden api는 안드로이드 OS 버전마다 다를 수 있고, 갑자기 사라질 수 있기 때문에 디바이스마다 다르게 동작할 수 있습니다.
Custom framework.jar
안드로이드의 모든 API가 포함된 Custom 라이브러리를 만드는 방법은 안드로이드 소스를 빌드하는 것 뿐입니다.
AOSP 소스를 다운받아 전체빌드를 하면 framework_intermediates
이름의 폴더에 모든 api가 포함된 라이브러리가 생성됩니다.
저는 아래처럼 out 폴더에서 이름으로 검색하여 찾았고, classes.jar
가 그 라이브러리 파일입니다.
aosp10/out$ find -name "*framework_intermediates*"
./target/common/obj/JAVA_LIBRARIES/framework_intermediates
./target/product/generic_x86_64/obj_x86/SHARED_LIBRARIES/libdrmframework_intermediates
./target/product/generic_x86_64/obj/JAVA_LIBRARIES/framework_intermediates
./target/product/generic_x86_64/obj/SHARED_LIBRARIES/libdrmframework_intermediates
aosp10/out$ ls ./target/common/obj/JAVA_LIBRARIES/framework_intermediates
classes-header.jar classes.jar javalib.jar link_type
라이브러리가 맞는지 확인하려면 jar tf [jar file]
명령어로 파일 안에 hidden class가 포함되어 있는지로 확인할 수 있습니다.
framework_intermediates$ jar tf classes.jar
android/os/storage/StorageManager$StorageEventListenerDelegate.class
android/os/storage/StorageManager$ObbListenerDelegate.class
android/os/storage/StorageManager$ObbListenerDelegate$1.class
....
classes.jar
파일의 이름을 보기 좋게 framework.jar
로 변경하고 안드로이드 스튜디오 프로젝트의 app/libs/
경로에 옴깁니다.
만약 AOSP를 빌드할 환경이 안되신다면 GitHub - CustomFramework에서
"../app/libs/framework.jar"를 다운받아서 사용하세요. Android 10(API 29)에서 빌드한 framework.jar입니다.
프로젝트에서 라이브러리 의존성 추가
라이브러리를 의존성에 추가하는 것은 앱의 build.gradle에 다음과 같이 추가하면 됩니다.
dependencies {
compileOnly files('libs/framework.jar')
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
}
compileOnly
옵션은 빌드할 때만 라이브러리를 사용하고 Apk에 라이브러리 코드를 포함시키지 않겠다는 의미입니다.
apk가 디바이스에 설치되면 디바이스에 존재하는 framework.jar
를 사용하기 때문에 굳이 이 코드를 포함시킬 필요가 없습니다.
우리가 만든 custom library 파일의 크기는 26MB 정도 되거든요. 포함시키면 빌드시간도 오래 걸리고 apk 사이즈도 커집니다.
SDK보다 Custom 라이브러리에서 API를 먼저 찾게 만들기
안드로이드 스튜디오의 프로젝트는 컴파일할 때 사용할 SDK 버전을 미리 설정해 놓습니다. 우리가 이 프로젝트에 Custom framework.jar를 추가해도 SDK에서 클래스를 먼저 찾기 때문에, hidden api를 찾지 못하는 일이 발생합니다.
그래서 앱을 컴파일할 때 SDK보다 Custom 라이브러리의 우선순위를 더 높게 만들어야 합니다.
안드로이드 프로젝트의 app.iml
파일은 라이브러리의 우선순위를 관리해주는 파일입니다.
이 파일을 잠시 보면 안드로이드 SDK가 커스텀 라이브러리보다 더 우선순위가 높습니다. SDK의 위치를 커스텀 라이브러리 아래로 내려주면 커스텀 라이브러리에서 hidden api를 찾을 수 있습니다.
<orderEntry type="jdk" jdkName="Android API 29 Platform" jdkType="Android SDK"/>
...
<orderEntry type="library" name="Gradle: __local_aars__:/home/js/AndroidStudioProjects/CustomFramework/app/libs/framework.jar:unspecified@jar" level="project"/>
app.iml
파일은 gradle이 변경될 때마다 새로 만들어지기 때문에 매번 변경해야 합니다.
그래서 gradle에 SDK의 위치를 맨 밑으로 옴겨주는 스크립트를 만들면 매우 편리합니다.
주의할 점은 Android Studio 버전에 따라서 .iml
의 파일 이름과 경로가 다릅니다.
- Android Studio 4.0 미만 : Project/app/app.iml (여기서 'app'은 프로젝트 이름)
- Android Studio 4.0 : Project/.idea/modules/app/app.iml (여기서 'app'은 프로젝트 이름)
- Android Studio 4.1 이상 : Project/.idea/modules/app/CustomFramework.app.iml (여기서 'app'은 프로젝트 이름, CustomFramework는 Root Project 이름)
Android Studio 4.1 이상의 스크립트
앱의 gradle에서 dependencies 하단에 다음과 같은 스크립트를 추가합니다.
안드로이드 스튜디오에서 만약 Node
등의 클래스를 못찾는다고 에러메시지가 발생해도 무시하시면 됩니다.
빌드될 때 스크립트는 잘 동작합니다.
동작이 잘안되면 println으로 iml 파일의 패스를 출력해보고 실제 파일이 존재하는지 확인하시면 됩니다.
그리고 Linux, Windows의 실행 환경에 따라서 File separator(/
또는 \
)가 다를 수 있기 때문에 File.separator
를 사용하였습니다.
preBuild {
doLast {
def imlFile = file( ".." + File.separator + ".idea" + File.separator
+ "modules" + File.separator + "app" + File.separator
+ rootProject.name + ".app.iml")
println 'Changing the library priority order: ' + imlFile.getPath()
try {
def parsedXml = (new XmlParser()).parse(imlFile)
def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' }
parsedXml.component[1].remove(jdkNode)
def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform"
println 'Android sdk version: ' + sdkString
new Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK'])
groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile))
} catch (FileNotFoundException e) {
// nop, iml not found
println "no iml found"
}
}
}
Android Studio 4.0 이하
버전에 따라 iml 파일의 위치가 다릅니다. 위의 스크립트에서 iml 파일의 path만 자신의 환경에 맞도록 변경해주시면 됩니다.
- Android Studio 4.0 미만 : Project/app/app.iml (여기서 'app'은 프로젝트 이름)
- Android Studio 4.0 : Project/.idea/modules/app/app.iml (여기서 'app'은 프로젝트 이름)
classpath에 대한 gradle 설정
그리고 앱이 컴파일 될 때 classpath에 우리가 추가한 라이브러리를 등록해줘야 합니다.
프로젝트의 gradle에 allprojects 아래에 다음과 같이 라이브러리를 클래스 패스에 추가해 줍니다.
allprojects {
....
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs.add('-Xbootclasspath/p:app/libs/framework.jar')
}
}
}
Hidden api 사용
지금까지 프로젝트 세팅을 모두 마쳤습니다. 이제 hidden api를 사용해보세요.
다음은 AOSP의 UserHandle 코드입니다. static 멤버 변수 CURRENT
는 hidden api입니다.
public final class UserHandle
implements Parcelable
{
@SystemApi
public static final UserHandle CURRENT = new UserHandle(-2);
...
}
다음과 같은 코드를 앱에서 빌드하여 실행해보면
Log.d("TEST", "UserHandle.CURRENT : " + UserHandle.CURRENT)
이렇게 출력이 됩니다.
11-30 11:02:50.227 7061 7061 D TEST : UserHandle.CURRENT : UserHandle{-2}
안드로이드 스튜디오에서 hidden api를 못찾는다고.. unresolved reference라도 뜨는데, 컴파일은 잘 됩니다.
예전에는 unresolved가 아니었는데,, 이 글을 쓰려고 샘플을 만들 때는 unresolved라고 뜨네요. 나중에 해결되면 업데이트하겠습니다.
Android Studio 4.0
미만의 버전에서 만든 샘플은 GitHub - CustomFramework에서 확인할 수 있습니다.
Android Studio 4.1
에서 만든 샘플은 GitHub - CustomFramework4.1을 참고해주세요.
참고
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력