1. 초기 설정
안드로이드 스튜디오 Tools -> SDK Manager -> SDK Tools 탭 -> NDK 체크 하고 설치한다.
File -> Project Strucutre -> SDK Location -> NDK Location 설정이 되어야 하는데
local.properties 파일에서 ndk.dir을 입력해주면 자동으로 설정된다.
2. 자바에서 생성한 JNI를 연결
아래에서 생성할 jni 라이브러리 명을 System.loadLibrary( ) 파라메터로 넣는다.
jni를 사용하여 주고받을 함수를 정의를 한다.
public static native <리턴타입 > 함수 형식으로 정의한다.
package com.example.init;
public class GLESNativeLib {
static {
try {
System.loadLibrary("glNative");
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n" + e);
System.exit(1);
}
}
public int aaa() { return 0;}
public int k = 50;
/**
* @param width the current opengl view window width
* @param height the current opengl view window view height
*/
public static native void init( String apkFilePath );
public static native void resize(int width, int height );
public static native void step();
}
3. JNI Native 코드 생성
JNI 폴더위치상 탐색이 안되므로
뷰 방식으로 android ->Project 로 변경하자.
JNI 폴더 생성은 마우스 우클릭 NEW -> Folder -> JNI 폴더
main 폴더 내부에 jni 폴더가 생성된다.
네티티브 코드를 빌드를 하는 방식은 Andorid.mk 방식이 있고, Cmake 방식이 있습니다.
Cmake 방식의 경우에는 new project시 안드로이드 native 샘플 코드를 참조하시면 됩니다.
우리는 Android.mk 방식으로 진행 합니다.
먼저 프로젝트 뷰를 Andoid -> Project 로 바꿉니다.
src/main/jni 폴더를 생성하고 아래 코드를 넣습니다.
> Cpp 코드 작성
c++ 함수와 헤더를 쉽게 만들기 위해서 javac.h를 사용하면 된다.
javac < Java File > -h < 생성될 Path >
Java File은 native 함수가 정의되어 있는 자바 파일, 필자는 GLESNativeLib 파일을 입력으로 넣어준다.
생성된 파일은 아래와 같다.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_init_GLESNativeLib */
#ifndef _Included_com_example_init_GLESNativeLib
#define _Included_com_example_init_GLESNativeLib
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_example_init_GLESNativeLib_init
(JNIEnv *, jclass, jstring);
JNIEXPORT void JNICALL Java_com_example_init_GLESNativeLib_resize
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
생성된 파일을 사용하여 jni cpp, header 파일을 만들어 준다.
Cpp 파일 정의 부분
#ifdef __ANDROID__
extern "C" JNIEXPORT void JNICALL Java_com_example_init_GLESNativeLib_init( JNIEnv *env, jobject obj, jstring FilePath )
{
}
extern "C" JNIEXPORT void JNICALL Java_com_example_init_GLESNativeLib_resize( JNIEnv *env, jobject obj, jint width, jint height)
{
}
#endif
헤더 파일
#include <jni.h>
#include <android/log.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
/* Header for class com_example_init_GLESNativeLib */
#ifndef _Included_com_example_init_GLESNativeLib
#define _Included_com_example_init_GLESNativeLib
extern "C" {
JNIEXPORT void JNICALL Java_com_example_init_GLESNativeLib_init(JNIEnv *, jobject, jstring);
JNIEXPORT void JNICALL Java_com_example_init_GLESNativeLib_resize(JNIEnv *, jobject, jint, jint);
JNIEXPORT void JNICALL Java_com_example_init_GLESNativeLib_step(JNIEnv *, jobject);
}
#endif
생성된 코드는 함수의 두번째 인자 타입이 jclass로 되어있는데, jobject로 바꾸어준다.
함수명 해석 방법.
- 빨간 영역은 고정이다.
- <리턴 타입> JAVA_ < Package Name > _ < 해당 Jni를 사용하 Class Name > _ 함수이름
extern "C" 는 외부에서 사용한다는 의미이기 때문에 반드시 써준다.
파라메터 해석 방법
첫 번째, 두 번째 인수 JNIEnv, jobject는 모든 JNI 함수에 생기며,
세 번째 인수부터 우리가 전달하고자 하는 인수이다.
세 번째 이후부터는 전달하고자 하는 변수 앞에 j 를 붙여서 보낸다.
첫 번째 인수 JNIEnv는 타입 변환할때 사용한다.
대표 적으로 java string을 c 기반 문자열 배열로 바꾸어주는 역할을 한다.
두 번째 인수는 첫 번째 인수와 함께 자바쪽 콜백함수를 만들어줄때 사용한다.
첫 번째 인수와 두번째 인수를 가지고 주로 많이 사용하는 기능 2가지를 살펴본다.
첫 번째는 jstring -> char *로 바꾸는 코드이며,
두 번째는 네이티브에서 자바쪽 콜백 메서드를 실행하는 기능이다.
package com.example.init;
import android.util.Log;
public class GLESNativeLib {
static {
try {
System.loadLibrary("glNative");
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n" + e);
System.exit(1);
}
}
public static void onCallBack() {
Log.d("GLESNativeLib", "onCallbacak");
}
/**
* @param width the current opengl view window width
* @param height the current opengl view window view height
*/
public static native void init( String apkFilePath );
public static native void resize(int width, int height );
public static native void step();
public static native String dlgmlals3(String str);
}
string을 변환하는 코드는 GetStringUTFChars 함수를 사용하면 된다.
콜백 함수의 관련해서는.
클래스를 find 해서 가져오는 방법도 있지만, 두번째 인자로 클래스가 넘어오는 것을 활용해서
호출한 클래스의 콜백함수를 호출해 볼 것이다.
GLESNativeLib 클래스를 객체를 만들지 않고, 정적 메서드로 실행하기 때문에
아래 JNI 함수의 두번째 파라메터가 jobject 가 아니고 jclass 이다.
인스턴스 메서드가 아니므로, 콜백함수도 정적 메서드로 선언해야 한다. (onCallback)
CallVoidMethod는 콜백함수가 인스턴스 메서드일때 사용하고,
정적 메서드일 경우, CallStaticVoidMethod를 사용한다.
GetStaticMethodID의 세번째 메서드 "()V" 는
괄호 안은 input parameter를 의미하여, V는 리턴타입이 없다는 것을 의미한다.
함수 원형의 따라 달라지는데. 이에 관련해서는 아래 예제를 참고해보자.
https://gist.github.com/bitsnaps/3cfdcf92f6dfef952fbfc5f50c4713b6
extern "C" JNIEXPORT jstring JNICALL Java_com_example_init_GLESNativeLib_dlgmlals3(JNIEnv * env, jclass clazz, jstring dlgmlals3)
{
//GraphicsRender();
// string
const char* w_buf = env->GetStringUTFChars(dlgmlals3, 0);
__android_log_print(ANDROID_LOG_INFO, "TESTJNI","dlgmlals3 String: %s", w_buf);
// callback
jmethodID mCallbackB = env->GetStaticMethodID(clazz, "onCallBack", "()V"); // 정적 메서드 ID 획득
env->CallStaticVoidMethod(clazz, mCallbackB); // 정적 메서드 onCallBack 호출
if (env->ExceptionCheck()) {
env->ExceptionDescribe(); // 예외 내용 출력
env->ExceptionClear(); // 예외 클리어
}
env->ReleaseStringUTFChars(dlgmlals3, w_buf);
return dlgmlals3;
}
> Android.mk 정의하여 빌드 스크립트를 작성
Jni cpp 코드를 빌드할때 사용합니다.
빌드가 완료되면 jniCalculator.so 파일이 생성이 됩니다.
// Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jniCalculator
LOCAL_SRC_FILES := Calculator.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
> Application.mk 생성
// Application.mk
APP_ABI := armeabi-v7a
externalNativeBuild 사용하기 때문에 생략가능.
3. Graddle 구성
build.gradle.kts 파일 내부에 넣습니다.
android {
....
externalNativeBuild {
ndkBuild {
path("src/main/jni/Android.mk")
}
}
}
안드로이드 복사할때
1) /app/bundle.grale 파일을 열어서 applicationId 를 변경한다.
2) /app/src/main/res/values/strings.xml 파일을 열어서 app_name 을 변경한다.
3) setting.gradle.kts 파일에서 rootProject.name = "Chapter_3_1"
3) Build > Clean Project를 실행하여 이전 빌드 파일을 정리하고, Rebuild Project를 실행하여 다시 빌드하세요.
4) File > Invalidate Caches / Restart를 클릭하여 캐시를 정리하세요.
5) .idea 및 .gradle 폴더 삭제
'Opengles 3.0 with Android' 카테고리의 다른 글
Chapter 2.1 VBO(Vertex Buffer Object) 사용하여 정사각형 렌더링. 해본다. (0) | 2024.10.17 |
---|---|
Chapter 1.3 Touch Event 받아서 Triangle 색상 변경해보기 (0) | 2024.10.15 |
Chapter 1.2 Vertex Buffer 사용해보기 (0) | 2024.10.12 |
Chapter 1.1 Shader Build (0) | 2024.10.12 |
Chapter 1.0 EGL Context 생성하여 GLSurfaceView 에 적용하기 (0) | 2024.10.10 |