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

안드로이드/프레임워크 2015. 2. 18. 23:12

 지금까지 프레임워크 프로그래밍을 진행하면서 Framework 단계에서 Java로 작성된 System Service를 추가하는 방법을 알아보았고 추가된 System Service에 대응하는 C/C++로 작성된 Native 코드를 추가하는 방법을 알아보았습니다. 이번 포스팅에서는 Native Framework 단계에서 Java Framework의 함수를 호출해 보도록 해보겠습니다.


 포스팅을 진행하기에 앞서 이번에 다루게 될 내용은 지금까지 본 포스팅의 프레임워크 프로그래밍을 진행하면서 만들어왔던 소스코드를 재활용합니다. 본 포스팅을 읽던 도중 해당 코드에 대해 좀 더 자세히 알고 싶으신 분께서는 이전에 작성했었던 포스팅을 참조해 주시길 바랍니다.


먼저 안드로이드 프레임워크에 System Service를 추가하는 방법에 다룬 포스팅입니다.

안드로이드 프레임워크 프로그래밍(4) [시스템서비스 추가하기]

http://elecs.tistory.com/64


이번 포스팅을 이해하기 위해 기본적으로 알아야할 JNI를 활용하는 방법에 대해서 입니다.

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

http://elecs.tistory.com/71


안드로이드 Framework에 Native 소스를 추가할 때 C++에서 C를 호출하는 방법에 대해 알아두면 좋습니다.

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

http://elecs.tistory.com/74


이번 포스팅에서 활용되는 예제입니다.

안드로이드 프레임워크 프로그래밍(9) [프레임워크에 JNI를 활용해 C/C++ 코드 추가하기]

http://elecs.tistory.com/75



=========================================================================

/frameworks/base/services/java/com/android/server/TestService.java

이전에 작성했었던 System Service 코드에 자신이 호출할 때 사용하게 될 Method를 추가합니다.


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
package com.android.server;
import android.content.Context;
import android.os.Handler;
import android.os.ITestService;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;
import android.widget.Toast;
public class TestService extends ITestService.Stub {
    private static final String TAG = "TestService";
    private TestWorkerThread mWorker;
    private TestWorkerHandler mHandler;
    private Context mContext;
 
    public static native String stringFromJNI();
    public static native void nativeCallJava();
 
    public TestService(Context context) {
        super();
        mContext = context;
        mWorker = new TestWorkerThread("TestServiceWorker");
        mWorker.start();
        Log.i(TAG, "Spawned worker thread");
    }
 
    public void setValue(int val) {
        Log.i(TAG, "setValue " + val);
        Message msg = Message.obtain();
        msg.what = TestWorkerHandler.MESSAGE_SET;
        msg.arg1 = val;
        mHandler.sendMessage(msg);
    }
 
    public void showToast(int val){
        Log.i(TAG, "showToast " + val);
        Message msg = Message.obtain();
        msg.what = 2;
        msg.arg1 = val;
        mHandler.sendMessage(msg);
    }
 
    public void callJava(){
        nativeCallJava();
    }
 
    public String callString(){
        Log.i(TAG, "callString()");
        return stringFromJNI();
    }
 
    private class TestWorkerThread extends Thread {
        public TestWorkerThread(String name) {
            super(name);
        }
        public void run() {
            Looper.prepare();
            mHandler = new TestWorkerHandler();
            Looper.loop();
        }
    }
 
    private class TestWorkerHandler extends Handler {
        private static final int MESSAGE_SET = 0;
        @Override
        public void handleMessage(Message msg) {
            try {
                if (msg.what == MESSAGE_SET) {
                    Log.i(TAG, "set message received: " + msg.arg1);
                }
        if (msg.what == 2){
            Log.i(TAG, "Show toast!!");
            Toast.makeText(mContext, "Toast message : "+msg.arg1, Toast.LENGTH_SHORT).show();
        }
        
            } catch (Exception e) {
                // Log, don't crash!
                Log.e(TAG, "Exception in TestWorkerHandler.handleMessage:", e);
            }
        }
    }
}
cs


위에 추가한 Method를 호출할 수 있도록 AIDL도 수정합니다.

/frameworks/base/core/java/android/os/ITetService.aidl

1
2
3
4
5
6
7
8
9
10
package android.os;
interface ITestService {
/**
* {@hide}
*/
    void setValue(int val);
    void showToast(int val);
    void callJava();
    String callString();
}
cs


 여기까지 진행하셨다면 이제 위에서 만들었던 System Service에 대응하는 Native 소스를 작성해 보도록 하겠습니다. 아래 예제은 이전 포스팅에 다루었던 JNI를 통한 Native 소스 추가를 이미 알고 있다는 전제 하에 설명할 것입니다. 안드로이드 Framework에서 Native 소스를 추가하는 방법에 대해 좀 더 자세히 알고 싶으신 분은 이전에 작성했던 포스팅을 참조해 주시기 바랍니다.


안드로이드 프레임워크 프로그래밍(9) [프레임워크에 JNI를 활용해 C/C++ 코드 추가하기]

http://elecs.tistory.com/75


/frameworks/base/services/jni/com_android_server_TestService.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
#include "jni.h"
#include "JNIHelp.h"
#include "com_android_server_TestService.h"
#include <string.h>
#include <stdio.h>
#include <utils/Log.h>
 
static void android_server_TestService_nativeCallJava(JNIEnv* env, jobject thiz){
    jclass jCallJava = (*env)->FindClass(env, "android/view/InputEventReceiver");
    jmethodID callJavaMethod = (*env)->GetStaticMethodID(env, jCallJava, "callJavaMethod""()V");
    (*env)->CallStaticVoidMethod(env, jCallJava, callJavaMethod);
 
}
 
static jstring android_server_TestService_stringFromJNI(JNIEnv* env, jobject thiz){
    return (*env)->NewStringUTF(env, "Hello, JNI World!");
}
 
/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "nativeCallJava""()V", (void*)android_server_TestService_nativeCallJava },
    "stringFromJNI""()Ljava/lang/String;", (void*)android_server_TestService_stringFromJNI },
};
 
int register_android_server_TestService(JNIEnv* env)
{
    //return jniRegisterNativeMethods(env, "com/android/server/TestService",
    //        gMethods, NELEM(gMethods));
 
    int res = jniRegisterNativeMethods(env, "com/android/server/TestService",
            gMethods, NELEM(gMethods));
 
    return 0;
}
cs


 위 소스코드에 대해 좀 더 자세한 설명을 해드리도록 하겠습니다.


1. int register_android_server_TestService(JNIEnv* env)

 위 함수는 현재 작성된 Native 함수들을 시스템에 등록하기 위해 하는 함수입니다. Java System Service에서 System.loadLibrary() 함수를 호출할 때 해당 Java System service에 해당되는 JNI 폴더 내에 있는 JNI_OnLoad() 함수를 찾아 해당 함수를 통해 Native 소스코드 내에 설정되어 있는 함스를 Java 내의 함수와 연결시켜주는 기능을 합니다. JNI_OnLoad() 함수는 해당 JNI 폴대 내의 onLoad.cpp에 포함되어 있습니다.


/frameworks/base/services/jni/onLoad.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
#include "JNIHelp.h"
#include "jni.h"
 
//등록하고자 하는 소스코드가 C의 경우 extern "C" 를 통해 header를 include 한다.
extern "C" {
#include "com_android_server_TestService.h"
}
 
#include "utils/Log.h"
#include "utils/misc.h"
 
namespace android {
 
....
 
int register_android_server_SerialService(JNIEnv* env);
//추가하고자 하는 소스코드가 C++이고 namespace가 android일 경우 이곳에 함수를 추가해준다.
//int register_android_server_TestService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
 
....
 
};
 
using namespace android;
 
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) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");
 
    ....
 
    register_android_server_AlarmManagerService(env);
    register_android_server_TestService(env);
    register_android_server_UsbDeviceManager(env);
 
    ....
 
 
    return JNI_VERSION_1_4;
}
cs


 위와 같이 onLoad.cpp 코드 내에 자신이 만든 Native 소스의 Methoid를 등록할 수 있게 되어있고 이 소스가 실행되면 System에 자신의 Native 함수가 Java의 Method와 Mapping 되는 것을 확인하실 수 있습니다.


2. static JNINativeMethod gMethods[]

 위 배열은 int register_android_server_TestService(JNIEnv* env) 함수가 onLoad.cpp에서 호출되었을 때 jniRegisterNativeMethods()함수 내에 사용되는 배열변수입니다. 이곳에 자신이 연결하고자 하는 Java의 native method와 해당 코드 내에 있는 함수명, 해당 함수의 Signature만 등록하면 Java에서 Native Method를 호출하였을 때 Native 함수가 실행될 수 있습니다.


 만약 여러분들 중에 위에서 설명한 내용들 중 이해되지 않는 부분이 있다면 이전 포스팅에서 설명하였던 관련 내용을 반드시 참조하셔서 완벽하게 이해하신 후 계속 본 포스팅을 읽어주셨으면 합니다.


http://elecs.tistory.com/75


3. static void android_server_TestService_nativeCallJava(JNIEnv* env, jobject thiz)

 이번 포스팅에서 설명하게 될 가장 중요한 부분입니다. Java의 native method로 부터 해당 함수가 호출되면 본 함수가 실행됩니다. 이 함수를 통해서 Java Framework 내에 동작중인 Class를 직접 호출하는 기능을 가지고 있습니다. 해당 함수의 실행 과정을 자세히 살펴보면 다음과 같습니다.


jclass jCallJava = (*env)->FindClass(env, "android/view/InputEventReceiver");

자신이 호출하고자 하는 method를 가지고 있는 Class를 불러옵니다. 경로명은 해당 클래스가 존재하는 경로와 클래스의 이름입니다.


jmethodID callJavaMethod = (*env)->GetStaticMethodID(env, jCallJava, "callJavaMethod""()V");

자신이 호출하고자 하는 method의 이름과 signature를 입력해줍니다.


(*env)->CallStaticVoidMethod(env, jCallJava, callJavaMethod);

위 과정에서 설정한 method를 호출합니다.


위에서 설정한 Java의 method는 제가 직접 해당 class에 첨부한 method입니다. 해당 method는 다음과 같이 첨부하였습니다.


/frameworks/base/core/java/android/view/InputEventReceiver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class InputEventReceiver {
 
    private static final String TAG = "InputEventReceiver";
 
    private final CloseGuard mCloseGuard = CloseGuard.get();
 
    private int mReceiverPtr;
 
    ....
 
    public static void callJavaMethod(){
        Log.i(TAG, "called 'callJavaMethod()'");
    }
 
    ....
 
}
cs


 호출되는 함수를 설정할 때 유의하실 점은 해당 함수를 static으로 설정하시면 Native에서 호출이 가능합니다.


지금까지 위에서 제작한 내용들을 모두 저장한 후 소스코드를 Build 해 보도록 하겠습니다.


$ make update-api && make -j4


컴파일이 완료되면 해당 함수를 호출하는 Application을 제작해 보도록 하겠습니다.


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
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
 
    <TextView
        android:id="@+id/textview1"
        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:onClick="OnClick"
        android:text="stringFromJNI" />
 
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="OnClick"
        android:text="callJavaFromJNI" />
 
</LinearLayout>
 
 
cs


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
package com.example.test;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.ITestService;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.util.Log;
 
 
public class MainActivity extends Activity {
    private static final String DTAG = "HelloServer";
    ITestService om;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        om = ITestService.Stub.asInterface(ServiceManager.getService("Test"));
        try{
            Log.d(DTAG, "Going to call service");
            om.setValue(20);
            om.showToast(30);
            Log.d(DTAG, "Service called successfully");
        }catch(Exception e){
            Log.d(DTAG, "FAILED to call service");
            e.printStackTrace();
        }
        
    }
    
    public void OnClick(View v) throws RemoteException{
        switch(v.getId()){
            case R.id.button1:
                ((TextView)this.findViewById(R.id.textview1)).setText(om.callString());
                break;
            case R.id.button2:
                om.callJava();
                break;
        }
    }
 
 
    @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);
    }
}
cs


여기까지 제작하셨다면 이제 Application을 실행하신 후 Native에서 Java System Service를 부르는 작업을 실행해 보도록 하겠습니다.




 이전 예제에서 JNI를 통해 String을 return 하는 실습이 잘 동작되었던 것을 확인했었습니다. 이번에 Java System Service를 호출하는 기능을 수행해봅시다.


위에서 확인한 대로 Native 단계에서 다른 Class의 method를 호출하는 기능을 구현하는데 성공하였음을 확인하실 수 있습니다.


300x250