-
android 에서는 ProGuard 를 통해서 사용하지 않는 class, field, method 를 제거할 수 있다.
또한 bytecode 를 optimize 하고, 사용하지 않는 code instruction 도 제거하고, 남아있는 코드들에 대해 short name 으로 난독화를 수행한다.
Shrink your code
-
ProGuard 로 code shrinking 을 하려면 gradle 에서 minifyEnable 을 true 를 설정해주면 된다.
code shrinking 은 build 시간을 늘리기 때문에 debug 에서는 사용하지 않는 것이 좋고, final apk 에 적용한 후에는 충분한 테스트도 거쳐야 한다. proguard 설정에 따라 에러가 날 수 있다.
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
...
}
-
getDefaultProguardFile('proguard-android.txt') 을 통해 Android plugin 에 들어있는 기본 ProGuard setting 을 읽어올 수 있다.
project build 를 할 때 plugin 은 setting file 의 복사본을 project-dir/build/intermediates/proguard-files 폴더에 생성한다
-
code shrinking 을 더 하드하게 하려면 proguard-android-optimize.txt 를 사용한다.
이 녀석은 기본 ProGuard rule 은 같지만, bytecode level 에서 optimization 을 추가로 수행하여 APK size 를 줄이고 조금 더 빠르게 앱이 작동할 수 있게 도와준다.
-
proguard-rules.pro 파일이 custom ProGuard rule 을 넣는 곳이다.
-
build variant 에 따라 추가적으로 ProGuard rule 을 추가하고자 하면 다른 proguardFiles 를 사용하면 된다.
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
flavor1 {
}
flavor2 {
proguardFile 'flavor2-rules.pro'
}
}
}
-
매 build 마다 ProGuard 는 아래 파일들을 생성한다.
dump.txt
APK 안에 있는 모든 class file 의 internal structure 를 보여준다.
mapping.txt
난독화된 class, method, field name 에 대한 mapping 을 제공한다.
seeds.txt
난독화되지 않은 class, member list 가 들어 있다.
usage.txt
APK 에서 제거된 code list 가 들어있다.
Customize which code to keep
-
일반적으로 기본 proguard-android.txt 가 사용하지 않는 code 를 제거하는 것으로 충분하다.
하지만 가끔 ProGuard 가 사용해야 하는 코드를 제거하기도 한다.
실수로 code 를 제거하는 케이스들은 아래와 같다.
AndroidManifest.xml 에만 정의되어 있고 참조되지 않는 파일
JNI 에서만 호출되는 코드
Reflection, Introspection 등을 통해 Runtime 에만 접근하는 코드
-
proguard 가 된 app test 를 통해 이를 발견할 수도 있지만, usage.txt 파일을 분석해서 확인할 수도 있다.
output 파일은 <module-name>/build/outputs/mapping/release 에 생성된다.
-
ProGuard 가 실수로 코드 제거하는 것을 방지하려면 -keep 을 사용해야 한다.
-keep public class MyClass
혹은 @Keep annotation 을 사용하는 것도 방법이다.
class 에 해당 annotation 을 적용하면 class 전체를 그대로 유지한다.
method, field 등에 적용하면 해당 method, field 를 보존하면서 난독화도 하지 않는다.
그리고 class name 도 난독화되지 않는다.
이 annotation 은 Annotation Support Lib 이 있어야 한다.
Decode an obfuscated stack trace
-
mapping.txt 는 매번 override 가 되기 때문에 release 될 때마다 mapping.txt 를 잘 보존해야 한다.
-
앱을 Google Play 에 올릴 때 mapping.txt 를 함께 올릴 수 있다.
그럼 Google Play 는 user-reported issue 에 대해 난독화 해독을 할 때 이 mapping 파일을 사용한다.
-
난독화된 stack trace 를 “retrace” script 를 통해 해독할 수 있다.
Windows 에서는 retrace.bat 이고, Mac/Linux 에서는 retrace.sh 이다.
이는 <sdk-root>/tools/proguard/ 에 있다.
script 는 mapping.txt 파일을 인자로 받는다.
retrace.bat -verbose mapping.txt obfuscated_trace.txt
Enable code shrinking with Instant Run
-
Incremental building 환경에서도 code shrinking 이 중요하다면, experimental code shrinker 를 사용할 수 있다.
이 녀석은 ProGuard 와는 다르게 Instant Run 에서도 shrinker 를 제공한다.
-
Android plugin shrinker 는 ProGuard 와 동일한 configuration file 을 사용한다.
하지만 이 녀석은 난독화와 code optimization 은 수행하지 않는다.
오직 미사용 코드만 제거한다.
useProguard 를 false 로 두면서 minifyEnabled 를 true 로 두면 된다.
android {
buildTypes {
debug {
minifyEnabled true
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}
Shrink your resources
-
Resource shrinking 은 code shrinking 과 함께 동작한다.
code shrinker 가 미사용 코드들을 제거한 다음에 어떤 resource 가 app 에 의해 계속 참조되는지를 본다.
-
resource shrinking 을 수행하기 위해서는 shrinkResources 를 true 로 주면 된다.
물론, minifyEnabled 는 true 로 되어 있어야 한다.
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}
-
resource shrinker 가 “현재”는 values 에 있는 resource 를 제거하지 않는다.
예를 들면 strings, dimensions, styles, colors 등이다.
왜냐하면 AAPT 가 Gradle Plugin 이 resource 의 predefine 된 version 을 명시하도록 허락하지 않기 때문이다.
Customize which resource to keep
-
특정 resource 를 keep 하거나 discard 하고 싶다면,
<resources> tag 로 XML file 을 만든다. ( res/raw/keep.xml )
이 파일은 APK packaging 에 포함되지 않는다.
tools:keep 을 통해 어떤 resource 들을 keep 할지, tools:discard 를 통해 어떤 녀석들을 discard 할지 명시할 수 있다.
이들은 resource name 을 comma-separated list 형태로 명시할 수 있다.
그리고 asterisk(*) 를 통해 wild card 도 적용할 수 있다.
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:discard="@layout/unused2" />
Enable strict reference checks
-
보통 resource shrinker 는 어떤 resource 가 사용되는지 잘 판별한다.
하지만 코드에서 Resources.getIdentifier() 와 같은 녀석들을 사용하여 dynamic resource 를 접근하는 경우에는 shrinker 가 방어적으로 이름 matching 이 되는 resource 들을 모두 보존한다.
String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
-
위는(getIdentifier 에서 사용하는 녀석들 보존) default 구현인 safe shrinking 이다.
확실히 사용되는 resource 들만 keep 하기 위해서는 keep.xml 파일에 shrinkMode 를 strict 로 설정하면 된다.
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
그리고 코드로 접근해서 보존하고 싶은 친구들은 tools:keep 으로 명시해주면 된다.
Remove unused alternative resources
-
Gradle resource shrinker 는 내 앱 코드에서 참조되지 않는 녀석들을 지워준다.
이 말인즉, 다른 device configuration 에 해당되는 대체 resource 들은 지우지 않는다는 의미이다.
필요하다면 Android Gradle plugin 의 resConfigs 를 통해 shrink 하지 않을 resource 들을 명시해주면 된다. ( 명시되지 않은 것들이 제거된다. )
예를 들어 AppCompat 이나 Google Play Service 를 사용하는 경우 그들이 제공하는 모든 translated language string 이 최종 APK 에 모두 들어간다.
내 앱이 한가지 혹은 두가지 언어만 지원하는 경우에도 말이다.
내 앱이 지원하는 언어에 대해서만 resource 를 유지하기 원한다면, 유지하고자 하는 언어 형태를 써 주면 된다. 아래는 English 와 French 를 공식 지원함을 이야기한다. 나머지 언어 res 들은 제거된다.
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
-
screen density, ABI resource 등도 마찬가지로 어떤 것을 유지할지 명시할 수 있다.
Merge duplicate resources
-
기본적으로 gradle 은 같은 이름을 가진 resource 를 머지한다.
이 동작은 shrinkResources 속성에 의해 control 할 수 없고, disable 할 수도 없다.
-
resource merge 는 2개 이상의 파일이 같은 resource 이름, type, qualifier 를 가지고 있을 때 발생한다.
gradle 이 아래의 priority order 에 의해 best choice 를 선정해서 한 개의 resource 를 AAPT 에 전달하고 이것이 APK 에 패킹된다.
-
gradle 은 다음 위치에서 중복 resource 를 찾는다. ( 숫자와 우선순위 관계 없음 )
1. main resource ( src/main/res )
2. variant overlays ( build type, build flavor )
3. library project dependency
-
gradle 은 중복 resource merge 를 다음 우선순위로 한다.
Dependencies < Main < Build flavor < Build type
예를 들어 중복 resource 가 main res 와 build flavor 둘 다에 등장하면, Gradle 은 build flavor 의 것을 선택한다.
만약 동일한 resource 가 같은 source set 에서 나온다면, gradle 은 resource merge error 를 뿜는다.
이는 src/main/res, src/main/res2 와 같은 sourceSet 속성에 여러 개의 source 를 제공한 경우에 발생한다.
Troubleshoot resource shrinking
-
resource shrinking 을 사용할 경우 Build window 는 제거된 resource 에 대한 summary 를 보여준다.
:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
-
gradle 은 resources.txt 라는 진단 파일도 만든다.
<module-name>/build/outputs/mapping/release 에 output 파일이 생성된다.
여기에는 어떤 resource 가 어떤 resource 를 참조하는지, 어떤 녀석이 사용되고 어떤 녀석이 제거되었는지 등이 적혀있다.
-
resources.txt 에는 아래와 같이 왜 제거되지 않았는지가 보인다.
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
“The root reachable resources are” 이라고 되어 있는 것은 code 에서 reachable 하다는 의미이다.
-
만약 strict checking 을 사용하지 않는다면 다음과 같은 결과를 볼 수 있다.
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
참고 자료
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[android] JobIntentService 소개 (tutorial) (0) | 2018.11.28 |
---|---|
[android] AAC 로 app 의 background, foreground 상태 알기 (1) | 2018.11.24 |
[android] D8 이 뭐야? (0) | 2018.11.22 |
[android] APK Signature Scheme v2 (0) | 2018.11.21 |
[android] Staged Rollout? (0) | 2018.10.12 |
댓글