안드로이드 JNI 에 대하여...
안드로이드 JNI 에 대하여...
JNI는 JAVA에서 Native code를 사용할 수 있는 인터페이스이다. 좀 더 쉽게 말하면 C나C++로 작성한 API를 JAVA에서 호출하게 해준다.
이를 위해서 framework의 안드로이드 소스 코드 중 service를 보자. (다른 쪽도 이와 유사하게 구현되어 있다.)
/framework/base/service/jni
/framework/base/service/java/com/android/server
jni 디렉토리의 Android.mk를 보면 libandroid_servers.so를 만들게 된다. 이는 java/com/android/server/SystemServer.java에서 다음과 같이 호출하게 된다.
public static void main(String[] args) {
if (SamplingProfilerIntegration.isEnabled()) {
SamplingProfilerIntegration.start();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
SamplingProfilerIntegration.writeSnapshot("system_server");
}
}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
}
// The system server has to run all of the time, so it needs to be
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
System.loadLibrary("android_servers");
init1(args);
}
System.loadLibrary에 의해서 libandroid_servers.so가 로드되며 최초로 호출되는 것이 JNI_OnLoad() 이 다. JNI_OnLoad()는 onload.cpp에서 정의되어 있으며, JNI_OnLoad()가 호출되기 전에 JNI로 정의한 메소드가 호출되어서는 안된다. 이유는 JNI_OnLoad()에서 각각의 서비스가 사용할 수 있는 native code를 등록하기 때문이다. 등록 전에 호출되면 JAVA VM은 관련 함수를 찾을 수 없게 된다. (JNI_OnLoad()가 System.loadLibrary에 의하여 자동으로 호출되기 때문에 java 파일에서는 호출되는 곳을 찾을 수 없다)
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
LOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_KeyInputQueue(env);
register_android_server_HardwareService(env);
register_android_server_AlarmManagerService(env);
register_android_server_BatteryService(env);
register_android_server_SensorService(env);
register_android_server_FallbackCheckinService(env);
register_android_server_SystemServer(env);
return JNI_VERSION_1_4;
}
여기에서 호출한 register 함수를 살펴볼 필요가 있다. Register_android_server_HardwareService()를 본다. jniRegisterNativeMethod()에 의해서 method_table을 com/android/server/HardwareService에 등록한다는 의미이다. 실제로 com/android/server/를 보면 HardwareService.java 파일이 있다. 위의 의미가 HardwareService.java 파일에 등록한다는 의미는 아니다. HardwareService.java 파일에서 정의한 HardwareService 클래스에 등록하여 사용한다는 의미이다. 이렇게 등록되면 HardwareService 클래스에 서는 method_table에 등록된 native 함수를 호출하여 사용할 수 있다. 당연히 다른 클래스에서는 native 함수를 호출할 수 없다.
static JNINativeMethod method_table[] = {
{ "init_native", "()I", (void*)init_native },
{ "finalize_native", "(I)V", (void*)finalize_native },
{ "setLight_native", "(IIIIIII)V", (void*)setLight_native },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
{ "vibratorOff", "()V", (void*)vibratorOff }
};
int register_android_server_HardwareService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/HardwareService",
method_table, NELEM(method_table));
}
한 예로 SystemServer.cpp를 보면 SystemServer, ServerThread, DemoThread가 있다. com_android_server_SystemService.cpp 에서 정의된 gMethods[]에 새로운 native 코드 set_mtd_data를 추가하고 이를 ServerThread에서 호출하려고 하면 컴파일에서는 에러가 나지 않지만 실행 시에 에러가 난다. 이 를 해결하기 위해서는 다음과 같이 호출하여야 한다.
즉 SystemServer 클래스의 메소드 형태로만 호출이 가능하다.
만약 동일한 native 코드를 두개 이상의 독립된 클래스에서 사용하려면 당연한 얘기지만 각각에 대하여 따로 등록해야만 한다.
다시 com_android_server_HardwareService.cpp 파일의 method_table[]을 보자. 여기에서는 5개의 native 함수가 등록되어 있다. 첫번째 인자가 실제 java에서 호출되면 함수면이고, 두번째 인자는 입출력 인스턴스의 타입을 정의한다. 세번째 인자는 com_android_server_HardwareService.cpp에서 정의한 JNI 인터페 이스 함수이다.
HardwareService.java의 HardwareService 클래스에서 이를 이용하기 위해서는 native 메소드임을 선언하는 과정이 필요하다.
public class HardwareService extends IHardwareService.Stub {
……
private static native int init_native();
private static native void finalize_native(int ptr);
private static native void setLight_native(int ptr, int light, int color, int mode,
int onMS, int offMS, int brightnessMode);
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStats;
volatile VibrateThread mThread;
private int mNativePointer;
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
}
JNI에서 새로운 native 함 수를 추가했다면 java 코드의 클래스에도 반드시 메소드 정의를 추가한다.