PathClassLoader

Jungwook Park
kjcoop
Published in
4 min readOct 2, 2021

App 에 포함하지 않은 다른 apk 로부터 class 를 불러오고 싶을 때

java compiler 는 bytecode 를 만들어내고 jvm 은 이 bytecode 를 이용하여 프로그램을 수행한다. 그렇다면 jvm 은 어떻게 bytecode 를 사용 가능한 상태로 만드는 것일까?

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html 에 따르면 jvm 이 bytecode 를 loading, linking, initializing 을 통해 Class 를 사용 가능한 상태로 만든다. 그리고 이 과정에 관여하는 class 가 ClassLoader 이다.

bytecode 는 저장소의 단순한 .class file 이기때문에 ClassLoader 를 이용한다면 bytecode 를 런타임에 불러온 후 실행하는 것이 가능하다.

혹은 platform dependent 한 Class 를 resolve 해 본 다음 이를 통해 플랫폼을 유추하는 것도 가능하다. (application -> extension -> bootstrap 중 platform 의존성이 있는 class 들이 있는 경우가 있으며 최종 resolve 가 실패한 경우 java.lang.UnsatisfiedLinkError 가 발생한다.)

Android 에서는 PathClassLoader, DexClassLoader 등을 사용하여 bytecode 를 불러올 수 있는데, PathClassLoader 를 이용하는 방법은 예를 들면 다음과 같다.

편의상 제공자를 provider, ClassLoad 를 이용하는 사용자를 consumer 로 명명한다.

provider 는 아래 기능을 제공하는 app 으로 apk 로 빌드하여 기기 설치하였다.

class Repeater {

fun repeat(text: String, count: Int): String {
return text.repeat(count)
}
}

provider package 가 query 가능한 상태에서 consumer 는 아래와 같이 사용할 수 있다.

fun classLoadAndRepeat(context: Context, packageName: String, classPath: String, text: String, count: Int): String {
val installedPath = context.packageManager.getApplicationInfo(packageName, 0).sourceDir
val pathClassLoader = PathClassLoader(installedPath, ClassLoader.getSystemClassLoader())
val clazz = Class.forName(classPath, true, pathClassLoader)
val repeatMethod = clazz.getDeclaredMethod("repeat", String::class.java, Int::class.java)
val instance = clazz.declaredConstructors.first().newInstance()

return repeatMethod.invoke(instance, text, count) as String
}

ClassLoader 를 이용하면 앱을 변경하지 않더라도 다른 apk 나 dex 등을 이용해 런타임에 코드를 대체할 수 있는 장점이 있다. 다만 구글 플레이 배포시 개발자 정책 위반 가능성이 있으니 유의해야 한다. https://support.google.com/googleplay/android-developer/answer/9888379?hl=en

--

--