Java JNI 코드 분석 : GetObjectClass()

안드로이드/프레임워크 2015. 4. 29. 00:12

 안드로이드 프레임워크 프로그래밍을 진행하시다 보면 JNI 부분이 생각보다 많은 지식이 필요함을 느끼고 있습니다. 오늘 분석하게 되는 코드 또한 안드로이드는 물론이고 Java 에서 JNI를 사용할 때 자주 사용하는 함수들인데요. 오늘부터 각 코드들에 대한 설명을 적어내려가볼까 생각합니다.


jclass    (*env)->GetObjectClass(env, jobject);
jclass    env->GetObjectClass(jobject);

 윗부분운 C로 JNI를 코딩하실 때, 아래부분은 C++로 JNI를 코딩하실 때 사용하는 함수입니다. 이 함수는 jobject 변수로부터 해당 변수의 jclass 값을 얻기 위해 사용합니다.

 이게 무슨 말이냐 하면, jobject는 java 클래스 객체 자체라고 이해하시면 되겠습니다. 그리고 jclass는 해당 java 클래스의 경로를 저장한다는 역할을 하고 있다는 식으로 이해하시면 감이 오실 것이라 생각합니다. 위의 함수의 결과값인 return은 아래의 함수를 실행할 때에도 같은 값을 나타냅니다.


jclass    (*env)->FindClass(env, "Java 클래스의 경로명");


 위와 같이 해당 Java 클래스의 경로명을 직접 적어주는 방식으로로도 jclass 값을 return 받으실 수 있습니다.


아래는 위의 소스코드를 적용한 예제입니다. 해당 예제에 대한 자세한 사항은 아래 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/71


hello.c

1
2
3
4
5
6
7
8
9
10
11
#include <string.h>
#include <jni.h>
#include <stdio.h>
 
void Java_com_example_calljava_MainActivity_callJava(JNIEnv* env, jobject thiz){
    //jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
    jclass jCallJava = (*env)->GetObjectClass(env, thiz);
 
    jmethodID testToast = (*env)->GetStaticMethodID(env, jCallJava, "testToast""()V");
    (*env)->CallStaticVoidMethod(env, jCallJava, testToast);
}
cs


 결론적으로 말해서


jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
jclass jCallJava = (*env)->GetObjectClass(env, thiz);


이 두 함수의 return 값이 같다는 의미로 기억해 주시면 되겠습니다!!


300x250

안드로이드 프레임워크 프로그래밍(20) [JNI를 통해 Native에서 JAVA 변수 및 클래스 객체 다루기]

안드로이드/프레임워크 2015. 4. 28. 13:19

 안드로이드 Native 단계의 Framework에서는 종종 Java 단계에서 사용되는 Framework의 값들을 사용해야 할 때가 종종 있습니다. 이를 위해 Java 단계에서 native 함수 호출을 통해 Native에 값들을 인자값을 통해 전달하는 방법이 있습니다만 Native 단계에서 실행중인 프로세스가 Java로부터 데이터를 받아올 수 있는 방법은 없을까요?

 이러한 의문은 아래의 Native 소스코드를 통해 어떤 방식으로 진행되는지 대략적인 감을 잡으실 수 있을겁니다.


/frameworks/base/core/jni/android_view_Surface.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
int register_android_view_Surface(JNIEnv* env)
{
    int err = AndroidRuntime::registerNativeMethods(env, "android/view/Surface",
            gSurfaceMethods, NELEM(gSurfaceMethods));
 
    jclass clazz = env->FindClass("android/view/Surface");
    gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
    gSurfaceClassInfo.mNativeObject =
            env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeObject""I");
    gSurfaceClassInfo.mLock =
            env->GetFieldID(gSurfaceClassInfo.clazz, "mLock""Ljava/lang/Object;");
    gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "<init>""(I)V");
 
    clazz = env->FindClass("android/graphics/Canvas");
    gCanvasClassInfo.mFinalizer = env->GetFieldID(clazz, "mFinalizer""Landroid/graphics/Canvas$CanvasFinalizer;");
    gCanvasClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas""I");
    gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat""I");
 
    clazz = env->FindClass("android/graphics/Canvas$CanvasFinalizer");
    gCanvasFinalizerClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas""I");
 
    clazz = env->FindClass("android/graphics/Rect");
    gRectClassInfo.left = env->GetFieldID(clazz, "left""I");
    gRectClassInfo.top = env->GetFieldID(clazz, "top""I");
    gRectClassInfo.right = env->GetFieldID(clazz, "right""I");
    gRectClassInfo.bottom = env->GetFieldID(clazz, "bottom""I");
 
    return err;
}
cs


 위의 소스코드는 우리들이 앞으로 예제를 통해 사용하게 될 소스코드의 일부입니다. 이 코드를 처음 보시는 분이시라면 상당히 복잡한 구조의 코드를 단번에 이해하시기 힘드실 것이라 생각합니다. 진행하기에 앞서 Android 환경에서 JNI의 활용에 대해 자세히 알고 싶으신 분은 이전에 작성하였던 포스팅을 참조해 주시기 바랍니다.



안드로이드 프레임워크 프로그래밍(7) [NDK 활용을 위한 JNI로 JAVA와 C 상호 호출]

안드로이드 프레임워크 프로그래밍(8) [JNI에서 작성된 C++ 코드에서 C 코드 함수 호출하기]

안드로이드 프레임워크 프로그래밍(10) [Native C/C++ 코드에서 Java 호출]


※본 포스팅의 예제는 이전에 작성하였던 예제를 토대로 진행할 예정입니다. 예제가 필요하신 분께서는 아래의 포스팅을 통해 예제를 다운로드 받으시길 바랍니다.

http://elecs.tistory.com/71



 이번 예제에서 사용되는 소스코드들중 일부를 미리 설명을 하고 넘어가도록 하겠습니다. 예제는 C언어를 기본으로 작성되었으며 C++에서는 약간의 수정이 필요함을 밝힙니다.

jclass (*env)->FindClass(env, "Java/Class/Path");    //C

jclass env->FindClass("Java/Class/Path");                //C++


jclass (*env)->FindClass(env, "Java/Class/Path")

 Native 소스코드에서 Java의 클래스의 경로를 저장하는 함수입니다.


jfieldID (*env)->GetFieldID(env, jclass, "변수명", "Signature값")

 FindClass를 통해 jclass에서 설정한 Class 내에 존재하는 변수의 이름과 해당 변수의 Signature를 설정하는 함수입니다.

 Signature에 대해 자세한 내용은 아래 포스팅을 참조해 주시길 바랍니다.

http://elecs.tistory.com/71


int (*env)->GetIntField(env, thiz, jfieldID);

char (*env)->GetCharField(env, thiz, jfieldID);

float (*env)->GetFloatField(env, thiz, jfieldID);

 이전 위에서 설정한 jclass와 jfieldID를 이용하여 Class 내에 있는 변수값을 가져옵니다. 이를 실행히면 Class에 저장된 현재값이 JNI를 통해 Native로 넘어오게 됩니다.


int (*env)->SetIntField(env, thiz, jfieldID, Class에 설정하고자 하는 값);

char (*env)->SetCharField(env, thiz, jfieldID, Class에 설정하고자 하는 값);

float (*env)->SetFloatField(env, thiz, jfieldID, Class에 설정하고자 하는 값);

 이를 통해 Java Class 내에 있는 변수의 값은 Native 단계에서 변경할 수 있습니다.


jmethodID(*env)->GetMethodID(env, jclass, "Method명", "Signature값");

jmethodID(*env)->GetStaticMethodID(env, jclass, "Method명", "Signature값");

 jclass에 설정된 Class 내에 있는 Method(함수)를 설정해 줍니다. 이를 통해 Native 단계에서 Java 내의 Method를 실행할 수 있는 준비가 완료됩니다.


(*env)->CallVoidMethod(env, jclass, jmethodID, ... );

(*env)->CallIntMethod(env, jclass, jmethodID, ... );

(*env)->CallCharMethod(env, jclass, jmethodID, ... );

(*env)->CallFloatMethod(env, jclass, jmethodID, ... );

(*env)->CallStaticVoidMethod(env, jCallJava, jmethodID, ...);

(*env)->CallStaticIntMethod(env, jCallJava, jmethodID, ...);

(*env)->CallStaticCharMethod(env, jCallJava, jmethodID, ...);

(*env)->CallStaticFloatMethod(env, jCallJava, jmethodID, ...);

 위에서 설정한 jclass와 jmethodID 값을 통해 Java Class 내의 Method를 실행합니다. 위의 ... 표시로 되어 있는 부분에 해당 호출하고자 하는 함수의 인자값을 넣어 실행하며 인자값이 없을 경우 빈칸으로 두시면 되겠습니다.


jmethodID (*env)->GetMethodID(env, jclass, "<init>", "Signature값");
jobject (*env)->NewObject(env, jclass, jmethodID);


 Native 단계에서 특정 Class를 생성하고자 할 때 사용하는 함수입니다. Native 단계 내에서 jmethodID를 통해 해당 클래스의 Constructor(생성자)를 함수명으로 "<init>"를 생성하며 NewObject() 함수를 통해 객체를 생성해 냅니다.


 아래는 이전 소스코드를 응용한 예제입니다.


MainActivity.java

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
 
 
package com.example.calljava;
 
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
 
public class MainActivity extends Activity {
    private TextView tv;
    private Button bt;
    private static Context mContext;
    public int a = 11;
    public int b = 23;
    
    static {
        System.loadLibrary("hello");
    }
    
    public native String stringFromJNI();
    public native void callJava();
    public native int nativeSum();
    public native TestClass nativeConst();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        tv = (TextView) findViewById(R.id.textview);
        tv.setText(this.stringFromJNI());
        mContext = this;
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
    
    public void OnClick(View v){
        switch(v.getId()){
            case R.id.button1:
                this.callJava();
                break;
            case R.id.button2:
                Toast.makeText(thisthis.nativeSum()+"", Toast.LENGTH_SHORT).show();
                break;
            case R.id.button3:
                Toast.makeText(thisthis.nativeConst().toString(), Toast.LENGTH_SHORT).show();
        }
    }
    
    public static void testToast(){
        Toast.makeText(mContext, "Hello, Toast!", Toast.LENGTH_SHORT).show();
    }
    
}
 
class TestClass{
    public TestClass(){
        
    }
    
    public String toString(){
        return "JNI를 통해 생성된 클래스입니다!";
        
    }
}
cs


Activity_Main.xml

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
42
43
44
45
46
47
48
49
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.calljava.MainActivity" >
 
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
 
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textview"
        android:layout_below="@+id/textview"
        android:layout_marginTop="14dp"
        android:onClick="OnClick"
        android:text="Button1" />
 
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/button1"
        android:layout_alignBottom="@+id/button1"
        android:layout_marginLeft="14dp"
        android:layout_toRightOf="@+id/button1"
        android:onClick="OnClick"
        android:text="Button2" />
 
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/button2"
        android:layout_alignBottom="@+id/button2"
        android:layout_marginLeft="20dp"
        android:layout_toRightOf="@+id/button2"
        android:onClick="OnClick"
        android:text="Button3" />
 
</RelativeLayout>
cs


hello.c

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
#include <string.h>
#include <jni.h>
#include <stdio.h>
 
void Java_com_example_calljava_MainActivity_callJava(JNIEnv* env, jobject thiz){
    jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
    //jclass jCallJava = (*env)->GetObjectClass(env, thiz);
 
    jmethodID testToast = (*env)->GetStaticMethodID(env, jCallJava, "testToast""()V");
    (*env)->CallStaticVoidMethod(env, jCallJava, testToast);
}
 
jstring Java_com_example_calljava_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz){
    return (*env)->NewStringUTF(env, "Hello, JNI World!");
}
 
jint Java_com_example_calljava_MainActivity_nativeSum(JNIEnv* env, jobject thiz){
    jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
 
    jfieldID ja = (*env)->GetFieldID(env, jCallJava, "a""I");
    int a = (*env)->GetIntField(env, thiz, ja);
    jfieldID jb = (*env)->GetFieldID(env, jCallJava, "b""I");
    int b = (*env)->GetIntField(env, thiz, jb);
 
    int c = a+b;
 
    return c;
}
 
jobject Java_com_example_calljava_MainActivity_nativeConst(JNIEnv* env, jobject thiz){
 
    jclass jTestClass = (*env)->FindClass(env, "com/example/calljava/TestClass");
 
    jmethodID jmid = (*env)->GetMethodID(env, jTestClass, "<init>""()V");
    jobject obj = (*env)->NewObject(env, jTestClass, jmid);
    
    
    return obj;
    
}
cs







300x250

안드로이드 Framework에서 Camera 동작 원리 분석(3)

안드로이드/카메라 2015. 4. 27. 00:04

 안드로이드 카메라 기능을 꾸준히 분석해 보고 있습니다만 생각보다 복잡한 구조에 놀라우면서도 한 편으로는 안드로이드의 심오함을 동시에 느끼고 있습니다. 이번 포스팅은 지난시간에 이어 계속 이어가도록 하겠습니다.


 지난 포스팅까지 Camera의 Connect() 함수가 동작하는 과정에 대해 살펴보았었습니다. 이번 포스팅에서는 바로 그 다음부터 이어가도록 하겠습니다.


/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


이제 한 줄씩 살펴보도록 하겠습니다.


sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);


JNICameraContext 클래스를 선언하는 모습입니다. JNICameraContext 클래스의 내용을 자세히 살펴보도록 합시다.


/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
40
41
42
43
44
45
46
47
48
49
// provides persistent context for calls from native code to Java
class JNICameraContext: public CameraListener
{
public:
    JNICameraContext(JNIEnv* env, jobject weak_this, jclass clazz, const sp<Camera>& camera);
    ~JNICameraContext() { release(); }
    virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
    virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr,
                          camera_frame_metadata_t *metadata);
    virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
    void postMetadata(JNIEnv *env, int32_t msgType, camera_frame_metadata_t *metadata);
    void addCallbackBuffer(JNIEnv *env, jbyteArray cbb, int msgType);
    void setCallbackMode(JNIEnv *env, bool installed, bool manualMode);
    sp<Camera> getCamera() { Mutex::Autolock _l(mLock); return mCamera; }
    bool isRawImageCallbackBufferAvailable() const;
    void release();
 
private:
    void copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType);
    void clearCallbackBuffers_l(JNIEnv *env, Vector<jbyteArray> *buffers);
    void clearCallbackBuffers_l(JNIEnv *env);
    jbyteArray getCallbackBuffer(JNIEnv *env, Vector<jbyteArray> *buffers, size_t bufferSize);
 
    jobject     mCameraJObjectWeak;     // weak reference to java object
    jclass      mCameraJClass;          // strong reference to java class
    sp<Camera>  mCamera;                // strong reference to native object
    jclass      mFaceClass;  // strong reference to Face class
    jclass      mRectClass;  // strong reference to Rect class
    Mutex       mLock;
 
    /*
     * Global reference application-managed raw image buffer queue.
     *
     * Manual-only mode is supported for raw image callbacks, which is
     * set whenever method addCallbackBuffer() with msgType =
     * CAMERA_MSG_RAW_IMAGE is called; otherwise, null is returned
     * with raw image callbacks.
     */
    Vector<jbyteArray> mRawImageCallbackBuffers;
 
    /*
     * Application-managed preview buffer queue and the flags
     * associated with the usage of the preview buffer callback.
     */
    Vector<jbyteArray> mCallbackBuffers; // Global reference application managed byte[]
    bool mManualBufferMode;              // Whether to use application managed buffers.
    bool mManualCameraCallbackSet;       // Whether the callback has been set, used to
                                         // reduce unnecessary calls to set the callback.
};
cs


 이 클래스는 Camera 관련 호출이 Native에서 Java로 전송해야 될 때 주로 쓰이는 클래스로 추측할 수 있습니다. 일단 자세한 사항은 다음에 기회가 될 때 살명하고 넘어가도록 하겠습니다.


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JNICameraContext::JNICameraContext(JNIEnv* env, jobject weak_this, jclass clazz, const sp<Camera>& camera)
{
    mCameraJObjectWeak = env->NewGlobalRef(weak_this);
    mCameraJClass = (jclass)env->NewGlobalRef(clazz);
    mCamera = camera;
 
    jclass faceClazz = env->FindClass("android/hardware/Camera$Face");
    mFaceClass = (jclass) env->NewGlobalRef(faceClazz);
 
    jclass rectClazz = env->FindClass("android/graphics/Rect");
    mRectClass = (jclass) env->NewGlobalRef(rectClazz);
 
    mManualBufferMode = false;
    mManualCameraCallbackSet = false;
}
cs


 위에서 보시는 바와 같이 JNICameraContext 클래스 내에 Camera와 관련된 Java 클래스값들을 선언하고 있는 것을 확인하실 수 있습니다.



context->incStrong((void*)android_hardware_Camera_native_setup);


함수 android_hardware_Camera_native_setup() 함수의 참조계수를 1 증가시킵니다.


camera->setListener(context);


위에서 선언하였던 JNICameraContext의 변수값을 camera 클래스 변수 내에 Listerner로 설정해줍니다.


/frameworks/av/camera/Camera.cpp

1
2
3
4
5
void Camera::setListener(const sp<CameraListener>& listener)
{
    Mutex::Autolock _l(mLock);
    mListener = listener;
}
cs


 이로서 대망의 Camera.open() 함수의 동작과정을 (1)~(3) 포스팅을 통해 모두 살펴보았습니다. Camera의 구현과정을 처음 보시는 분들이시라면 상당히 큰 어려움이 있으실 것이라 생각합니다. 막히더라도 일단 망설이지 마시고 대략 이러한 기능을 한다는 것을 기억하신 후 다음 단계로 넘어가신다면 이후 넘어갔던 부분이 이해가 되실 날이 오리라 저는 생각합니다!

300x250