native code에서 java class에 접근과 android 난독화(proGuard)

 

// Find the Mat classe
jclass matClass = env->FindClass("org/opencv/core/Mat");

// Get Mat class's methods<getNativeObjAddr()>
jmethodID getNativeObjAddrMethod = env->GetMethodID(matClass, "getNativeObjAddr", "()J");

native code에서 자바 클래스를 액세스 하기위해 java class나 Method를 구하는 경우,

Debug모드에서 잘 동작하던 코드가 Release 모드에서 

JNI DETECTED ERROR IN APPLICATION: JNI NewStringUTF called with pending exception java.lang.NoSuchMethodError: no non=static mathod xxxxxxx

와 같은 에러를 뱉어 내며 Crash나는 현상이 발생할 수 있다.

이러한 에러는 android app 빌드시, 자바 클래스가 난독화 되어 class나 method를 찾지 못하여 발생하는 것으로 보인다.

 

관련 설정은

bundle.gradle(Module: app) 의

buildTypes {
	release {
    	minifyEnabled true 
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 
        }
    }

와 관련된 부분으로

release모드에서만 발생하는 이유는 당연하게도 프로젝트 생성시, release모드에 난독화 활성화 되도록 디폴트로 설정되기 때문이다.

 

release모드에서만 발생하고,

minifyEnabledfalse로 변경했을때 정상적으로 실행된다면 이문제일 확률이 99.9%이다.

 

하지만, 난독화를 아예 비활성 시키기에는 찜찜하니 조그만 더 알아 보자면

난독화는 'proguard-rules.pro'파일에 설정된 내용을 기본으로 반영하므로 해당 파일을 수정해서 특정 Class, method, field등을 난독화 예외 대상으로 지정하면 난독화를 유지한 상태에서 일부 Class만 예외 적용 할 수 있다.

class 전체 예외

-keep class org.opencv.core.Mat { *; }

class 필드 예외

-keep class org.opencv.core.Mat { <fields>; }

class 메소드 예외

-keep class org.opencv.core.Mat { <methods>; }

특정 도메인 이하를 예외하거나 특정 class에서 상속받은 클래스를 예외할 수 있는 방법도 있는 모양이지만,

지금은 귀찮으니 여기까지만 알아본다.

 

*테스트 결과 native코드를 프로젝트에 포함한 경우에는 난독화가 문제가 되지 않았다.

(native코드를 포함한 라이브러리를 다른 프로젝트에서 사용할때 상기의 이슈가 발생함)

 

[bundle.gradle]

signingConfigs {
  release {
    keyAlias 'aliasname'
    keyPassword 'password'
    storeFile file('.jks file path')
    storePassword 'password'
  }
}

buildTypes {
  release {
    debuggable true
    signingConfig signingConfigs.release

    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  }
}

ndk-build를 이용한

Native Code 빌드 및 사용방법

 

다음과 같은 이유로 Android App에서 Native Code(C/C++등)로 코드를 작성하여 사용해야 할 경우가 존재합니다.

  • 처리 속도 및 성능향상
  • C/C++로 만들어진 코드 재사용
  • Native Code로만 개발 가능한 기능 구현등

 

본 포스팅에서는 Android Studio에서 Native Code를 작성하고 빌드하는 방법에 대해 "최대한 많은 스크린 샷"을 통해 설명하고자 합니다.

작성 예제는 Native코드를 포함한 라이브러리(ndklib)와 이 라이브러리를 사용하는 app(ndklibdemo)으로 모듈을를 구분하여 작성 하도록 하겠습니다.

 

프로젝트 및 라이브러리 생성

1. 프로젝트 생성

프로젝트 타입을 'Empty Activity'로 선택합니다.

 

적당한 프로젝트 이름을 지정합니다. "ndklibdemo"로 설정했습니다.

 

2.라이브러리 모듈생성

[File>New>New Module..] 메뉴를 통해 새로운 라이브러리 모듈 추가

(Native Code가 작성될 모듈입니다.)

"Android Library"를 선택합니다.
적당한 라이브러리 명을 지정합니다. "ndklib"로 지정했습니다.
프로젝트에 ndklib 라이브러리가 추가 되었습니다.

 

Native Code 빌드를 위한 툴킷 설치

1. NDK  설치

1) [Tool>SDK Manager] 메뉴를 통해 프로젝트 설정 창을 열고 [SDK Tools]탭을 선택후,

[NDK] 항목을 체크해서 설치합니다.

(1) Android NDK는 Native Development Kit의 약자로, C / C++과 같은 Native 언어를 사용하여 앱의 일부를 구현할 수있게 해주는 개발 툴킷입니다.

 

NDK 빌드환경 구성 및 코드 작성

1. C/C++ 소스 폴더 생성

1) 프로젝트 표시창의 뷰타입을 "Project"로 변경 후,

ndklib 라이브러리의 [ src/main ] 하위에 jni 폴더를 생성합니다.

2. NDK 빌드환경 구성

1) [File>Setting] 메뉴을 통해 설정 다이얼로그를 연후, [Tool>External Tools] 항목을 선택합니다.

본 스크린 샷에는 이미 설정된 3개의 구성이 이미 존재합니다. 이 3개의 구성을 아래의 설명대로 생성합니다.

2) NDK Group에 세가지 환경을 추가합니다. 각각의 역할은 아래와 같으며 각 설정방법은 아래에 기술합니다.

(1) ndklib-javah

- Java Class를 기반으로 Jni규격에 맞게 헤더파일을 생성해주는 역할

(2) ndklib-build

- Native(C/C++) 코드를 빌드하는 구성

(3) ndklib-clean

- Native(C/C++) 코드의 빌드 결과물을 지워주는 구성 

3) [ + ] 버튼을 눌러 각 구성을 추가합니다.

1) ndklib-javah

(1) Group: NDK

(2) Program: javah.exe의 경로를 적어 줍니다.

$JDKPath$\bin\javah.exe

(3) Arguments: javah.exe 실행시 전달되는 파라미터 입니다. 그냥 아래 내용대로 적어 줍니다.

-classpath "$Classpath$" -v -jni $FileClass$

(4) Working directory: Native Code 소스파일을 작성할 폴더 경로를 적어 줍니다.

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main\jni

- 위에서 생성한 jni폴더를 지정합니다.

- 지정한 경로에 헤더파일 생성됩니다.

2) ndklib-build

(1) Group: NDK

(2) Program: ndk-build.cmd의 경로를 적어 줍니다.

$JDKPath$\bin\javah.exeC:\Users\unodi\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

(3) Working directory: Library module의 src/main 폴더 경로를 적어 줍니다. 

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main

(3) ndklib-clean

(1) Group: NDK

(2) Program: ndk-build.cmd의 경로를 적어 줍니다.

$JDKPath$\bin\javah.exeC:\Users\unodi\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

(3) Arguments:

clean

(4) Working directory: Library의 main 폴더 경로를 적어 줍니다. 

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main

 

Native Code 작성

1. Native Wrapping Java Class 작성

1) [라이브러리>java>패키지]에 Native API를 Wrapping하는 Java Class를 작성합니다.

Class 이름을 "NativeWrapper"로 지정했습니다.

2) 생성된 Wrapping class에 라이브러리를 로드하는 기본 코드를 구현합니다.

static {

   System.loadLibrary("ndklib");

}

"ndklib"부분은 생성된 라이브러리의 이름을 기술 합니다.

public native int nativeSum(int a, int b);

native로 구현할 함수의 원형을 기술 합니다.

이 선언을 기반으로 javah가 Jni규격에 맞는 함수명을 생성하게 됩니다.

2. Native 코드 작성

1) Header 파일 생성

위에서 생성한 클래스에서 오른쪽 마우스를 누른후, [NDK>ndklib-javah] 메뉴를 선택해서 헤더파일을 생성합니다.

아래와 같이 class파일을 찾지 못한다고 표시되는 경우, 프로젝트를 한번 빌드한후 다시 시도합니다.

정상적으로 수행되면 아래와 같이 *.h 파일이 생성됩니다.

생성된 header파일은 [jni] 폴더에서 확인 할 수 있습니다.(ndklib-javah 환경 구성에서 지정한 경로 입니다.)

Jni 규격에 맞게 함수명과 파라미터가 작성됩니다.

2) cpp 파일 구현

헤더파일이 위치한 [jni]폴더에 cpp파일을 생성하여 함수내용을 구현합니다.

#include "com_thirteenrain_ndklib_NativeWrapper.h"

JNIEXPORT jint JNICALL Java_com_thirteenrain_ndklib_NativeWrapper_nativeSum
(JNIEnv *env, jobject obj, jint a, jint b)
{
     return a+b;
}

 

Gradle연동

1. android.mk 생성

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := ndklib

FILES := NativeSum.cpp

LOCAL_SRC_FILES := $(FILES)

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

"ndklib" - 라이브러리 이름을 기술합니다.

NativeSum.cpp - 빌드할 CPP 파일을 기술합니다.

2. NDK build - gradle 연동

1) Java Wrapping Class를 오른쪽 클릭하여 NDK Build를 Gradle과 연동합니다.

(1) Build System: ndk-build

(2) project Path: 위에서 생성한 Android.mk를 선택합니다.

이 작업을 통해 ndklib 모듈의 gradle에 NativeBuild 구문이 추가되어, Gradle빌드시 ndkBuild가 함께 진행되도록 설정됩니다.

android {
     externalNativeBuild {
          ndkBuild {
               path file('src/main/jni/Android.mk')
           }
     }
}

이상으로 Native Code 라이브러리 생성을 위한 설정이 완료되었습니다.

 

Native Code Library Build

1. Java Wrapping Class 우클릭 > NDK > ndklib-build 메뉴를 통해 Native Code를 빌드 할 수 있습니다.

2. ndklib/src/main/libs 하위에 라이브러리 파일(*.so)이 생성됩니다.

 

Native Library 사용

1. 코드 작성

1) 테스트를 위해 MainActivity의 onCreate 메소드에 ndklib의 NativeWapper.nativeSum을 호출하는 코드를 삽입합니다.

NativeWrapper nativeApi = new NativeWrapper();
int result = nativeApi.nativeSum(5, 10);

2. dependency 추가

1) [Alt+Enter] 단축키로 ndklib에 대한 dependency를 추가해 줍니다.

2) ndklib에 대한 dependency가 추가되었습니다.

디버깅

1) 디버깅 Tool설치

(1) LLDB

: Native Debugging을 위해서는 LLDB를 설치해야 합니다.(LLDB는 Android Studio에서 사용하는 Native Debugger입니다.)

2) Debug type

(1) Debug type이 [java]로 설정되어 있으면 Native Code에 대한 디버깅이 되지 않습니다.

: Java - Java Code만 디버깅 합니다.

: Auto - 디버깅 대상을 자동으로 판단합니다.

: Native - Native Code만 디버깅 합니다.

: Dual - Java, Native 둘다 디버깅 합니다.

 

실행확인

1) cpp 소스에 중단점을 설정합니다.

2) 툴바의 디버그 버튼 혹은 [Shit+F9] 단축키를 눌러 디버그를 진행합니다.

3) 중단점에 적중되어 Native Code 디버깅이 가능합니다.

 

  1. dd 2020.04.24 00:52

    javah.exe 파일이 없네요 ㅠㅠ

CardView

 

CardView는 아래와 같이 곡선 테두리와 그림자(음영)를 표시할 수 있는 형태의 레이아웃입니다.

 

종속성

CardView는 v7 Support library에 포함된 위젯으로

CardView를 사용하기 위해서는 Bundle.gradle(Module: app)에 다음과 같은 종속성을 추가해 주어야 합니다.

dependencies {
...
implementation 'com.android.support:cardview-v7:28.0.0'

...
}

 

CardView 속성

app:cardBackgroundColor - CardView의 배경색 지정

 

app:cardCornerRadius - 코너 radius 지정

 

app:cardElevation

- 음영 elevation 지정, elevation값이 클수록 음영을 표시하기 위한 공간이 더 필요하다.

app:cardMaxElevation

- 음영표시 영역 max값 지정이라고 되어 있는데, 왜 적용이 안되지?

 

app:contentPadding - CardView 패딩 지정

 

app:cardUseCompatPadding="true"

- 음영이 표시될 자동으로 확보 합니다. cardElevation 값이 클 수록 공간이 많이 확보됩니다.

 

app:cardPreventCornerOverlap - ?

 

 

그림자(음영) 표시

그림자(음영)이 표시되기 위해서는 CardView 주변으로 추가적인 공간이 필요합니다.

그림자가 표시될 영역을 확보할 수 있는 방법은 다음의 세가지 방법이 있습니다.

 

1. cardUseCompatPadding 속성을 통해 지정

app:cardUseCompatPadding="true"로 설정하여 음영을 그릴 공간을 자동으로 확보하도록 설정 합니다.

다만 이경우, cardElevation값이 클 수로 생각보다 많은 공간이 확보되어 CardView 영역이 작아 집니다. 

app:cardElevation="40dp"으로 설정한 모습입니다.

아래의 두가지 방법을 통해 고정된 공간을 확보할 수는 있으나, 반대로 음영을 표시할 공각이 부족하면

연결부분이 약간 부자연 스럽게 표시될수 있습니다.

 

2. 상위 layout의 padding을 통해 지정

상위 layout에 padding을 지정하고, clip 속성을 false로 지정해야 합니다.

http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:orientation="vertical"
    android:padding="8dp">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:cardBackgroundColor="#ff0000ff"
        app:cardCornerRadius="10dp"
        app:cardElevation="5dp"
        app:cardPreventCornerOverlap="false"
        app:contentPadding="0dp">

 

3. CardView에 layout_marging을 지정합니다.

<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="#ff0000ff"
app:cardCornerRadius="10dp"
app:cardElevation="5dp"
app:cardPreventCornerOverlap="false"
app:contentPadding="0dp"
android:layout_margin="8dp">

 

기타 속성

[parent layout]

android:clipChildren"
android:clipToPadding

 

참고URL

https://developer.android.com/guide/topics/ui/layout/cardview

https://stackoverflow.com/questions/41615468/how-to-add-shadow-on-cardview-aligned-to-bottom-of-parent/46374054

http://devstory.ibksplatform.com/2018/05/android-cardview.html

View의 background 속성을 이용해 View의 모양 변경하기


layout xml의 적용 부분

<View
android:id="@+id/marker"
android:layout_width="match_parent"
android:layout_height="7dp"
android:background="@drawable/marker_bound_top" />


marker_bound_top.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item >
<shape android:shape="rectangle">
<corners android:topLeftRadius="10dp" android:topRightRadius="10dp" />
<solid android:color="@color/default_marker_color"/>
</shape>
</item>
</layer-list>


shape Background 지정한 상태에서 필요에 따라 배경색상 변경

*setBackgroundColor()로 색상을 변경할 경우, xml로 지정한 내용이 무효화 된다.

아래와 같은 코드로 모양은 유지하면서 배경색만 변경 가능하다.

public void setMarkerColor(@ColorInt int color) {

Drawable backgroundOff = vMarker.getBackground();
backgroundOff.setTint(color);
vMarker.setBackground(backgroundOff);

}


Error type 3: Activity class {xxxx.MainActivity} does not exist.



package rename 후 빌드는 정상적으로 되나, app 실행 안되는 현상


$ adb shell am start -n "com.x.y.z/com.c.b.a.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER

Error while executing: am start -n "com.x.y.z/com.c.b.a.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER

Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.x.y.z/com.c.b.a.MainActivity }

Error type 3

Error: Activity class {com.x.y.z/com.c.b.a.MainActivity} does not exist.


Error while Launching activity 



1. Android Studio 종료
2. .idea Directory  삭제
3. .iml 확장자 파일 삭제
4. Android Studio 재시작


http://1004lucifer.blogspot.com/2015/03/intellij-build-apk.html

  1. ㅇㅇ 2020.09.12 14:35

    굿 덕분에 해결했습니다. 근데 ideal 파일이 뭔가요 자동으로 생성되는데, ,

+ Recent posts