Mobile App/Android

[안드로이드] release 해서 앱 실행하면 왜 crash 가 발생할까? (ProGuard leads to NullPointerException)

Jman 2024. 2. 25. 11:25

겪은 이슈

WebView 를 이용하여 하이브리드 App 을 개발하고, release 로 .apk 를 말아 사업팀에 전달하다 발생한 이슈였다.

발생한 에러는 아래와 같다.

 

 

자꾸 WebViewClient 쪽에서 url 이 Null 이 발생하여 NullPointerException 이 발생하는 Error 만 발생했다.

WebViewClient.class 83 줄에는 아래와 같은 코드가 있다.

 

@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    return super.shouldOverrideUrlLoading(view, request);
}

 

저 부분에서 왜 Null 이 발생했던 걸까?? Why?


이슈 해결

처음에 이슈가 난 이유를 몰라 한참을 헤맸다.

우선 첫 번째로 헤맸던 건,

android {
    // ...
    buildTypes {
        debug {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    'proguard-rules.txt',
                    'proguard-rules.debug.txt'
            debuggable true
        }
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    'proguard-rules.txt'
        }
    }
}

 

build.gradle 파일 내, 소스에서 buildTypes 중에 release minifyEnabled 'false' 를 하게 되면,

release 시에 Crash 가 발생하지 않았다. 

 

어?? 모지? Proguard 이슈 인가?

난독화하는 과정에서 발생한 이슈인데.. 어디서부터 잘못된걸까? 라는 생각을 하게 됐다.

 

그래도 이렇게 접근을 해서 해결할 수 있긴 했었다.

일단 내 소스는 WebViewClient 를 커스텀해서 사용하고 있었고,

shouldOverrideUrlLoading 함수를 오버라이딩 하여, 로그를 찍으면서 해당 웹뷰 화면 전환 간 Url 이 잘 찍히는지 확인하는 소스가 있었다. 해당 소스는 아래 Util 쪽, Logger Class 를 이용하여 Log tracking 을 했다.

private static void Logger(int priority, String msg) {
    if (GiftshopInfo.LOGGER) {
        StringBuilder msgBuilder = new StringBuilder();
        msgBuilder.append("[FORSHOP_LOG]").append("[").append(Thread.currentThread().getStackTrace()[4].getMethodName())
                .append("()").append("]").append(" ").append(msg)
                .append(" (").append(Thread.currentThread().getStackTrace()[4].getFileName()).append(":")
                .append(Thread.currentThread().getStackTrace()[4].getLineNumber()).append(")");

        Log.println(priority, Thread.currentThread().getStackTrace()[4].getFileName().replace(".java", ""), msgBuilder.toString());
    }
}

 

그러면서 생각했던 건,

어? 난독화를 하게 되면 Null Exception 이 발생하니깐, 난독화 이후에 Class 를 찾지 못하는 이슈일까?

이게 맞았다.

 

난독화를 하면, 소스 클래스가, a.b.c.d.e.class 이런식으로 소스파일 명들이 바뀌게 된다.

그러다보니 위 Logger Class 내에서 getFileName() 이런 함수를 사용하게 될 경우, 난독화 이전 원하는 Class 를 찾지 못해서 발생한 Error 였다.

 

오버라이딩해서 사용했던, Log 소스 한 줄을 주석하니, release 시에, Crash 가 발생되지 않았다.

참.. 난독화 관련하여 공부 안한 티가 났다.


난독화란?

코드 난독화는 프로그램 언어로 작성된 코드를, 치환 및 자리바꿈 등의 기법으로 소스코드를 알아보기 어렵게 만드는 기술이다.

 

그러면 왜 앱 소스를 난독화를 하게 될까?

앱 소스 코드를 분석하기 어렵게 만들기 위해 앱 소스 내에서 난독화 기술을 사용하게 된다. 그 중 Proguard 를 난 사용했다.

 

그러면 소스를 난독화하는 건 꼭 필요할까?

보안 수준을 높일 수 있기 때문에 난독화한다.

우리는 Google Play Store 를 통해서 Android 앱을 누구나 안드로이드 폰 기종으로 설치를 할 수 있다.

누구나 자유롭게 해당 앱을 다로드 받아, APK 파일을 추출하는 건 가능하다.

 

이 APK 파일을 디컴파일(Decompile) 을 하여, 소스코드를 보는 것도 가능하다.

이 말은 소스 코드를 뜯어서 앱 내 민감한 데이터를 복제하는데 이용하고 소스코드에 침입할 시에 사용될 수 있다는 말이다.

 

여기서 사용했던, Proguard 는 식별자 난독화(Renaming) 을 지원하고,

이는 소스코드를 식별자 변환, 클래스/메소드 이름을 특정 키워드로 대체하게 된다. 

(ex. a,b,c, ac, k 등)