본 포스팅은 DroidKaigi 2018 ~ なんとなく動いているProGuardから脱出するために 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
DroidKaigi 2018
Sato Shun
서론
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
단, 빈번한 패턴이 있고, 그 패턴 몇 가지를 이해하면 의외로 어떻게든 된다.
Proguard의 기본적인 움직임과 UseCase를 보면서 Proguard를 깊게 이해하는 것
그런데 정말로 Proguard는 필요한가?
코드 사이즈 축소/Method 수 축소
→ 구체적으로 어느 정도 삭제 가능한가?
Before (2 dex files)
After (1 dex file)
Before (3 dex files)
After (2 dex file)
Proguard 구조/기능
![https://cdn-images-1.medium.com/max/1600/0Y0tJVDd5RnFy_qUL.](https://cdn-images-1.medium.com/max/1600/0Y0tJVDd5RnFy_qUL.)
[https://cdn-images-1.medium.com/max/1600/0Y0tJVDd5RnFy_qUL.](https://cdn-images-1.medium.com/max/1600/0Y0tJVDd5RnFy_qUL.)
어떻게 EntryPoint 혹은 난독화 유무를 지정하는가
삭제/난독화 무효 | 난독화 무효 | |
---|---|---|
Class, Member | -keep | -keepnames |
Member | -keepclassmembers | -keepclassmembernames |
Member가 존재할 경우의 Class, Member |
-keepclasseswithmemembers | -keepclasseswithmemembernames |
Android에서 구체적인 UseCase
사례 1 : OkHttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-dontwarn
-keepnames
2 종류의 Rule을 지정하고 있다
이것들을 Proguard를 지정하지 않은 경우
100줄 이상 Warning 이 나온다
하지만 Warning 을 보면 can’t find referenced class Class 명 뿐이다
can’t find referenced class?
javax.annotation.Nullable이 참조 불가능하므로 Warning이 나온다
Warning을 무시하는 Rule
-dontwarn javax.annotation.Nullable
↓
-dontwarn javax.annotation.**
javax.annotation Pacakge 아래에 대한 Warning을 모두 무시한다
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
를 지정해 Warning이 사라지고 무사히 컴파일이 통과된다
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
컴파일은 통과하지만 Document에는 위와 같이 필요하다고 적혀있다
지정한 Class, Member를 낙독화하지 않는다
okhttp3.internal.publicsuffix.PublicSuffixDatabase
↓
a.a.a.p
와 같이 낙독화되는 것을 방지
PublicSuffixDatabase.java
PublicSuffixDatabase::class.java.getResourceAsStream("publicsuffixes.gz")
PublicSuffixDatabase Class의 상대 경로로부터 gz 파일을 읽고 있다 (gz 파일은 OkHttp jar에 포함된다)
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
난독화를 막고, Class 경로를 유지
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
사례 2 : moshi
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepclasseswithmembers class * {
@com.squareup.moshi.* <methods>;
}
-keep @com.squareup.moshi.JsonQualifier interface *
-dontwarn
-keepclasseswithmembers
-keep
3종류의 Rule을 사용하고 있다
val moshi = Moshi.Builder()
.add(ColorAdapter())
.build()
val r = moshi
.adapter(Rectangle::class.java)
.fromJson("{\"width\":1,\"color\":\"#ff0000\"}")
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class HexColor
class Rectangle(
@field:Json(name = "width") val w: Int,
@field:HexColor @field:Json(name = "color") val c: Int
)
class ColorAdapter(
@ToJson fun toJson(@HexColor rgb: Int): String {
return String.format("#%06x", rgb)
}
@FromJson @HexColor fun fromJson(rgb: String): Int {
return Integer.parseInt(rgb.substring(1), 16)
}
)
class Rectangle(
@field:Json(name = "width") val w: Int,
@field:Json(name = "color") val c: Int
)
class ColorAdapter(
)
이것은 Runtime시에 필요하므로 삭제되면 안된다
-keepclasseswithmembers class ColorAdapter {
@com.squareup.moshi.ToJson <methods>;
@com.squareup.moshi.FromJson <methods>;
}
↓
-keepclasseswithmembers class * {
@com.squareup.moshi.* <members>;
}
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class HexColor
class Rectangle(
@field:Json(name = “width”) val w: Int,
@field:HexColor @field:Json(name = “color”) val c: Int
)
class ColorAdapter {
@ToJson fun toJson(@HexColor rgb: Int): String {
return String.format(“#%06x”, rgb)
}
@FromJson @HexColor fun fromJson(rgb: String): Int {
return Integer.parseInt(rgb.substring(1), 16)
}
}
@HexColor는 Method, 필드에 붙어있을 뿐, Java 코드상에서 참조하지 않는다
→ 결과, Proguard가 삭제한다
→ 왜 삭제되는지 않겠나요?
APK 내부를 분석하는 도구
https://developer.android.com/studio/build/apk-analyzer#load_proguard_mappings
Method 정보에 FromJson Annotation 정보뿐이고 HexColor Annotation이 삭제된 것을 알 수 있다
해당 발표 자료의 이미지를 부분 캡쳐했습니다.
-keep interface HexColor
↓
-keep @com.squareup.moshi.JsonQualifier interface *
JsonQualifier Annotation를 가진 모든 interface를 삭제/난독화하지 않는다
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class HexColor
class Rectangle(
@field:Json(name = "width") val w: Int,
@field:HexColor @field:Json(name = "color") val c: Int
)
class ColorAdapter {
@ToJson fun toJson(@HexColor rgb: Int): String {
return String.format("#%06x", rgb)
}
@FromJson @HexColor fun fromJson(rgb: String): Int {
return Integer.parseInt(rgb.substring(1), 16)
}
}
-dontwarn okio.**
-dontwarn javax.annotation.** -keepclasseswithmembers class * {
@com.squareup.moshi.* <methods>;
}
-keep @com.squareup.moshi.JsonQualifier interface *
사례 3 : Keep Annotation
Class, Member에 붙이는 것으로 삭제, 난독화를 방지할 수 있다
@Keep
class User { ... }
어떻게 구현하고 있는가?
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
-keep class android.support.annotation.Keep
Keep Annotation을 삭제/난독화하지 않는다
-keep @android.support.annotation.Keep class * {*;}
Keep Annotation이 있는 Class가 대상
Class, Member 삭제/난독화하지 않는다
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
Keep Annotation이 있는 Method가 대상
Class를 난독화, Member를 삭제/난독화하지 않는다
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
Keep Annotation이 있는 Field가 대상
Class를 난독화, Field를 삭제/난독화하지 않는다
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
Keep Annotation이 있는 생성자가 대상
Class를 난독화, 생성자를 삭제//난독화하지 않는다
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
사례 4 : AAPT
Activity나 View의 Keep Rule은 (거의) 자동생성된다
AAPT = Android Asset Packaging tool
하지만 예외는 있다
DroidKaigi 2018에서 Proguard 대응 PR
Process: io.github.droidkaigi.confsched2018.debug
b.h: null cannot be cast to non-null type
android.support.v7.widget.SearchView
at io.github.droidkaigi.confsched2018.presentation.search.c.a(SearchFragment.kt:135)
https://github.com/DroidKaigi/conference-app-2018/pull/198#issuecomment-358142894
<item
android:id="@+id/action_search"
android:title="@string/search_title"
app:actionViewClass="....widget.SearchView"
app:showAsAction="always" />
위 Menu Layout에서 SearchView를 참조하고 있다
AAPT 도구는 app:actionViewClass로 지정한 Class에 대해서 Proguard Rule을 생성해주지 않는다
View에 필요한 생성자가 사라진 것을 알 수 있다
해당 발표 자료의 이미지를 부분 캡쳐했습니다.
-keep class android.support.v7.widget.SearchView {
<init>(...);
}
SearchView 의 모든 생성자를 삭제하지 않기 위한 Rule을 추가
↓ 조금 전의 Proguard를 적용
이 Rule을 지정하면 View에 필요한 생성자가 삭제되지 않은 것을 알 수 있다
해당 발표 자료의 이미지를 부분 캡쳐했습니다.
추가 tips
아래 코드만 사용
Observable.just(100)
.map { it.toString() }
.subscribe()
(io.reactivex.** Method 개수)
대부분의 Class, Member를 사용하지 않으므로 대부분 삭제된다
라이브러리쪽에서 Proguard를 제공할 수 있다
ex. leakcanary
https://github.com/square/leakcanary/blob/master/leakcanary-android/consumer-proguard-rules.pro
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024