검색결과 리스트
글
안드로이드 프레임워크 프로그래밍(11) [JAVA 프레임워크와 Native 프레임워크 연결 원리]
어느덧 안드로이드 프레임워크를 연구한지 3달 정도 되어갑니다. 초반에는 온갖 삽질이 길어져서 작업 하나를 수행하는 데에도 많은 시간이 소요되었습니다만 현재 조금 프로그램의 흐름을 볼 수 있는 눈이 생겨 어느 정도 감을 잡아가는 단계가 되었다고 조심스럽게 말해봅니다.
이번 포스팅에서는 안드로이드 기기가 부팅된 후 Java와 Native 프레임워크가 JNI로 연결되는 과정에 대해 다루어 보도록 하겠습니다.
이전 포스팅에서 service의 JNI 연결과정을 다룬 바 있습니다. 이번 포스팅에서는 '/framework/base/core'에 있는 프레임워크들이 JNI를 통해 Native와 서로 연결되는 과정을 다루게 됩니다. Java Service와 Native Service의 연결 과정에 대해서는 아래 포스팅을 참조해 주시길 바랍니다.
본 포스팅은 안드로이드 카메라 시스템인 Camera.java와 android_hardware_Camera.cpp가 JNI로 함수가 연결되는 과정을 예로 설명드리겠습니다.
안드로이드 Framework는 기기의 전원이 들어온 후 부팅이 완료되었을 때 init를 통해 zygote가 실행됩니다. 이 zygote가 동작하는 과정에서 AndroidRuntime::start() 함수를 소환함으로서 안드로이드 기기의 동작을 준비하는 과정을 거칩니다. 해당 코드를 살펴보면 다음과 같습니다.
/frameworks/base/core/jni/AndroidRuntime.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void AndroidRuntime::start(const char* className, const char* options) { .... /* * Register android functions. */ if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } .... } | cs |
AndroidRuntime::start() 함수가 실행된 후 startReg()함수를 불러들이는 모습입니다. startReg() 함수를 통해 JNI가 동작되는 함수들을 실행하게 되는데 해당 함수를 자세히 살펴보도록 하겠습니다.
/frameworks/base/core/jni/AndroidRuntime.cpp
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 31 32 | /* * Register android native functions with the VM. */ /*static*/ int AndroidRuntime::startReg(JNIEnv* env) { /* * This hook causes all future threads created in this process to be * attached to the JavaVM. (This needs to go away in favor of JNI * Attach calls.) */ androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); ALOGV("--- registering native functions ---\n"); /* * Every "register" function calls one or more things that return * a local reference (e.g. FindClass). Because we haven't really * started the VM yet, they're all getting stored in the base frame * and never released. Use Push/Pop to manage the storage. */ env->PushLocalFrame(200); if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { env->PopLocalFrame(NULL); return -1; } env->PopLocalFrame(NULL); //createJavaThread("fubar", quickTest, (void*) "hello"); return 0; } | cs |
startReg() 함수 내에서 JNI를 등록하는 register_jni_procs()함수가 동작하는 것을 확인하실 수 있습니다. 그 안에 있는 인자인 gRegJNI는 JNI로 연결시킬 Register 함수들이 등록되어있는 구조체입니다. 코드를 통해 좀 더 자세히 구성요소를 확인해 보도록 하겠습니다.
/frameworks/base/core/jni/AndroidRuntime.cpp
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 31 32 33 34 35 36 37 38 39 40 41 | extern int register_android_hardware_Camera(JNIEnv *env); #ifdef NDEBUG #define REG_JNI(name) { name } struct RegJNIRec { int (*mProc)(JNIEnv*); }; #else #define REG_JNI(name) { name, #name } struct RegJNIRec { int (*mProc)(JNIEnv*); const char* mName; }; #endif typedef void (*RegJAMProc)(); static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env) { for (size_t i = 0; i < count; i++) { if (array[i].mProc(env) < 0) { #ifndef NDEBUG ALOGD("----------!!! %s failed to load\n", array[i].mName); #endif return -1; } } return 0; } static const RegJNIRec gRegJNI[] = { .... REG_JNI(register_android_hardware_Camera), .... }; | cs |
위 코드 내용을 읽어보니 상당히 흥미로운 부분을 확인할 수 있었습니다. 각 함수들을 하나씩 살펴보도록 하겠습니다.
static const RegJNIRec gRegJNI[]
구조체 RegJNIRec를 배열로 선언하고 있습니다. 배열은 초기화가 진행중인데 그 안에 있는 REG_JNI()는 코드 내에서 define 되어 있는 것을 확인하실 수 있습니다.
#define REG_JNI(name) { name }
위에서 보시면 아시듯 'REG_JNI(name)'으로 선언된 부분이 { name }으로 변경되고 있는 것을 확인하실 수 있습니다. 그래고 바로 아래에는 구조체 RegJNIRec가 선언되고 있는 것을 보실 수 있습니다. 구조체 내부에 있는 int(*mProc)(JNIEnv*)는 포인터 함수로서 extern으로 선언되어 있던 함수 register_android_hardware_Camera()를 등록하는 구조임을 알 수 있습니다.
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
위 함수를 통해 드디어 본격적으로 JNI가 동작하게 됩니다. 함수 내부의 코드를 살펴보면 다음과 같은 것을 보실 수 있습니다.
if (array[i].mProc(env) < 0)
위에서 설명한 gRegJNI[] 배열 내에 있던 구조체 RegJNIRec 내에 설정했던 함수 포인터들이 위 코드를 통해 반복문으로 모두 실행하는 것을 보실 수 있습니다. 위 과정을 실행하게 되면 프레임워크 내에 존재하는 모든 Java와 Native 함수들이 연결됩니다.
다음으로 우리들이 등록하였던 register_android_hardware_Camera()함수를 보도록 하겠습니다.
/frameworks/base/core/jni/android_hardware_Camera.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | static JNINativeMethod camMethods[] = { .... { "native_setup", "(Ljava/lang/Object;ILjava/lang/String;)V", (void*)android_hardware_Camera_native_setup }, .... }; // Get all the required offsets in java class and register native functions int register_android_hardware_Camera(JNIEnv *env) { .... // Register native functions return AndroidRuntime::registerNativeMethods(env, "android/hardware/Camera", camMethods, NELEM(camMethods)); } | cs |
위 코드를 보았을 때 JNINativeMethod 배열을 선언하는 과정을 보실 수 있는데 해당 배열 내에 선언된 값을 하나 보시면 다음과 같습니다.
위의 구조를 보았을 때 JNINativeMethod가 struct 구조체로 구성되어 있다는 사실을 어느 정도 직감하실 수 있으실 겁니다! 해당 구조체 내에는 3개의 변수를 저장할 수 있는대 내용은 다음과 같습니다.
{ Java에서 선언된 native method, Signature, C++에서 연결하고자 하는 함수 }
위와 같은 방식으로 값들을 선언해 주시면 되겠습니다. 위에서 Signature는 Java에서 선안된 함수의 인자와 return을 간단하게 나타낸 것을 말합니다. Signature에 대해 좀 더 자세히 알고 싶으신 분들은 아래 포스팅을 참조해 주시기 바랍니다.
다음으로 AndroidRuntime.cpp에서 연결했었던 register_android_hardware_Camera(JNIEnv *env)함수를 확인해 보면 리턴형으로 다시 AndroidRuntime 내의 함수를 호출하고 있는 모습을 보실 수 있습니다. 이를 다시 AndroidRuntime.cpp 에서 확인해 보도록 하겠습니다.
/frameworks/base/core/jni/AndroidRuntime.cpp
1 2 3 4 5 6 7 8 | /* * Register native methods using JNI. */ /*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { return jniRegisterNativeMethods(env, className, gMethods, numMethods); } | cs |
드디어 이번 포스팅의 핵심이라 할 수 있는 jniRegisterNativeMethods()함수가 등장하였습니다! 해당 함수가 실행되게 되면 C++과 Java와의 함수가 최종적으로 연결됩니다. 해당 함수의 인자는 다음과 같이 구성되어 있습니다.
(Java VM 환경변수, 연결하고자 하는 Class의 경로, Native와 Java 사이에 연결하고자 하는 함수들의 내역이 있는 구조체 JNINativeMethod, gMethod의 길이);
위의 과정을 통해 연결된 Java 매소드는 해당 코드를 통하여 확실하게 확인하실 수 있습니다!
/frameworks/base/core/java/android/hardware/Camera.java
1 2 3 4 5 6 7 8 9 | public class Camera { .... private native final void native_setup(Object camera_this, int cameraId, String packageName); .... } | cs |
/frameworks/base/core/jni/android_hardware_Camera.cpp
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 31 32 33 34 35 36 37 38 39 | // connect to camera service static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, jint cameraId, jstring clientPackageName) { // Convert jstring to String16 const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL); jsize rawClientNameLen = env->GetStringLength(clientPackageName); String16 clientName(rawClientName, rawClientNameLen); env->ReleaseStringChars(clientPackageName, rawClientName); sp<Camera> camera = Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID); if (camera == NULL) { jniThrowRuntimeException(env, "Fail to connect to camera service"); return; } // make sure camera hardware is alive if (camera->getStatus() != NO_ERROR) { jniThrowRuntimeException(env, "Camera initialization failed"); return; } jclass clazz = env->GetObjectClass(thiz); if (clazz == NULL) { jniThrowRuntimeException(env, "Can't find android/hardware/Camera"); return; } // We use a weak reference so the Camera object can be garbage collected. // The reference is only used as a proxy for callbacks. sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera); context->incStrong((void*)android_hardware_Camera_native_setup); camera->setListener(context); // save context in opaque field env->SetIntField(thiz, fields.context, (int)context.get()); } | cs |
'안드로이드 > 프레임워크' 카테고리의 다른 글
안드로이드 프레임워크 프로그래밍(13) [커널이미지 적용후 부팅화면만 나올 때 대처법] (0) | 2015.03.24 |
---|---|
안드로이드 프레임워크 프로그래밍(12) [IServiceManager 등록과정] (1) | 2015.03.23 |
안드로이드 Native 코드 분석 : sp<> - Smart Pointer (0) | 2015.03.02 |
SurfaceView에서 SurfaceHolder의 동작원리(Principle of SurfaceHolder in SurfaceView) (1) | 2015.02.26 |
안드로이드 프레임워크 프로그래밍(10) [Native C/C++ 코드에서 Java 호출] (0) | 2015.02.18 |