Android Jar 파일만 ProGuard 적용하기

 간단한 안드로이드 앱에 프로가드를 적용해 봤었습니다. 이번에는 안드로이드 JAR 파일을 프로가드 적용하는 것을 정리해 보겠습니다. 기존 처럼 프로젝트에 포함된 proguard-project.txt와 project.properties 파일을 수정하는게 아닌 프로가드 GUI를 활용해서 처리합니다. 또한 안드로이드 앱과 JAR 파일 모두 적용하는게 아닌 JAR 파일만 프로가드를 적용합니다.


1. 기본 구성


 테스트를 위한 안드로이드 프로젝트를 생성합니다. 패키지명 com.wwforever.androidobfuscationtest로 만들었습니다.

package com.wwforever.androidobfuscationtest;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;

import com.wwforever.obfuscationjartest.ObfuscationJar;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  ObfuscationJar.showMessage();
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }
}
 소스 내용은 간단합니다. 밑에서 만들 ObfuscationJar 객체의 showMessage() 메서드를 호출해줍니다.

 프로가드를 적용할 안드로이드 JAR 프로젝트를 생성합니다. 패키지명 com.wwforever.obfuscationjartest로 만들었습니다.

package com.wwforever.obfuscationjartest;

import android.util.Log;

public class ObfuscationJar {
 public static void showMessage() {
  Log.d("WestWoodForever", "Android JAR ObfuscationTest");
 }
}

 JAR 프로젝트의 소스 역시 간단합니다. 로그만 출력하고 있죠.


2. JAR 파일용 프로가드 GUI 설정

 각자의 안드로이드 SDK 설치 위치의 sdk\tools\proguard\bin\proguardgui.bat를 실행합니다.

 프로가드 GUI가 실행되면 Load configuration을 클릭해 sdk\tools\proguard\examples 에 있는 android.pro 파일을 로드합니다. 안드로이드 jar용은 없어서 일단 이걸 기반으로 설정을 수정해 나갈려고 합니다. Next를 클릭합니다.

 프로가드를 적용할 jar 파일과 종속성이 있는 library를 설정할 수 있는 Input/Output 화면이니다. 그림과 같이 기존에 이미 입력된 값들은 모두 Remove 시켜줍니다.

 그리고 각자의 환경에 맞게 프로가드 적용할 JAR 파일을 Add input을 눌러 추가하고 Add output을 눌러 결과물 저장할 폴더를 지정합니다.

 밑에 Library 부분은 설치한 안드로이드 SDK의 android.jar 파일은 기본적으로 추가하고 android-support-v4.jar는 작업중인 jar가 필요로 한다면 추가합니다. 그 외에도 종속적인 lib들을 추가후 Next를 클릭합니다.

 사용하지 않는 메서드등을 없애는 Shrinking 부분입니다. 여기서 위에서 만든 객체와 메서드를 프로가드 적용하지 않고 그대로 유지하기 위한 설정을 그림과 같이 추가합니다. 제대로 설정하지 못하면 메서드를 찾지 못하는

The method xxx() is undefined for the type ObfuscationJar xxx.java
 에러나,

Note: the configuration refers to the unknown class 'ObfuscationJar'
      Maybe you meant the fully qualified name 'com.wwforever.obfuscationjartest.ObfuscationJar'?
Note: there were 1 references to unknown classes.
      You should check your configuration for typos.

 클래스를 찾지 못하는 에러등을 접할 수 있습니다. Next를 클릭합니다.

 난독화 옵션을 처리하는 Obfuscation 부분입니다. 그림과 같이 기본적인 사항 적용 후 Next를 클릭합니다.

 최적화 옵션을 처리하는 Optimization 입니다. Optimization passes가 1로 되어있을텐데 5로 설정해줍니다. 또한 Remove debugging에서 원하시는 불필요한 로그 정보는 체크하시고 Next를 클릭합니다.

 나머지 설정을 처리하는 Infomation 부분입니다. Target을 1.6으로 해주고 그림과 같이 설정 후 Next를 클릭합니다.

 프로가드 적용 준비가 끝났습니다. Process를 눌러 적용합니다. 또한 지금까지 만든 프로가드 설정을 Save configuration을 눌러 파일로 저장해 놓습니다. 필요시 이것을 로딩해서 수정 후 사용하면 되겠습니다.

ProGuard, version 4.7
Reading program jar [D:\obfuscationjartest\obfuscationjartest.jar]
Reading library jar [D:\adt-bundle-windows-x86_64-20130219\sdk\platforms\android-17\android.jar]
Reading library jar [D:\adt-bundle-windows-x86_64-20130219\sdk\extras\android\support\v4\android-support-v4.jar]
Preparing output directory [D:\obfuscationjartest\proguard_result]
  Copying resources from program jar [D:\obfuscationjartest\obfuscationjartest.jar]
Processing completed successfully

 Process를 눌러 나온 로그가 위와 비슷하다면 성공한 것입니다.

 프로가드 적용된 JAR 파일을 안드로이드 앱 프로젝트에서 가져와 적용해봤습니다. 잘 작동하고 있네요.

 JD-GUI로 디컴파일 해본 모습입니다. 테스트 중이라 간단해서 난독화고 뭐고 적용이 안된 것 처럼 보이지만,

 파일 크기를 보면 최적화는 된 상태입니다. 오른쪽이 프로가드 적용 파일이죠. 소스가 워낙 간단하고 위에서 keep 처리해서 난독화는 패스된 것이죠.

 지금까지 간단한 JAR 파일에 프로가드 적용하는 것을 정리해 봤습니다. 아직 옵션마다 어떤 기능인지 파악이 안된 상황입니다. 설정을 변경해가면서 프로젝트에 맞는 최적의 옵션을 찾는게 관건인 듯 합니다.

 다음에는 유니티3D 바이두 Duoku 안드로이드 플러그인에 프로가드를 적용하는 것을 정리해 보겠습니다. 아래는 위에서 만든 설정 파일 내용입니다.

-injars obfuscationjartest.jar
-outjars proguard_result

-libraryjars 'D:\adt-bundle-windows-x86_64-20130219\sdk\platforms\android-17\android.jar'
-libraryjars 'D:\adt-bundle-windows-x86_64-20130219\sdk\extras\android\support\v4\android-support-v4.jar'

-optimizationpasses 5
-dontusemixedcaseclassnames
-dontpreverify
-dontnote com.android.vending.licensing.ILicensingService
-dontwarn android.support.**


# Preserve all fundamental application classes.
-keep public class * extends android.app.Activity

-keep public class * extends android.app.Application

-keep public class * extends android.app.Service

-keep public class * extends android.content.BroadcastReceiver

-keep public class * extends android.content.ContentProvider

# Preserve all View implementations, their special context constructors, and
# their setters.
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context,android.util.AttributeSet);
    public <init>(android.content.Context,android.util.AttributeSet,int);
    public void set*(...);
}

# Preserve all classes that have special context constructors, and the
# constructors themselves.
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet);
}

# Preserve all classes that have special context constructors, and the
# constructors themselves.
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet,int);
}

# Preserve the special fields of all Parcelable implementations.
-keepclassmembers class * extends android.os.Parcelable {
    static android.os.Parcelable$Creator CREATOR;
}

# Preserve static fields of inner classes of R classes that might be accessed
# through introspection.
-keepclassmembers class **.R$* {
    public static <fields>;
}

# Preserve the required interface from the License Verification Library
# (but don't nag the developer if the library is not used at all).
-keep public interface  com.android.vending.licensing.ILicensingService

# Preserve the special static methods that are required in all enumeration
# classes.
-keepclassmembers class * extends java.lang.Enum {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# WestWood Forever ObfuscationJar Test
-keepclasseswithmembers public class com.wwforever.obfuscationjartest.ObfuscationJar {
    public void *(...);
}

# Also keep - Bean classes. Keep all specified classes, along with their getters
# and setters.
-keep class * {
    void set*(***);
    void set*(int,***);
    boolean is*();
    boolean is*(int);
    *** get*();
    *** get*(int);
}

# Keep names - Native method names. Keep all native class/method names.
-keepclasseswithmembers,allowshrinking class * {
    native <methods>;
}

# Remove - System method calls. Remove all invocations of System
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.System {
    public static long currentTimeMillis();
    static java.lang.Class getCallerClass();
    public static int identityHashCode(java.lang.Object);
    public static java.lang.SecurityManager getSecurityManager();
    public static java.util.Properties getProperties();
    public static java.lang.String getProperty(java.lang.String);
    public static java.lang.String getenv(java.lang.String);
    public static java.lang.String mapLibraryName(java.lang.String);
    public static java.lang.String getProperty(java.lang.String,java.lang.String);
}

# Remove - Math method calls. Remove all invocations of Math
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.Math {
    public static double sin(double);
    public static double cos(double);
    public static double tan(double);
    public static double asin(double);
    public static double acos(double);
    public static double atan(double);
    public static double toRadians(double);
    public static double toDegrees(double);
    public static double exp(double);
    public static double log(double);
    public static double log10(double);
    public static double sqrt(double);
    public static double cbrt(double);
    public static double IEEEremainder(double,double);
    public static double ceil(double);
    public static double floor(double);
    public static double rint(double);
    public static double atan2(double,double);
    public static double pow(double,double);
    public static int round(float);
    public static long round(double);
    public static double random();
    public static int abs(int);
    public static long abs(long);
    public static float abs(float);
    public static double abs(double);
    public static int max(int,int);
    public static long max(long,long);
    public static float max(float,float);
    public static double max(double,double);
    public static int min(int,int);
    public static long min(long,long);
    public static float min(float,float);
    public static double min(double,double);
    public static double ulp(double);
    public static float ulp(float);
    public static double signum(double);
    public static float signum(float);
    public static double sinh(double);
    public static double cosh(double);
    public static double tanh(double);
    public static double hypot(double,double);
    public static double expm1(double);
    public static double log1p(double);
}

# Remove - Number method calls. Remove all invocations of Number
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.* extends java.lang.Number {
    public static java.lang.String toString(byte);
    public static java.lang.Byte valueOf(byte);
    public static byte parseByte(java.lang.String);
    public static byte parseByte(java.lang.String,int);
    public static java.lang.Byte valueOf(java.lang.String,int);
    public static java.lang.Byte valueOf(java.lang.String);
    public static java.lang.Byte decode(java.lang.String);
    public int compareTo(java.lang.Byte);
    public static java.lang.String toString(short);
    public static short parseShort(java.lang.String);
    public static short parseShort(java.lang.String,int);
    public static java.lang.Short valueOf(java.lang.String,int);
    public static java.lang.Short valueOf(java.lang.String);
    public static java.lang.Short valueOf(short);
    public static java.lang.Short decode(java.lang.String);
    public static short reverseBytes(short);
    public int compareTo(java.lang.Short);
    public static java.lang.String toString(int,int);
    public static java.lang.String toHexString(int);
    public static java.lang.String toOctalString(int);
    public static java.lang.String toBinaryString(int);
    public static java.lang.String toString(int);
    public static int parseInt(java.lang.String,int);
    public static int parseInt(java.lang.String);
    public static java.lang.Integer valueOf(java.lang.String,int);
    public static java.lang.Integer valueOf(java.lang.String);
    public static java.lang.Integer valueOf(int);
    public static java.lang.Integer getInteger(java.lang.String);
    public static java.lang.Integer getInteger(java.lang.String,int);
    public static java.lang.Integer getInteger(java.lang.String,java.lang.Integer);
    public static java.lang.Integer decode(java.lang.String);
    public static int highestOneBit(int);
    public static int lowestOneBit(int);
    public static int numberOfLeadingZeros(int);
    public static int numberOfTrailingZeros(int);
    public static int bitCount(int);
    public static int rotateLeft(int,int);
    public static int rotateRight(int,int);
    public static int reverse(int);
    public static int signum(int);
    public static int reverseBytes(int);
    public int compareTo(java.lang.Integer);
    public static java.lang.String toString(long,int);
    public static java.lang.String toHexString(long);
    public static java.lang.String toOctalString(long);
    public static java.lang.String toBinaryString(long);
    public static java.lang.String toString(long);
    public static long parseLong(java.lang.String,int);
    public static long parseLong(java.lang.String);
    public static java.lang.Long valueOf(java.lang.String,int);
    public static java.lang.Long valueOf(java.lang.String);
    public static java.lang.Long valueOf(long);
    public static java.lang.Long decode(java.lang.String);
    public static java.lang.Long getLong(java.lang.String);
    public static java.lang.Long getLong(java.lang.String,long);
    public static java.lang.Long getLong(java.lang.String,java.lang.Long);
    public static long highestOneBit(long);
    public static long lowestOneBit(long);
    public static int numberOfLeadingZeros(long);
    public static int numberOfTrailingZeros(long);
    public static int bitCount(long);
    public static long rotateLeft(long,int);
    public static long rotateRight(long,int);
    public static long reverse(long);
    public static int signum(long);
    public static long reverseBytes(long);
    public int compareTo(java.lang.Long);
    public static java.lang.String toString(float);
    public static java.lang.String toHexString(float);
    public static java.lang.Float valueOf(java.lang.String);
    public static java.lang.Float valueOf(float);
    public static float parseFloat(java.lang.String);
    public static boolean isNaN(float);
    public static boolean isInfinite(float);
    public static int floatToIntBits(float);
    public static int floatToRawIntBits(float);
    public static float intBitsToFloat(int);
    public static int compare(float,float);
    public boolean isNaN();
    public boolean isInfinite();
    public int compareTo(java.lang.Float);
    public static java.lang.String toString(double);
    public static java.lang.String toHexString(double);
    public static java.lang.Double valueOf(java.lang.String);
    public static java.lang.Double valueOf(double);
    public static double parseDouble(java.lang.String);
    public static boolean isNaN(double);
    public static boolean isInfinite(double);
    public static long doubleToLongBits(double);
    public static long doubleToRawLongBits(double);
    public static double longBitsToDouble(long);
    public static int compare(double,double);
    public boolean isNaN();
    public boolean isInfinite();
    public int compareTo(java.lang.Double);
    public <init>(byte);
    public <init>(short);
    public <init>(int);
    public <init>(long);
    public <init>(float);
    public <init>(double);
    public <init>(java.lang.String);
    public byte byteValue();
    public short shortValue();
    public int intValue();
    public long longValue();
    public float floatValue();
    public double doubleValue();
    public int compareTo(java.lang.Object);
    public boolean equals(java.lang.Object);
    public int hashCode();
    public java.lang.String toString();
}

# Remove - String method calls. Remove all invocations of String
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.String {
    public <init>();
    public <init>(byte[]);
    public <init>(byte[],int);
    public <init>(byte[],int,int);
    public <init>(byte[],int,int,int);
    public <init>(byte[],int,int,java.lang.String);
    public <init>(byte[],java.lang.String);
    public <init>(char[]);
    public <init>(char[],int,int);
    public <init>(java.lang.String);
    public <init>(java.lang.StringBuffer);
    public static java.lang.String copyValueOf(char[]);
    public static java.lang.String copyValueOf(char[],int,int);
    public static java.lang.String valueOf(boolean);
    public static java.lang.String valueOf(char);
    public static java.lang.String valueOf(char[]);
    public static java.lang.String valueOf(char[],int,int);
    public static java.lang.String valueOf(double);
    public static java.lang.String valueOf(float);
    public static java.lang.String valueOf(int);
    public static java.lang.String valueOf(java.lang.Object);
    public static java.lang.String valueOf(long);
    public boolean contentEquals(java.lang.StringBuffer);
    public boolean endsWith(java.lang.String);
    public boolean equalsIgnoreCase(java.lang.String);
    public boolean equals(java.lang.Object);
    public boolean matches(java.lang.String);
    public boolean regionMatches(boolean,int,java.lang.String,int,int);
    public boolean regionMatches(int,java.lang.String,int,int);
    public boolean startsWith(java.lang.String);
    public boolean startsWith(java.lang.String,int);
    public byte[] getBytes();
    public byte[] getBytes(java.lang.String);
    public char charAt(int);
    public char[] toCharArray();
    public int compareToIgnoreCase(java.lang.String);
    public int compareTo(java.lang.Object);
    public int compareTo(java.lang.String);
    public int hashCode();
    public int indexOf(int);
    public int indexOf(int,int);
    public int indexOf(java.lang.String);
    public int indexOf(java.lang.String,int);
    public int lastIndexOf(int);
    public int lastIndexOf(int,int);
    public int lastIndexOf(java.lang.String);
    public int lastIndexOf(java.lang.String,int);
    public int length();
    public java.lang.CharSequence subSequence(int,int);
    public java.lang.String concat(java.lang.String);
    public java.lang.String replaceAll(java.lang.String,java.lang.String);
    public java.lang.String replace(char,char);
    public java.lang.String replaceFirst(java.lang.String,java.lang.String);
    public java.lang.String[] split(java.lang.String);
    public java.lang.String[] split(java.lang.String,int);
    public java.lang.String substring(int);
    public java.lang.String substring(int,int);
    public java.lang.String toLowerCase();
    public java.lang.String toLowerCase(java.util.Locale);
    public java.lang.String toString();
    public java.lang.String toUpperCase();
    public java.lang.String toUpperCase(java.util.Locale);
    public java.lang.String trim();
}

# Remove - StringBuffer method calls. Remove all invocations of StringBuffer
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.StringBuffer {
    public <init>();
    public <init>(int);
    public <init>(java.lang.String);
    public <init>(java.lang.CharSequence);
    public java.lang.String toString();
    public char charAt(int);
    public int capacity();
    public int codePointAt(int);
    public int codePointBefore(int);
    public int indexOf(java.lang.String,int);
    public int lastIndexOf(java.lang.String);
    public int lastIndexOf(java.lang.String,int);
    public int length();
    public java.lang.String substring(int);
    public java.lang.String substring(int,int);
}

# Remove - StringBuilder method calls. Remove all invocations of StringBuilder
# methods without side effects whose return values are not used.
-assumenosideeffects public class java.lang.StringBuilder {
    public <init>();
    public <init>(int);
    public <init>(java.lang.String);
    public <init>(java.lang.CharSequence);
    public java.lang.String toString();
    public char charAt(int);
    public int capacity();
    public int codePointAt(int);
    public int codePointBefore(int);
    public int indexOf(java.lang.String,int);
    public int lastIndexOf(java.lang.String);
    public int lastIndexOf(java.lang.String,int);
    public int length();
    public java.lang.String substring(int);
    public java.lang.String substring(int,int);
}

# Remove debugging - Throwable_printStackTrace calls. Remove all invocations of
# Throwable.printStackTrace().
-assumenosideeffects public class java.lang.Throwable {
    public void printStackTrace();
}

댓글

이 블로그의 인기 게시물

CMake Windows에 설치하기

'xxx.exe' 프로그램을 시작할 수 없습니다. 지정된 파일을 찾을 수 없습니다.

크로스 스레드 작업이 잘못되었습니다. xxx 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다