안드로이드 프레임워크 프로그래밍(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