반응형

Android API 업그레이드 후, Push 메시지 표시에 문제가 있다면 다음 부분을 확인 하자.

 

1. API 31 이상으로 업데이트 시 

PendingIntent FLAG 관련 이슈

Push Message 수신 부에서 PendingIntent 생성시 , 아래의 플래그를 함께 설정해 주어야 한다.

설정해 주지 않으면 아래와 같은 메시지가 똭 !!

Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

( FLAG_IMMUTABLE이 권장 되는 FLAG 인듯 )

PendingIntent.FLAG_IMMUTABLE 혹은 PendingIntent.FLAG_MUTABLE

 

여기까지 적용하면 App이 foreground상태에서는 push message가 정상적으로 처리 된다..

 

그런데 App이 Background상태에서는 TaskBar에 Push message가 표시되지 않을 수 있는데... 

(위에 문제는 직관적인데 이 문제는 찾느라 고생함)

해결 방법은 ? 당근 ?

Gragle에서 firebase-messaging관련 패키지의 버전을 높여주어야 한다

implementation 'com.google.firebase:firebase-messaging-ktx:23.0.4'

문제가 되는 플래그가 반영된 버전이 정확히 23.0.4 버전부터 인지는 모르겠으나, 23.0.4 버전 이상으로 업그레이드 할 경우 문제가 해소 된다.

원인은 위에 내용과 동일하다 나의 경우 이전에 참조한 버전이 FLAG_IMMUTABLE 혹은 FLAG_MUTABLE이 적용되기 전 버전이라 발생하는 문제...

 

그러면 끝인가? 하면

아직 하나 남아있다 .ㅡㅡ;

token을 얻는 부분이 변경되어야 한다.

그리고 23.0.4버전을 적용하고 나면, 토큰을 얻는 코드 또한 변경되어야 한다.

기존에 다음과 같이 토큰을 얻었을 텐데

FirebaseInstanceId.getInstance().instanceId.addOnCompleteListener(OnCompleteListener { task ->
    if (!task.isSuccessful) {
        return@OnCompleteListener
    }
    deviceToken = task.result!!.token
})

FirebaseInstanceId 가 사라졌다... OTL

수정은 아래 코드로 대체하면 된다.

 

JAVA

FirebaseMessaging.getInstance().getToken()
    .addOnCompleteListener(new OnCompleteListener<String>() {
        @Override
        public void onComplete(@NonNull Task<String> task) {
          if (!task.isSuccessful()) {
            Log.w(TAG, "Fetching FCM registration token failed", task.getException());
            return;
          }

          // Get new FCM registration token
          String token = task.getResult();

          // Log and toast
          String msg = getString(R.string.msg_token_fmt, token);
          Log.d(TAG, msg);
          Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
        }
    });

Kotlin

FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
    if (!task.isSuccessful) {
        Log.w(TAG, "Fetching FCM registration token failed", task.exception)
        return@OnCompleteListener
    }

    // Get new FCM registration token
    val token = task.result

    // Log and toast
    val msg = getString(R.string.msg_token_fmt, token)
    Log.d(TAG, msg)
    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
})

 

2. API 33 업데이트 시

API 33 이상의 경우, Push 수신을 위한 권한이 추가로 필요하다.

아래와 같은 권한이 필요하고

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"
        android:minSdkVersion="33"/>

Permission획득 방법은 다른 Permission과 동일하니 기술 하지 않는다.

 

위의 내용을 포함한 push message관련 사항은 아래 링크를 참조!!

https://firebase.google.com/docs/cloud-messaging/android/client?hl=ko#java_1 

 

Android에서 Firebase 클라우드 메시징 클라이언트 앱 설정

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 Android에서 Firebase 클라우드 메시징 클라이언트 앱 설정 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐

firebase.google.com

 

반응형
반응형

목표

android 프로젝트에 포함된 네이티브소스(c++)에서 crypto++라이브러리를 사용

 

 

환경

OS

: Windows 10

 

빌드환경(crypto++)

: MINGW32

 

빌드 툴(crypto++을 사용하는 Project)

: Android Studio 4.1

 

빌드 절차

1. crypto++ 라이브러리를 NDK로 빌드하여 동적라이브러리 생성

( Android Studio를 이용하여 함께 빌드하도록 구성하는 방법도 존재하겠으나, 성공하지 못했고

매번 빌드하는것보다는 미리 빌드해놓고 링크하는 것이 효율적이므로, 별도로 빌드하는 방식을 채택)

 

2. 빌드된 동적라이브러리(.so)파일을 링크하여 사용하도록 android.mk 구성

 

 

여기서 기술하는 내용은 아래의 github 프로젝트를 기반으로 하였으며,

 

noloader/cryptopp-android-mk

Android.mk build files for Crypto++ project . Contribute to noloader/cryptopp-android-mk development by creating an account on GitHub.

github.com

추가로 설정해야 하는 환경변수관련 내용과

Window 에서 빌드 할때만 발생할 것으로 추정되는 문제

그리고, Android Project에서 빌드된 동적라이브러리(libcrypto++.so)를 링크하는 방법을

추가 하였다.

( NDK 빌드를 포함하는 Android Project 경험이 있는 상황을 가정하며

사용되는 툴에 대한 설명이나, NDK 를 사용하는 프로젝트 구성등은 다루지 않는다. )

 

 

1. crypto++ 라이브러리를 NDK로 빌드로 빌드하여 동적라이브러리 생성

1) Crypto++ 소스파일 다운로드

https://github.com/weidai11/cryptopp 프로젝트 소스를 다운로드 받아 압축을 해제한다.

* 이후의 과정은 Shell을 통해 실행한다.

 

2) 빌드 스크립트 복사

(다운받은 압축파일에는 cryptopp 소스과 [TestScripts]내에 crypto++을 빌드 할수 있는 스크립트가 존재한다.

여러 가지 빌드 스크립트 중 cryptest-android-mk.sh 를 사용한다.)

(1) 소스 폴더 내로 이동한 후 다음 명령어를 통해 [TestScripts]폴더의 cryptest-android-mk.sh을 소스파일이 있는 폴더로 복사한다.

cp -p TestScripts/cryptest-android-mk.sh .

 

3) 환경변수 설정

다음의 명령어를 통해 빌드 스크립트를 실행하면 되지만

$bash cryptest-android-mk.sh

아마도 다음과 같은 에러가 발생할 확률이 높다.

ERROR: ANDROID_NDK_ROOT is not a valid path for . Please set it
ANDROID_NDK_ROOTis ''
ERROR: ANDROID_SDK_ROOT is not a valid path for . Please set it
ANDROID_SDK_ROOT is ''

빌드에 필요한 Android ndk와 sdk 경로가 설정되지 않을 경우 발생하는 에러로

 

a. 환경변수를 등록하거나

b. shell 실행파라미터로 설정해주면 되지만

 

설명이 길어지니 cryptest-android-mk.sh 파일을 직접 수정하도록 한다.

cryptest-android-mk.sh 을 편집기로 오픈한 후, 다음과 1행과 2행을 추가해 주면 된다.

ANDROID_NDK_ROOT="/c/Users/xxxx/AppData/Local/Android/Sdk/ndk/20.1.5948944"
ANDROID_SDK_ROOT="/c/Users/xxxx/AppData/Local/Android/Sdk"

# Error checking
if [ ! -d "${ANDROID_NDK_ROOT}" ]; then
    echo "ERROR: ANDROID_NDK_ROOT is not a valid path for ${USER}. Please set it."
    echo "ANDROID_NDK_ROOT is '${ANDROID_NDK_ROOT}'"
    exit 1
fi

# Error checking
if [ ! -d "${ANDROID_SDK_ROOT}" ]; then
    echo "ERROR: ANDROID_SDK_ROOT is not a valid path for ${USER}. Please set it."
    echo "ANDROID_SDK_ROOT is '${ANDROID_SDK_ROOT}'"
    exit 1
fi

 

* 기술해야 하는 경로는 Android Studio의 [File>Project Structure...] 메뉴를 선택 후, SDK Location에서 확인 할 수 있다.

 

4) (아마도 windows에서 발생하는) ndk-build 관련 에러

위의 설정을 마친 후 빌드 커맨드를 실행하면 ndk-build관련 에러가 발생한다.

ERROR: ndk-build is not on-path for . Please set it

cryptest-android-mk.sh 을 편집기로 오픈한 후, ndk-build 라고 기술된 부분을 ndk-build.cmd 로 변경해 준다.

( 총 3군데가 존재한다)

# Error checking
if [ -z "$(command -v ndk-build.cmd 2>/dev/null)"  ]; then
    echo "ERROR: ndk-build is not on-path for ${USER}. Please set it."
    echo "PATH is '${PATH}'"
    exit 1
fi

 

# Clean all past artifacts
ndk-build.cmd APP_ABI=all NDK_PROJECT_PATH="${NDK_PROJECT_PATH}" NDK_APPLICATION_MK="${NDK_APPLICATION_MK}" distclean &>/dev/null

 

if ndk-build.cmd -j "${MAKE_JOBS}" APP_ABI="${platform}" NDK_PROJECT_PATH="${NDK_PROJECT_PATH}" NDK_APPLICATION_MK="${NDK_APPLICATION_MK}" V=1;

(제공되는 빌드 스크립트는 linux를 기준으로 작성되어있고, linux는 ndk-build, windows는 ndk-build.cmd 가 아닌가 추정해 본다)

 

5) 빌드

이제 빌드 스크립트를 실행하여 crypto++라이브러리를 빌드한다.

$bash cryptest-android-mk.sh

정상적이라면 빌드에 사용될 Android.mk와 Application.mk를 다운받은 후, 빌드가 진행된다.

추가로 다운로드 되는 hxx,cxx은 라이브러리 테스트를 위한 소스로 크게 신경쓰지 않아도 된다.

기본적으로 4개의 플랫폼용 라이브러리를 생성하며 문제없이 생성이 완료되면 아래와 같이 표시된다.

빌드시 시간이 꽤 소요되는데, 필요한 플랫폼만 빌드하려면 sh파일의 아래 부분에 필요한 플랫폼만 기술하면 된다.

PLATFORMS=(armeabi-v7a arm64-v8a x86 x86_64)

 

6)라이브러리 확인

빌드를 실행한 폴더의 [libs] 폴더에 각 플랫폼별 바이너리(*.so)가 생성된다.

각각의 폴더 내에는 아래와 같은 4개의 파일이 생성되는데

실제 crypto++ 라이브러리 구동에 필요한 파일은

 

libcryptopp_shared.so 와 

libc++_shared.so 파일이다

(나머지는 라이브러리 테스트를 위한 파일이므로 무시한다)

 

libcryptopp_shared.so 는 crypto++의 구현을 포함하고 있는 라이브러리 이며

libc++_shared.so 는 C++ 표준 라이브러리 이다.

 

libcryptopp_shared.so 이 정상적으로 로드되기 위해서는 libc++_shared.so이 필수적으로 필요한데

이에 대해서는 아래에서 다시 설명한다.

 

2) 빌드된 동적라이브러리(.so)파일을 링크하여 사용하도록 android.mk 구성

1) 폴더 구성

이제 libcryptopp_shared.so를 링크하기 위해 빌드된 libcryptopp_shared.so 파일을 android 프로젝트에 복사해야 한다.

기존의 Native code(c++) 파일들이 [Project Root]/app/src/main/jni]폴더 에 존재하고

crypto++ 관련 headr파일은 [jni/include/cryptopp]에

crypto++ 관련 라이브러리는 [jni/libs/cryptopp]에 위치하도록 구성하는 것을 가정한다.

 

2) 파일 복사

빌드에 사용된 crypto++ 소스중 헤더파일(h)을  [jni/include/cryptopp] 폴더에 복사하고

빌드된 crypto++ 라이브러리를 [jni/libs/cryptopp]에 복사한다.

( 지원하고자 하는 플랫폼(ABI)에 해당하는 폴더를 복사하면 되며, so 파일 중 libcryptopp_shared.so 만 복사한다. 

libc++_shared.so 파일은 Android.mk구성을 통해 NDK의 표준 파일을 사용하도록 구성한다.)

아래와 같은 형태로 구성되면 되겠다.

jni

 - libs

      - cryptopp

           - arm64-v8a / libcryptopp_shared.so                 

           - armeabi-v7a / libcryptopp_shared.so

           - x86 / libcryptopp_shared.so

           - x86_64 / libcryptopp_shared.so

 

3) crypto++ 링크를 위한 Android.mk 구성

이제 기존의 모듈이 crypto++ 파일을 포함하도록 android.mk를 구성하면 된다.

foo.cpp 소스가 crypto++ 라이브러리를 사용한다 가정할때 아래와 같은 구성으로 cryptopp를 링크 할 수 있다.

OCAL_C_INCLUDES += $(LOCAL_PATH)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/cryptopp
LOCAL_MODULE    := foo
LOCAL_SRC_FILES := foo.cpp
LOCAL_LDLIBS += -llog -ldl
LOCAL_SHARED_LIBRARIES += cryptopp
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_PATH = $(ROOT_PATH)
LOCAL_MODULE := cryptopp
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/cryptopp/$(TARGET_ARCH_ABI)/libcryptopp_shared.so
LOCAL_SHARED_LIBRARIES += c++_shared
include $(PREBUILT_SHARED_LIBRARY)

 

* 라인별로 추가 설명을 해보자면 대략 다음과 같다.

LOCAL_C_INCLUDES += $(LOCAL_PATH)/include/cryptopp

- crypto++ 헤더 참조를 위해 include 경로를 설정한다.

 

LOCAL_SHARED_LIBRARIES += cryptopp

- foo 모듈에서 cryptopp 라이브러리를 참조함을 표시한다.

 

LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/cryptopp/$(TARGET_ARCH_ABI)/libcryptopp_shared.so

- cryptopp모듈의 소스가 지정된 경로의 에서 libcryptopp_shared.so 파일임을 나타낸다.

- $(LOCAL_PATH)/libs/cryptopp/$(TARGET_ARCH_ABI) 는  [jni/libs/cryptopp] 폴더내의 각 플랫폼별 폴더를 지칭한다.

 

LOCAL_SHARED_LIBRARIES += c++_shared

- cryptopp 모듈이 c++_shared(즉 libc++_shared)를 링크함을 나타낸다.

- libcryptopp_shared.so 시 생성된 libc++_shared.so를 지정하여 기술해도 되지만 이경우 중복참조 warnnig이 발생한다.

- c++_shared 로 지정하여 NDK 기본 라이브러리를 사용하도록 설정한다.(결국은 같은 파일이긴 하다)

 

include $(PREBUILT_SHARED_LIBRARY)

- 이미 빌드된 라이브러리(*.so)를 포함하는 것을 나타낸다.

 

플래폼에 따라 오픈소스를 빌드하는건 항상 시행착오와 시간 투자를 요구하는거 같다. ㅡㅡ^

 

반응형
반응형

변수

Type    
val value  like const
var variable  
    var name: String = "tom"
    var age: Int = 18

    var nameVar = "tom"
    var age = 18

함수

fun sum(num1:Int, num2:Int) : Int {
	return num1+num2
}

fun sum1(num1:Int, num2:Int) : Int = num1 + num2

fun sum3(num1:Int, num2:Int) = num1 + num2

fun add10(num1:Int, num2:Int = 10) = num1 + num2

 

클래스

class Person{
    public var name: String = ""
    protected var age: String = ""
    private var phone: String = ""
    internal var address: String = ""
}

Constructor

Primary constructor(주 생성자)

class Person() {
    public var name: String = ""
    protected var age: String = ""
    private var phone: String = ""
    internal var address: String = ""
}


class Person() {
    public var name: String = ""
}

class Person(_name: String) {
    var name: String = _name
}

class Person(var name: String = "") {
}

Secondary constructor(부 생성자)

* 주 생성자 없음

class Person {
    var name: String = ""
    var age: Int  = 18
    
    constructor(_name: String){
        this.name = _name
    }
    
    constructor(_name: String, _age: Int){
        this.name = _name
        this.age = _age
    }
}

* 주 생성자 + 부 생성자 ( 부생성자는 반드시 주생성자를 재호출 해야 한다 --> 주생성자는 반드시 호출 되어야 한다.)

class Person(var _name: String) {
    var name: String = ""
    var age: Int  = 18

    constructor(_name: String, _age: Int) : this(_name){
        this.age = _age
    }
}

* 인자에 대한 디폴트 값을 전달받기 위해 부 생성자를 여러개 만들지 않아도, 디폴트 값을 활용하여 기능을 제공할 수 있다.

class Person(var _name: String, var age: Int = 18, var sex: String = "male" ) 

 

Initializer Block(초기화 블록)

* 초기화 블록은 주 생성자 호출 직후 실행되며, 부 생성자 보다 먼저 실행된다.

* 초기화 블록은 주 생성자가 없더라도, 부 생성자 보다 먼저 실행된다.

class Person{
    var _name: String = ""
    init {
        println("initial block")
    }
}

 

상속

반응형
반응형

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의 경로를 적어 줍니다.

C:\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의 경로를 적어 줍니다.

C:\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 디버깅이 가능합니다.

 

반응형

+ Recent posts