코딩하는 빵
[Android] 안드로이드 스튜디오(NDK)에서 JNI 사용하기 본문
안드로이드 스튜디오에서 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 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/main 에 jni 폴더를 생성합니다.
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 메뉴로 들어가 아래 이미지와 같이 세팅합니다.
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
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
'코딩하는 빵 > Android' 카테고리의 다른 글
[Android] 안드로이드 스튜디오에서 페이스북 로그인 연동하기 (10) | 2016.07.22 |
---|