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

안드로이드/프레임워크 2015. 2. 17. 01:05

 안드로이드 프레임워크에 자신이 만든 System Service를 추가하여 사용하다 보면 안드로이드 기기의 하드웨어를 제어하는 Native 단계를 제어해야 하는 경우가 있습니다. Native 단계를 다루기 위해서는 불가피하게도 JNI를 통하여 C/C++로 구성된 코드를 작성해야 하는 경우가 거의 대부분입니다.


 물론 AIDL로 Stub 클래스를 통해 Java로 작성된 다른 프로세스에 접근하여 이를 제어하는 방법도 있습니다만, 항상 사용하지 못할 뿐 아니라 해당 Class가 다른 Class를 상속하는 상황이어서 Stub를 상속하지 못할 경우 이를 처리하기가 상당히 어렵습니다. 하지만 JNI를 활용하여 Native 단계에 접근한다면 좀 더 쉽게 Java로 작성된 코드의 Method를 호출할 수 있어 생각보다 유용하게 사용하실 수 있습니다.


 이번 포스팅에서는 안드로이드 프레임워크 단계에서 Java 기반으로 작성된 System Service에서 C/C++로 작성된 코드를 호출하는 방법에 대해 다루어 보도록 하겠습니다.


 혹시 JNI에 대해 자세한 내용과 예제를 참조하고 싶으신 분께서는 이전에 작성된 포스팅을 참조해 주시기 바랍니다.

http://elecs.tistory.com/71



※본 포스팅은 Ubuntu 12.04 데스크톱 에서 NEXUS5 Kitkat 4.4.4 r2 환경의 안드로이드 기기로 실행되었음을 알립니다.


 이전 포스팅에서 작성했던 Java System Service인 TestService.java에 대응하는 Native 소스코드를 작성해 보도록 하겠습니다. 이전에 작성했었던 TestService.java의 전체 코드를 확인하고자 하시는 분은 아래 링크를 참조해 주시길 바랍니다.

http://elecs.tistory.com/64


 그리고 이전에 적용하게 되는 Native 소스코드는 이전에 작성했던 JNI 활용 예제 때 사용했던 소스코드를 사용합니다. 해당 코드에 대해 알고 싶으신 분은 아래 링크를 참조해 주시길 바랍니다.

http://elecs.tistory.com/71



기존에 작성된 Java System Service에 native 소스코드를 연결해주는 코드를 작성해줍니다.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestService extends ITestService.Stub {
 
    ....
 
    public static native String stringFromJNI();
    public static native void nativeCallJava();
 
    ....
 
    public void callJava(){
        nativeCallJava();
    }
 
    public String callString(){
        Log.i(TAG, "callString()");
        return stringFromJNI();
    }
 
    ....
 
}
cs

다음으로 위에 추가한 함수를 불러들일 수 있도록 AIDL을 수정해줍니다.


/frameworks/base/core/java/android/os/ITestService.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


다음으로 위에 작성된 native 함수에 해당하는 C 기반의 코드를 작성해 줍니다.


/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
#include "jni.h"
#include "JNIHelp.h"
#include <string.h>
#include <stdio.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!");
}


끝으로 Android.mk에 새로운 소스코드를 추가하여 컴파일이 되도록 설정해줍니다.


/frameworks/base/services/jni/Android.mk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
 
LOCAL_SRC_FILES:= \
    com_android_server_AlarmManagerService.cpp \
    com_android_server_AssetAtlasService.cpp \
    com_android_server_ConsumerIrService.cpp \
    com_android_server_input_InputApplicationHandle.cpp \
    com_android_server_input_InputManagerService.cpp \
    com_android_server_input_InputWindowHandle.cpp \
    com_android_server_LightsService.cpp \
    com_android_server_power_PowerManagerService.cpp \
    com_android_server_SerialService.cpp \
    com_android_server_SystemServer.cpp \
    com_android_server_TestService.c \
    com_android_server_UsbDeviceManager.cpp \
    com_android_server_UsbHostManager.cpp \
    com_android_server_VibratorService.cpp \
    com_android_server_location_GpsLocationProvider.cpp \
    com_android_server_location_FlpHardwareProvider.cpp \
    com_android_server_connectivity_Vpn.cpp \
    onload.cpp
 
    ....
cs


 이렇게 Application 단계에서 간단하게 작성한 후 바로 실행되었던 것과는 달리 Java System service가 C 코드를 찾지 못하는 상황이 발생합니다. 도대체 무엇이 빠졌길래 이런 결과가 나온 걸까요?


 위 Android.mk 폴더 내에 코드를 보면 다음과 같이 작성된 부분을 보실 수 있습니다.

LOCAL_MODULE:= libandroid_servers


이는 libandroid_servers.so 를 만들겠다는 의미로 보시면 됩니다. 해당 파일은 SystemServer에서 호출됩니다.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SystemServer {
 
    ....
 
    private static native void nativeInit();
 
    public static void main(String[] args) {
 
    ....
 
        System.loadLibrary("android_servers");
 
        Slog.i(TAG, "Entered the Android system server!");
 
        // Initialize native services.
        nativeInit();
 
        // This used to be its own separate thread, but now it is
        // just the loop we run on the main thread.
        ServerThread thr = new ServerThread();
        thr.initAndLoop();
    }
}
cs


 위 과정을 통해 Library가 호출되면서 이에 해당되는 Native 함수들이 등록됩니다. 이 때 JNI_OnLoad() 함수가 호출되는데 해당 함수는 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
49
50
51
52
53
54
55
56
57
58
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
 
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_AssetAtlasService(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_PowerManagerService(env);
    register_android_server_SerialService(env);
    register_android_server_InputApplicationHandle(env);
    register_android_server_InputWindowHandle(env);
    register_android_server_InputManager(env);
    register_android_server_LightsService(env);
    register_android_server_AlarmManagerService(env);
    register_android_server_UsbDeviceManager(env);
    register_android_server_UsbHostManager(env);
    register_android_server_VibratorService(env);
    register_android_server_SystemServer(env);
    register_android_server_location_GpsLocationProvider(env);
    register_android_server_location_FlpHardwareProvider(env);
    register_android_server_connectivity_Vpn(env);
    register_android_server_AssetAtlasService(env);
    register_android_server_ConsumerIrService(env);
 
 
    return JNI_VERSION_1_4;
}
 


 위에서 보시는 바와 같이 jni 폴더 내부에 있던 Native 소스코드들이 호출되며 자신이 가지고 있는 함수들이 등록되고 있는 과정을 보실 수 있습니다. 자신이 작성한 Native 소스코드 내에는 자신의 함수를 등록하는 과정을 거치지 않았기 때문에 Java System Service에서는 해당 Native 함수를 부를 수 없었던 것입니다. 이번에는 자신의 Native 함수를 등록하는 과정을 보도록 하겠습니다.


 이후부터는 자신의 소스코드가 C++로 작성되었다는 것을 전재로 진행하게 됩니다. 만약 자신의 코드가 C로 작성된 분일 경우 아래 포스팅을 통해 진행해주시면 되겠습니다.


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


 우선 자신의 Native 함수를 등록할 수 있도록 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
49
50
51
52
53
54
55
56
57
58
59
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"
 
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_ConsumerIrService(JNIEnv *env);
int register_android_server_InputApplicationHandle(JNIEnv* env);
int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_TestService(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
int register_android_server_AssetAtlasService(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_PowerManagerService(env);
    register_android_server_SerialService(env);
    register_android_server_InputApplicationHandle(env);
    register_android_server_InputWindowHandle(env);
    register_android_server_InputManager(env);
    register_android_server_LightsService(env);
    register_android_server_AlarmManagerService(env);
    register_android_server_TestService(env);
    register_android_server_UsbDeviceManager(env);
    register_android_server_UsbHostManager(env);
    register_android_server_VibratorService(env);
    register_android_server_SystemServer(env);
    register_android_server_location_GpsLocationProvider(env);
    register_android_server_location_FlpHardwareProvider(env);
    register_android_server_connectivity_Vpn(env);
    register_android_server_AssetAtlasService(env);
    register_android_server_ConsumerIrService(env);
 
 
    return JNI_VERSION_1_4;
}
cs


다음으로 자신의 소스코드에 다음 내용을 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 * 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)
{
    int res = jniRegisterNativeMethods(env, "com/android/server/TestService",
            gMethods, NELEM(gMethods));
 
    return 0;
}
cs


여기서 각 코드를 입력시 주의할 점이 있습니다.

static JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "Java 코드 내에 정의한 native method의 이름""Signature", (void*)Native_소스_코드_내에_있는_함수의_이름 },
};


 위 코드에서 중간에 들어가는 Signature는 Java의 인자와 return값을 표현한 것으로 해당 함수의 특성을 간단하게 나타냈다고 볼 수 있겠습니다. 이에 대한 자세한 설명은 아래 사이트를 참조해 주시기 바랍니다.

http://forum.falinux.com/zbxe/index.php?document_srl=573117&mid=lecture_tip


 signature를 처음 접하시는 분이라면 도통 뭔소린지 도저히 이해하지 못할 분들이 많으실 겁니다. Java에서는 해당 함수의 signature를 확인할 수 있는 방법이 있습니다. 이는 linux의 Terminal에서 다음과 같은 명령어를 통해 알아볼 수 있습니다.


$ javap -s '클래스명'


자신이 알아보고자 하는 Class를 알아내기 위해서는 먼저 Native 소스를 포함하기 이전에 Java로 작성된 소스를 Build 해주셔야 합니다.


$ cd ~/kitkat

$ make


 소스코드를 Build 하시면 /out/target/common/obj/JAVA_LIBRARIES/ 폴더 내부에 소스코드의 Class들이 생성되어 있는 것을 확인하실 수 있습니다. 현재 우리가 작성하고 있는 해당 Class가 있는 폴더로 이동하면 다음과 같이 해당 Class가 있음을 확인하실 수 있습니다.

/out/target/common/obj/JAVA_LIBRARIES/services_intermediates/classes/com/android/server





이제 Terminal을 실행하신 후 해당 폴더로 이동하신 후 명령어를 입력하시면 자신이 원하는 함수의 signature가 나타나는 것을 확인하실 수 있습니다.


위에서 확인한 대로 해당 함수에 해당되는 signature를 입력해주시면 되겠습니다!

※주의!!

인자나 return 값에 class가 있을 경우 반드시 해당 클래스명의 끝에 ;(세미콜론)을 붙여주셔야 합니다!

예) String의 경우 'Ljava/lang/String;'


int register_android_server_TestService(JNIEnv* env)
{
    int res = jniRegisterNativeMethods(env, "Native 함수와 연결되는 Java 소스코드의 경로",
            gMethods, NELEM(gMethods));
 
    return 0;
}


 위에서 작성된 gMethods가 해당 함수에서 jniRegisterNativeMethods를 호출함으로서 Native 소스 내에 있는 함수들이 Java의 native로 선언된 함수와 연결됩니다. 여기까지 진행하셨다면 Framework 단계에서 Java 코드와 C 소스코드가 JNI와 연결되는 과정이 끝납니다. 다시 소스코드를 Build 하신 후 Application을 작성해 보도록 하겠습니다.


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
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;
        }
    }
}
 
 
cs



참고자료 : http://blog.daum.net/baramjin/16011086

300x250