Notice
Recent Posts
Recent Comments
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

코딩하는 빵

[Android] 안드로이드 스튜디오(NDK)에서 JNI 사용하기 본문

코딩하는 빵/Android

[Android] 안드로이드 스튜디오(NDK)에서 JNI 사용하기

빵그레 2016. 6. 29. 17:35

안드로이드 스튜디오에서 NDK를 설치하고 JNI 예제를 실행해보자!

부제: C/C++로 함수를 만들어 안드로이드(자바코드)에서 함수를 호출하자!


NDK(Native Development Kit)란? NDK란 네이티브 코드 언어를 사용할 수 있게 해주는 툴셋이다. 간단하게 말하자면 안드로이드 프로그래밍 중 C/C++ 언어를 사용할 필요가 있다면 NDK를 설치해야 한다.


JNI(Java Native Interface)란? Java와 자바 이외의 언어들 간에 서로 호출하고 호출될 수 있도록 인터페이스를 제공한다.


본 포스팅은 MacOS 10.11.5, AndroidStudio 2.1.1 기준으로 작성되었습니다.

새로 만들거나 준비된 프로젝트가 있다고 가정하며, 프로젝트 생성에 관하여 따로 언급하지 않겠습니다.

패키지나 클래스명에는 _ 가 들어가지 않는 편이 구현하기 편합니다.

동작원리를 완벽히 깨우치고 쓰는 글이 아니며, 입문하는 과정에서 이렇게 하면 되더라 하고 기록하는 글입니다.

부족한 부분 지적해주시면 감사하겠습니다 :)


1. NDK 설치


SDK Manager - SDK Tools - NDK 항목에 체크 후 OK 버튼을 눌러 NDK를 설치합니다.


SDK Manager

SDK Manager


SDK Tools - NDK

SDK Tools - NDK


File - Project Structure - SDK Location - Android NDK Location 이 설정되어있는지 확인합니다.

설정되어 있지 않다면 본인의 sdk가 설치된 폴더에서 ndk-bundle의 위치로 설정합니다.


File - Project Structure


Project Structure - SDK Location - Android NDK Location


2. JNI 코드 작성하기


프로젝트폴더/app/src/mainjni 폴더를 생성합니다.


jni 폴더 경로


생성한 jni 폴더 내부에 마우스 우클릭 - New - C/C++ Source File 메뉴로 jniMain.cpp 파일을 생성합니다.

Create an associated header 옵션은 체크해제 하였습니다.


마우스 우클릭 - New - C/C++ Source File


jniMain.cpp


매개변수가 void 이며 string을 반환하는 함수를 만들어 보겠습니다.


  • 헤더파일 아직 생성 전이므로 추후에 쓰셔도 됩니다.

  • 패키지 경로에서 ._ 로 대치합니다.

  • 함수명은 마음대로 지어도 되지만 패키지_클래스는 반드시 삽입해주어야 합니다.

#include <헤더(패키지_클래스.h)>

JNIEXPORT 리턴타입 JNICALL 패키지_클래스_함수명(JNIEnv *env, jobject obj){

return 리턴타입;

}

#include <com_example_ppang_jniexample_MainActivity.h>

JNIEXPORT jstring JNICALL Java_com_example_ppang_jniexample_MainActivity_getJNIString(JNIEnv *env, jobject obj) {

return env->NewStringUTF("Message from jniMain");
}


3. Android.mk 파일 작성하기


jni 폴더 내에 마우스 우클릭 - New - File 메뉴를 통해 Android.mk 라는 파일을 새로 만듭니다.


마우스 우클릭 - New - File


아래 코드와 같이 Android.mk 파일을 작성하여 안드로이드에서 C/C++ 파일을 라이브러리로 사용할 수 있도록 합니다.


...

LOCAL_MODULE := 라이브러리명(마음대로 정하시면 됩니다.)

LOCAL_SRC_FILES := 함수를 작성한 c/cpp 파일명.

...


LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := jniExample
LOCAL_SRC_FILES := jniMain.cpp
LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)


4. 자바 코드 작성하기


안드로이드에서 C/C++ 함수를 호출하는 코드를 작성하겠습니다.


static{

System.loadLibrary("지정한 라이브러리명"); <- 3단계에서 만든 라이브러리명과 동일하게 써주셔야합니다.

}

public native 리턴타입 함수명();    <- 2단계에서 만든 jni 함수명과 동일하게 써주셔야합니다.

사용하고자 하는 곳에서 함수호출.


package com.example.ppang.jniexample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

static {
System.loadLibrary("jniExample");
}

public native String getJNIString();

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

TextView jniText = (TextView)findViewById(R.id.jniText);
jniText.setText(getJNIString());
}
}


추후 헤더파일을 만들기 위해 클래스 파일이 필요하므로, 작성한 코드로 빌드합니다.

실행되지 않는 것이 정상입니다.


Run이나 Make Project


5. 헤더파일 생성하기


클릭 한번으로 헤더파일을 쉽게 만들 수 있도록 툴을 만들어두겠습니다.

툴을 만들어둠으로써 다른 프로젝트에서도 쉽게 헤더파일을 생성할 수 있게 됩니다.

Preferences - Tools - External Tools - Create Tool 메뉴로 들어가 아래 이미지와 같이 세팅합니다.


Android Preferences

Preferences


Tools - External Tools - Create Tools


헤더파일 만드는 툴 세팅


방금 만든 툴을 이용해 헤더파일을 생성해보겠습니다.

4단계를 진행한 클래스(MainActivity)에서 오른쪽 우클릭 - NDK(위에서 지정한 Group) - javah(위에서 지정한 Name) 을 눌러 헤더파일을 생성합니다.


★ C/C++ 파일에서 만들었던 함수명이나 리턴타입, 매개변수 등이 변경되면 헤더파일도 다시 생성해줘야합니다.


오른쪽 우클릭 - NDK - javah


6. gradle 설정


드디어 마지막 단계입니다. gradle 설정을 마치면 실행가능한 상태가 됩니다.

gradle 설정에 관한 부분은

https://www.davidlab.net/ko/tech/using-the-android-ndk-with-android-studio-part1/

위 링크에서 도움받았습니다.

  • build.gradle (Module: app) 에 아래와 같은 설정 추가. (점선으로 감싸진 주석있는 두 영역만 본인 프로젝트에 추가하시면 됩니다.)
  • gradle.properties 에 android.useDeprecatedNdk=true 추가.
★ build.gradle 설정 후 헤더파일 없다면 빌드시 에러가 나며, 이전에 빌드해두지 않았다면 클래스파일이 없어 헤더파일을 만들 수 없는 딜레마에 빠지게 됩니다. 즉 gradle 설정 전 헤더파일을 만들어두도록 합시다.


build.gradle

import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'



// Project Structure에서 설정한 NDK 경로를 읽어들여 Return합니다.
def getNdkBuildPath() {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())

def command = properties.getProperty('ndk.dir')
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
command += "\\ndk-build.cmd"
} else {
command += "/ndk-build"
}

return command
}



android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

defaultConfig {
applicationId "com.example.ppang.jniexample"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}



sourceSets.main {
// Compile된 Native Library가 위치하는 경로를 설정합니다.
jniLibs.srcDir 'src/main/libs'

// 여기에 JNI Source 경로를 설정하면 Android Studio에서 기본적으로 지원하는 Native
// Library Build가 이루어집니다. 이 경우에 Android.mk와 Application.mk를
// 자동으로 생성하기 때문에 편리하지만, 세부 설정이 어렵기 때문에 JNI Source의
// 경로를 지정하지 않습니다.
jni.srcDirs = []
}

ext {
// 아직은 Task 내에서 Build Type을 구분할 방법이 없기 때문에 이 Property를
// 이용해 Native Library를 Debugging 가능하도록 Build할 지 결정합니다.
nativeDebuggable = true
}

// NDK의 ndk-build 명령을 이용하여 Native Library를 Build하기 위한 Task를 정의합니다.
//noinspection GroovyAssignabilityCheck
task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
if (nativeDebuggable) {
commandLine getNdkBuildPath(), 'NDK_DEBUG=1', '-C', file('src/main').absolutePath
} else {
commandLine getNdkBuildPath(), '-C', file('src/main').absolutePath
}
}

// App의 Java Code를 Compile할 때 buildNative Task를 실행하여 Native Library도 같이
// Build되도록 설정합니다.
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn buildNative
}

// NDK로 생성된 Native Library와 Object를 삭제하기 위한 Task를 정의합니다.
//noinspection GroovyAssignabilityCheck
task cleanNative(type: Exec, description: 'Clean native objs and lib') {
commandLine getNdkBuildPath(), '-C', file('src/main').absolutePath, 'clean'
}

// Gradle의 clean Task를 실행할 떄, cleanNative Task를 실행하도록 설정합니다.
clean.dependsOn 'cleanNative'


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

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}


gradle.properties

# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useDeprecatedNdk=true


7. 실행


여지껏 작성한 프로젝트를 실행하면 화면에서 다음과 같은 글을 볼 수 있습니다.


★ 실행 전에 cpp 파일에 헤더 추가해주는 것 잊지맙시다!


실행결과





gitHub: https://github.com/yucaroll/jniExample

Comments