Android

[Android/NDK] 기존 프로젝트에 NDK 설정하는 방법

Question영 2019. 12. 20. 11:28
반응형

NDK 프로젝트를 매번 진행할때마다 설정을 구글에 검색하고 있는 저를 보며

 

이번 기회에 정리해야겠다 벼루고 있었는데 게을러서 미루기를 수차례

 

드디어 칼을 뽑아 정리합니다.

 

기존 프로젝트에 NDK 를 설정하는 방법을 정리한다고 했으나

 

내용은 프로젝트 생성부터(Java) 설정으로 정리하려고 노력했습니다.

 

그리고 정리하다 보니 Mac 과 Window 가 환경이 달라 경로 설정도 다른것을 확인하여

 

경로는 각각 정리하였습니다.

 

전반적인 내용은 빌드되는 운영체제 상관없이 같으니 참고해서 봐주세요.

 

.so 파일만 가지고 연결하는 법은 아래에 포스팅에 설명 되어 있습니다.

 

[Android/NDK] 기존 프로젝트에 .so 파일 연결하는 법

 

[Android/NDK] 기존 프로젝트에 .so 파일 연결하는 법

.so 파일은 NDK 의 C/C++ 파일이 컴파일된 파일입니다. NDK 를 사용하기 위해서는 그냥 JNI 로 구성된 C/C++ 파일이 있으면 됩니다. [Android/NDK] 기존 프로젝트에 NDK 설정하는 방법 [Android/NDK] 기존 프로젝..

question0.tistory.com

 

목적

 

NDK 프로젝트를 사용하는 목적은 여러가지가 있으나

 

제가 경험한 사항은 주로 라이브러리를 구성할때 사용되었습니다.

 

경험 사례를 기초로 목적을 정리해보면 다음과 같습니다.

 

  1. 빌드되는 .so 확장자 파일에 내용을 구성해서 보안을 높이기 위함
  2. C/C++ 로 구성하여 성능 향상 위함
  3. 고성능의 그래픽 혹은 OpenCV, 머신러닝등의 라이브러리와 연결하기 위함

 

경험한 사례의 공통적인 사항은

 

C/C++ 로 프로젝트를 전체적으로 구성하는 사항이 아닌

 

부분적으로 필요한 기능만 구현했기 때문에

 

기존 프로젝트에 NDK 환경을 연결하는 경우가 많았습니다.

 

용어

 

생소할수 있는 용어를 간단하게 정리하면 다음과 같습니다.

 

JNI

자바 네이티브 인터페이스(Java Native Interface, JNI)는 
자바 가상 머신(JVM)위에서 실행되고 있는 자바코드가
네이티브 응용 프로그램(하드웨어와 운영 체제 플랫폼에 종속된 프로그램들) 
그리고 C, C++ 그리고 어샘블리 같은 다른 언어들로 작성된 라이브러리들을 호출하거나 
반대로 호출되는 것을 가능하게 하는 프로그래밍 프레임워크이다.
- 위키백과

 

NDK

NDK(Native Development Kit)는 
Android에서 C 및 C++ 코드를 사용할 수 있게 해주는 일련의 도구 모음 
- Android 공식홈페이지

 

.so

so(Shared Objec)는 C/C++ 컴파일 후 빌드를 하기 위한 파일
Run Time에 Linking 이 되며 .a 파일은 자체적으로 실행 할 수 있으나
.so 파일은 자체적으로 실행을 할 수 없고 외부와 Linking 되는 환경이 있어야 실행 가능

 

과정

 

간단하게 요약해보면 다음과 같습니다.

 

  1. NDK 관련 파일 설치
  2. 경로 설정
  3. 폴더 구성
  4. Java 와 NDK 연결을 위한 설정 및 .h 파일 구성
  5. .so 파일 생성 및 .c, .cpp 파일 빌드를 위한 구성
  6. 빌드 후 실행

그리고 이 과정 중 4번과 6번 과정은 cmd 명령어로 대체 할 수도 있습니다.

 

우선 AndroidStudio IDE 설정을 먼저 소개하고 cmd 명령을 별도로 다루겠습니다.

 

프로젝트 시작

 

 

포스팅 주제가 기존 프로젝트에 NDK 를 설정하는 방법이기 때문에

 

예제 프로젝트는 Java 나 Kotlin 프로젝트 생성했던 그대로 동일하게 생성합니다.

 

 

생성을 다했다면 왼쪽 File Explorer 의 상태바를 기본 설정인 Android 에서 Project 로 변경해줍니다.

 

 

설치

 

AndroidStudio 에서 NDK 를 사용하기 위해서는 SDK Manager 를 통해 관련 파일들을 설치해줘야 합니다.

 

LLDB, NDK, CMake 를 선택하여 설치합니다.

 

폴더 구성

 

NDK 를 빌드하기 위해서는 .c, .cpp 확장자를 갖고 있는 C/C++ 파일이 필요합니다.

 

해당 파일들을 별도로 모아두고 빌드를 위한 컴파일을 하기 위해 jni 라는 폴더를 생성합니다.

 

생성 방법은 두가지가 있습니다.

 

디렉토리로 생성하는 방법

 

 

보통 src/main/jni경로에 폴더 형태로 위치하기 때문에 메뉴를 통해 보통 디렉토리 생성 방법으로 진행하는 방법입니다.

 

Folder 메뉴를 통해 생성하는 방법

 

Android 에서는 JNI 를 위한 폴더를 생성해주는 메뉴를 제공하고 있습니다.

 

 

기본으로 설정된 경로는 src/main/jni 이기 때문에 바로 Finish 를 누르시면

 

해당 경로에 폴더가 생성된 것을 보실수 있습니다.

 

 

다른 경로로 폴더를 설정하고 싶으면 change folder location 체크 박스를 체크하고 설정하면 됩니다.

 

환경설정

 

NDK 를 빌드하고 .so 파일을 생성하기 위한 방법은 두가지가 있습니다.

 

  1. 환경 설정은 IDE 에 빌드 설정을 저장하여 사용하는 방법
  2. CMD 명령을 통한 방법

 

이 중 1번 방법은 한번 설정하면 이후 간단하게 빌드 및 생성이 가능하기 때문에

 

2번과 비교했을때 더 좋은 방법이라고 개인적으로 생각하고 있습니다.

 

Window 와 Mac OS 에 따라 설정되는 빌드 경로만 다르고 설정하는 방법은 같습니다.

 

NDK 설정

 

Mac 에서 NDK 설정
Window 에서 NDK 설정

 

File > Project Structure > SDK Location 에서 NDK 경로를 설정합니다.

 

External Tools 설정

 

Mac 에서 External Tool 위치

 

상단 Android Studio > Preferences > External Tools 검색

 

 

File > Settings > External Tools 검색

 

External Tools 에서 NDK 를 위해 3가지 빌드 설정을 합니다.

 

// Mac 설정
Name : javah
Description : javah (안써도 됨)
Programe : /usr/bin/javah
Arguments : -v -jni -d $ModuleFileDir$/src/main/jni $FileClass$
Working directory : $SourcepathEntry$
// Windows 설정
Name : javah
Description : javah (안써도 됨)
Programe : C:₩Program Files₩Java₩[jdk 버전 폴더]₩bin₩javah.exe
Arguments : -classpath "$Classpath$" -v -jni $FileClass$
Working directory : $ProjectFileDir$₩app₩src₩main₩jni

 

javah 는 Android 와 C 언어간 연결을 위한 인터페이스 역할의 .h 파일을 생성해주는 역할을 합니다.

 

// Mac 설정
Name : NDK-build
Description : NDK build (안써도 됨)
Programe : /Users/[ 사용자이름]/Library/Android/sdk/ndk/[ndk 버전]/ndk-build
Working directory : $ProjectFileDir$/app/src/main/jni
// Windows 설정
Name : NDK-build
Description : NDK build (안써도 됨)
Programe : C:₩Users₩[사용자이름]₩AppData₩Local₩Android₩Sdk₩ndk₩[ndk버전]₩ndk-build.cmd
Working directory : $ProjectFileDir$₩app₩src₩main

 

NDK build 설정은 C/C++ 파일을 컴파일 하여 .so 파일을 생성하는 역할을 합니다.

 

// Mac 설정
Name : NDK-build clean
Description : NDK build clean (안써도 됨)
Programe : /Users/[ 사용자이름]/Library/Android/sdk/ndk/[ndk 버전]/ndk-build
Working directory : $ProjectFileDir$/app/src/main/jni
// Windows 설정
Name : NDK-build clean
Description : NDK build clean (안써도 됨)
Programe : C:₩Users₩[사용자이름]₩AppData₩Local₩Android₩Sdk₩ndk₩[ndk버전]₩ndk-build.cmd
Working directory : $ProjectFileDir$₩app₩src₩main

 

NDK build clean 은 기존 컴파일한 파일들을 초기화 하는 역할을 합니다.

 

 

NDK 빌드를 위한 IDE 의 환경 설정이 완료 되었습니다.

 

.h 파일 생성

 

C/C++ 파일과 Java 를 연결을 위한 JNI 헤더 파일인 .h 를 생성해봅시다.

 

static {
  System.loadLibrary("native-lib");
}

 

우선 Activity 에 위와 같이 입력한 후 .h 파일을 생성합니다.

 

.h 파일은 C/C++ 로 구성되어 있는 헤더 파일입니다.

 

이 파일을 C/C++ 에 대한 지식이 없어도 자동으로 만들어주는 방법들이 두가지 있습니다.

 

command 명령어를 이용하여 생성하는 방법

 

헤더 생성 Native 소스를 위한 header 파일 생성하기 위해

 

command창을 열어 java 의 bin 폴더의 javah 를 이용하여 cmd 명령어를 실행합니다.

 

~\bin>javah -classpath ./classes/ -jni com.hc.ndkexample.MainActivity

 

추후에 있을 NDK Build 를 위해 bin 폴더에 생성된 *.h 파일을 jni 폴더로 이동시킵니다.

 

IDE 에 설정한 정보를 이용하는 방법

 

 

환경 설정 단계에서 설정을 했다면 위와 같은 메뉴가 생긴것을 볼수 있을 것입니다.

 

 

여기서 javah 라는 항목을 클릭해주면 자동으로 jni 폴더 안에 .h 파일이 생성된 것을 볼수 있습니다.

 

Sample 코드를 위한 구성

 

연결이 됬는지 보기 위해 Activity 에 다음과 같은 코드를 추가적으로 입력했습니다.

 

public native int helloNDK(int v);

 

연결되는 Activity 의 정보가 변경되었으니 .h 파일의 갱신을 해줘야 합니다.

 

위에서 언급한 두가지 방법 중 하나를 선택해서 실행해줍니다.

 

 

갱신된 .h 파일을 확인해보면 처음에 생성 했을때 파일과 달리

 

Activity 에 새로 설정된 매서드와 연결을 위한 함수 정보가 생성된 것을 확인 할 수 있습니다.

 

C/C++ 파일구성

 

NDK 를 이용한 실질적인 빌드를 위한 몇가지 파일과 단계가 남았습니다.

 

우선 Android.mkApplication.mk 파일의 작성이 필요합니다.

 

 

File 생성 메뉴를 이용하여 두개의 파일을 생성합니다.

 

 

해당 파일들은 C 로 컴파일을 하기 위한 정보들을 담고 있습니다.

 

하단에 작성한 코드들은 이번 포스팅의 예시를 기준으로 작성된 설정으로

 

진행하고 계시는 프로젝트에 맞게 정보를 변경해야 하는점 참고바랍니다.

 

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := native-lib
LOCAL_SRC_FILES := HelloWorld.cpp
LOCAL_CFLAGS = -DSTDC_HEADERS


include $(BUILD_SHARED_LIBRARY)

 

 

Application.mk

#APP_OPTIM := debug
APP_ABI := all
APP_PLATFORM := android-16

APP_STL := c++_static

APP_CPPFLAGS := -frtti -fexceptions
NDK_TOOLCHAIN_VERSION := clang

APP_BUILD_SCRIPT := [폴더 경로]\[App 경로]\app\src\main\jni\Android.mk

 

여기까지 생성을 했다면 실제 NDK 빌드를 위한 C/C++ 컴파일이 필요합니다.

 

해더 파일을 생성했던것 처럼 이것도 두가지 방법이 있습니다.

 

Sample 을 위한 코드

 

방법을 소개하기 전에 Sample 을 위한 코드를 구성을 하겠습니다.

 

/src/main/jni 폴더에 HelloWorld.cpp 파일을 생성하여 다음과 같이 구성합니다.

 

#include "com_hc_ndkexample_MainActivity.h"

JNIEXPORT jint JNICALL Java_com_hc_ndkexample_MainActivity_helloNDK
  (JNIEnv * env, jobject obj, jint v) {

    jint a = v + 10;

    return a;
 }

 

command 명령어를 이용하여 생성하는 방법

 

해당 방법은 필요한 정보를 입력한 후

 

빌드에 필요한 파일을 직접 실행하는 것이기 때문에

 

별도의 정보나 환경설정이 필요없습니다.

 

터미널을 실행후 root 폴더 또는 /jni 폴더로 이동합니다.

 

# Mac
/Users/[사용자이름]/Library/Android/sdk/ndk/[ndk 버전정보]/ndk-build

# Windows
C:₩Users₩[사용자이름]₩AppData₩Local₩Android₩Sdk₩ndk₩[ndk버전]₩ndk-build.cmd

 

cygwin 빌드를 위해 ndk-build 라는 명령을 실행합니다.

 

so 파일 생성이 완료가 됩니다.

 

IDE 에 설정한 정보를 이용하는 방법

 

.h 파일 생성하는 방법과 비슷하게 미리 환경 설정하여 생성된 메뉴버튼을 사용할 것입니다.

 

 

하지만 바로 사용하면 에러가 발생됩니다.

 

설정한 정보는 빌드를 위한 실행 파일에 대한 경로 정보 만 기술한 것이기 때문입니다.

 

Android Studio IDE 를 이용하여 NDK Build 를 하기 위해서는

 

Gradle 에 컴파일 언어와 대상의 정보연결 필요합니다.

 

이 역할을 이전에 생성했던 Android.mkApplication.mk 가 하고 있습니다.

 

 

오른쪽 클릭 후 나타나는 메뉴에서 Link C++ Project with Gradle 라는 메뉴를 클릭합니다.

 

나타나는 창에서 Build System 항목을 ndk-build 로 변경하고

 

아까 생성해둔 Android.mk 파일 경로를 Project Path 정보에 설정합니다.

 

 

build.gradle(app) 정보에 NDK 정보가 자동으로 추가되었습니다.

 

이제 빌드를 해보면 위의 하단 화면처럼 정상적으로 빌드되었다는 메세지를 확인 할 수 있습니다.

 

최종 확인

 

이제 정상적으로 구성되었는지 확인만 하는 작업이 남았습니다.

 

최종적으로 Activity 와 Layout 을 다음과 같이 구성합니다.

 

package com.hc.ndkexample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    public native int helloNDK(int v);

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


        int result = helloNDK(5);

        ((TextView)findViewById(R.id.tvHello)).setText("result : " + result);

    }
}

 

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvHello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

Android 빌드하여 실행해봅시다.

 

 

경우 중앙에 result : 15 라는 숫자가 보이면서 정상적으로 NDK 가 빌드가 된 것을 확인할 수 있습니다.

반응형