본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] code 와 resource shrink 하자!

by 돼지왕 왕돼지 2018. 11. 23.
반응형


@keep, Android, Android Proguard, Annotation Support Lib, build flavor, build type, code shrink, code shrinking, Customize which code to keep, Customize which resource to keep, Decode an obfuscated stack trace, dump.txt, Enable code shrinking with Instant Run, Enable strict reference checks, getdefaultproguardfile, gradle, internal structure, keep.xml, library project dependency, main resource, mapping.txt, minifyEnable, minifyenabled, proguard file location, proguard output file location, proguard-android-optimize.txt, proguard-android.txt, proguard-rules.pro, resConfigs, resource discard, resource keep, resource merge, resource merge 우선순위, resource shrink, resource shrink file location, Resources.getIdentifier, resources.txt, resources>, retrace, retrace script location, seeds.txt, Shrink your code, Shrink your resources, shrinkResources, tools:discard, tools:keep, Troubleshoot resource shrinking, usage.txt, useProguard, [android] code 와 resource shrink 하자!, 난독화 mapping


-

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.





참고 자료




반응형

댓글