안드로이드 프레임워크 프로그래밍(13) [커널이미지 적용후 부팅화면만 나올 때 대처법]

안드로이드/프레임워크 2015. 3. 24. 02:16

 AOSP를 알면 알수록 참으로 심오한 안드로이드의 신비를 몸소 느끼고 있습니다. 아마 이 포스팅을 보시는 여러분들도 안드로이드의 구조에 대해 열심히 공부하고 계시리라 생각합니다. 이번 포스팅에서는 프로그래밍 도중 일어날 수 있는 무한 부팅모드에 대해 이야기를 해보려 합니다.


 안드로이드 기기를 켰을 때 부팅화면이 나오게 되는데 이 상태에서 안드로이드 기기는 설정되어 있는 시스템들을 모두 구동될 때 까지 준비중이라는 의미로 부팅화면을 띄우는데요. 간혹 특정 코드 부분을 코딩하다보면 의도치 않게 보시는 바와 같이 무한 로딩 상태에 빠지는 경우가 있습니다.



 아마 위의 화면처럼 안드로이드의 부팅화면이 반복되는 것을 본다면 혹시 고장은 아닌가 하는 걱정이 들 것입니다. 이는 eclipse를 실행 후 Logcat 화면을 확인해 보시면 분명 기기는 동작중이긴 한데 특정 부분에서 exception이 벌어지고 있는 것을 보실 수 있습니다.


 과연 이 경우는 어떤 경우일까요? 가장 큰 요인은 자신이 수정한 부분에서 문제가 발생하였을 때 이러한 현상이 주로 발생합니다. 비록 단순하지만 다음과 같이 대처하시면 원인을 찾으실 수 있으실 겁니다.


1. 일단 자신의 기기의 전원버튼을 계속 누르셔서 기기를 종료합니다. 어떤 기기의 경우 전원이 꺼지자마자 바로 부팅모드에 진입하는 경우가 있는데 그런 경우 그냥 처음부터 bootloader 모드로 진입하도록 합니다.


2. 커널 이미지를 적용하기 직전 자신이 작성한 소스코드 부분들을 모두 주석처리합니다. 그런 다음 다시 컴파일을 시도하신 후 이미지를 bootloader에 진입한 기기에 적용합니다.


3. 주석처리한 코드를 꼼꼼히 살피면서 주석을 한 줄씩 지워가며 문제가 발생한 부분을 찾습니다. 거의 이 단계에서 무안 부팅모드에 빠지는 코드를 찾아내실 수 있으실 겁니다.

300x250

안드로이드 프레임워크 프로그래밍(12) [IServiceManager 등록과정]

안드로이드/프레임워크 2015. 3. 23. 01:43

 처음 안드로이드 프레임워크를 분석하게 되었을 때엔 적어도 Java Framework 단계에서 모든 작업을 해결할 수 있을 듯해 보였습니다만 최근 연구하는 Camera의 경우 안드로이드 내 리눅스 커널 단계까지 연구하고 있는 제 모습을 보고 있습니다...


 이번 포스팅에서는 CameraService와 같이 Hardware Service들을 IServiceManager에 등록되는 과정에 대해 샅샅히 살펴보고자 합니다. 본 포스팅에서는 CameraService.cpp를 예로 들어볼 것입니다.


 카메라 작동과 관련된 동작을 관리하는 CameraService는 main_mediaserver.cpp에 의해 안드로이드 시스템 내에 등록이 되어집니다.

/frameworks/av/media/mediaserver/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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
LOCAL_PATH:= $(call my-dir)
 
ifneq ($(BOARD_USE_CUSTOM_MEDIASERVEREXTENSIONS),true)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := register.cpp
LOCAL_MODULE := libregistermsext
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_LIBRARY)
endif
 
include $(CLEAR_VARS)
 
LOCAL_SRC_FILES:= \
    main_mediaserver.cpp 
 
LOCAL_SHARED_LIBRARIES := \
    libaudioflinger \
    libcameraservice \
    libmedialogservice \
    libcutils \
    libnbaio \
    libmedia \
    libmediaplayerservice \
    libutils \
    liblog \
    libbinder
 
LOCAL_STATIC_LIBRARIES := \
    libregistermsext
 
LOCAL_C_INCLUDES := \
    frameworks/av/media/libmediaplayerservice \
    frameworks/av/services/medialog \
    frameworks/av/services/audioflinger \
    frameworks/av/services/camera/libcameraservice
 
LOCAL_MODULE:= mediaserver
 
include $(BUILD_EXECUTABLE)
 
cs


/frameworks/av/media/mediaserver/main_mediaserver.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
#define LOG_TAG "mediaserver"
....
#include <binder/IServiceManager.h>
....
#include "CameraService.h"
 
using namespace android;
 
int main(int argc, char** argv)
{
    signal(SIGPIPE, SIG_IGN);
    char value[PROPERTY_VALUE_MAX];
    bool doLog = (property_get("ro.test_harness", value, "0"> 0) && (atoi(value) == 1);
    pid_t childPid;
    if (doLog && (childPid = fork()) != 0) {
        ....
    } else {
        // all other services
        if (doLog) {
            prctl(PR_SET_PDEATHSIG, SIGKILL);   // if parent media.log dies before me, kill me also
            setpgid(00);                      // but if I die first, don't kill my parent
        }
    ....
        CameraService::instantiate();
    ....
    }
}
cs


 위의 소스코드에서 보시는 바와 같이 CameraService::instantiate() 함수가 실행됨으로서 ServiceManager에 등록되는 과정이 진행됩니다. 그런데 막상 CameraService.cpp 내에서 imstantiate() 함수는 눈을 씻고 보아도 어디에도 보이지 않습니다. 이는 즉 다른 클래스에서 상속되었다는 뜻으로 볼 수 있을텐데요. 그렇다면 이를 직접 찾아보도록 하겠습니다.


/frameworks/av/services/camera/libcameraservice/CameraService.h

1
2
3
4
5
6
7
8
9
10
11
12
....
#include <binder/BinderService.h>
....
class CameraService :
    public BinderService<CameraService>,
    public BnCameraService,
    public IBinder::DeathRecipient,
    public camera_module_callbacks_t
{
    friend class BinderService<CameraService>;
....
}
cs

/frameworks/native/include/binder/BinderService.h
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
#ifndef ANDROID_BINDER_SERVICE_H
#define ANDROID_BINDER_SERVICE_H
 
#include <stdint.h>
 
#include <utils/Errors.h>
#include <utils/String16.h>
 
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
 
// ---------------------------------------------------------------------------
namespace android {
 
template<typename SERVICE>
class BinderService
{
public:
    static status_t publish(bool allowIsolated = false) {
        sp<IServiceManager> sm(defaultServiceManager());
        return sm->addService(
                String16(SERVICE::getServiceName()),
                new SERVICE(), allowIsolated);
    }
 
    static void publishAndJoinThreadPool(bool allowIsolated = false) {
        publish(allowIsolated);
        joinThreadPool();
    }
 
    static void instantiate() { publish(); }
 
    static status_t shutdown() { return NO_ERROR; }
 
private:
    static void joinThreadPool() {
        sp<ProcessState> ps(ProcessState::self());
        ps->startThreadPool();
        ps->giveThreadPoolName();
        IPCThreadState::self()->joinThreadPool();
    }
};
 
 
}; // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_BINDER_SERVICE_H
 
 
cs


 위의 코드에서 보시는 바와 같이 instantiate() 함수는 BinderService 클래스에서 이루어지고 있는 모습을 보실 수 있습니다. 그리고 그 안에는 publish() 함수가 실행되고 있으며 publish() 함수는 CameraService를 IServiceManager에 등록하고 있는 과정을 보이고 있습니다.


return sm->addService(

                String16(SERVICE::getServiceName()),
                new SERVICE(), allowIsolated);


static char const* getServiceName() { return "media.camera"; }



 위 코드를 통해 CameraService는 IServiceManager에 등록됩니다.

/frameworks/native/libs/binder/IServiceManager.cpp

1
2
3
4
5
6
7
8
9
10
11
12
    virtual status_t addService(const String16& name, const sp<IBinder>& service,
            bool allowIsolated)
    {
        Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }
 
cs






300x250

안드로이드 프레임워크 프로그래밍(11) [JAVA 프레임워크와 Native 프레임워크 연결 원리]

안드로이드/프레임워크 2015. 3. 18. 02:49

 어느덧 안드로이드 프레임워크를 연구한지 3달 정도 되어갑니다. 초반에는 온갖 삽질이 길어져서 작업 하나를 수행하는 데에도 많은 시간이 소요되었습니다만 현재 조금 프로그램의 흐름을 볼 수 있는 눈이 생겨 어느 정도 감을 잡아가는 단계가 되었다고 조심스럽게 말해봅니다.


 이번 포스팅에서는 안드로이드 기기가 부팅된 후 Java와 Native 프레임워크가 JNI로 연결되는 과정에 대해 다루어 보도록 하겠습니다.


 이전 포스팅에서 service의 JNI 연결과정을 다룬 바 있습니다. 이번 포스팅에서는 '/framework/base/core'에  있는 프레임워크들이 JNI를 통해 Native와 서로 연결되는 과정을 다루게 됩니다. Java Service와 Native Service의 연결 과정에 대해서는 아래 포스팅을 참조해 주시길 바랍니다.


http://elecs.tistory.com/75


 본 포스팅은 안드로이드 카메라 시스템인 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 }

    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
};


위에서 보시면 아시듯 '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 배열을 선언하는 과정을 보실 수 있는데 해당 배열 내에 선언된 값을 하나 보시면 다음과 같습니다.

  { "native_setup",
    "(Ljava/lang/Object;ILjava/lang/String;)V",
    (void*)android_hardware_Camera_native_setup }

 위의 구조를 보았을 때 JNINativeMethod가 struct 구조체로 구성되어 있다는 사실을 어느 정도 직감하실 수 있으실 겁니다! 해당 구조체 내에는 3개의 변수를 저장할 수 있는대 내용은 다음과 같습니다.


{ Java에서 선언된 native method, Signature, C++에서 연결하고자 하는 함수 }

위와 같은 방식으로 값들을 선언해 주시면 되겠습니다. 위에서 Signature는 Java에서 선안된 함수의 인자와 return을 간단하게 나타낸 것을 말합니다. Signature에 대해 좀 더 자세히 알고 싶으신 분들은 아래 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/75


다음으로 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


300x250

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

안드로이드/카메라 2015. 3. 11. 02:06

 안드로이드 프레임워크를 공부하면서 느끼는 점이 애플리케이션 단계에서는 함수 몇 줄만 쓰면 쉽게 구현되는 기능이 프레임워크를 공부하면서 안드로이드 세계의 심오함을 몸소 느끼고 있습니다. 정말로 운영체제를 만든 분들이 존경스러울 정도 입니다.

 이번 포스팅 부터는 안드로이드 카메라가 Framework 단계에서 동작하는 과정에 대해 살펴보도록 하겠습니다. 내용이 상당히 방대하기 때문에 분량을 중간에 나누어 가면서 다루도록 하겠습니다.



 우리들이 Applycation 단계에서 Camera를 사용할 때 보통은 SurfaceHolder를 통해서 Camera를 사용합니다. 혹시 SurfaceHolder의 동작 원리에 대해 자세히 알고 싶으신 분은 아래 포스팅을 참조해 주셨으면 합니다.

http://elecs.tistory.com/78


Application 단계에서 Camera 클래스가 사용되는 사례를 보도록 합시다.

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
 
    private SurfaceHolder.Callback surfaceListener = new SurfaceHolder.Callback() {
        
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera.release();
            
        }
        
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera = Camera.open();
            try {
                camera.setPreviewDisplay(holder);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
        
        @Override
        public void surfaceChanged(SurfaceHolder holder, int formatint width, int height) {
            // TODO Auto-generated method stub
            Camera.Parameters parameters = camera.getParameters();
            parameters.setPreviewSize(width, height);
            camera.startPreview();
            
        }
    };
cs


 흔히들 위의 방식대로 Camera 클래스를 사용하고 계시리라 생각합니다. 그렇다면 각 함수별로 좀 더 들어가보도록 하겠습니다.


 SurfaceView가 화면에 보이게 되면 SurfaceHolder에서 surfaceCreated()함수가 실행됩니다. 이 때 Camera 관련 기능들이 동작을 시작하게 되는 것이지요.


camera = Camera.open();


 이 부분은 Camera의 기능을 활성화 하는 단계입니다. Camera의 코드 내부를 살펴보면 다음과 같습니다.


/frameworks/base/core/java/android/hardware/Camera.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
public class Camera {
 
    ....
 
    public static Camera open(int cameraId) {
        return new Camera(cameraId);
    }
 
    public static Camera open() {
        int numberOfCameras = getNumberOfCameras();
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
            getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
                return new Camera(i);
            }
        }
        return null;
    }
 
    Camera(int cameraId) {
        mShutterCallback = null;
        mRawImageCallback = null;
        mJpegCallback = null;
        mPreviewCallback = null;
        mPostviewCallback = null;
        mUsingPreviewAllocation = false;
        mZoomListener = null;
 
        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
        }
 
        String packageName = ActivityThread.currentPackageName();
 
        native_setup(new WeakReference<Camera>(this), cameraId, packageName);
    }
 
    private native final void native_setup(Object camera_this, int cameraId,
                                           String packageName);
 
    ....
 
 
}
cs


위에서 보시는 바와 같이 JNI를 통해 native_setup()를 호출하는 모습을 보실 수 있습니다. 이번에는 Native 코드를 확인해 보도록 하겠습니다.


/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
// 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<>의 기능에 대해 생소해 하실 분들이 계실 겁니다. 이전 포스팅에서 제가 아는 한에서 나마 설명한 자료가 있으니 해당 자료를 참조해 주셨으면 합니다.

http://elecs.tistory.com/79


다음으로 해당 함수가 실행되는 것을 보실 수 있습니다.

sp<Camera> camera = Camera::connect(cameraId, clientName,

            Camera::USE_CALLING_UID);


이는 Camera.cpp 의 클래스에서 카메라와 연결되는 과정을 다루고 있는 것입니다. 이를 한 번 자세히 보도록 하겠습니다.

/frameworks/av/camera/Camera.cpp

1
2
3
4
5
sp<Camera> Camera::connect(int cameraId, const String16& clientPackageName,
        int clientUid)
{
    return CameraBaseT::connect(cameraId, clientPackageName, clientUid);
}
cs

 여기까지 쉼없이 달려왔건만 이번에는 CameraBaseT라는 함수가 튀어나오는군요!


/frameworks/av/include/camera/CameraBase.h

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
template <typename TCam>
struct CameraTraits {
};
 
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
public:
    typedef typename TCamTraits::TCamListener       TCamListener;
    typedef typename TCamTraits::TCamUser           TCamUser;
    typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;
    typedef typename TCamTraits::TCamConnectService TCamConnectService;
 
    static sp<TCam>      connect(int cameraId,
                                 const String16& clientPackageName,
                                 int clientUid);
    virtual void         disconnect();
 
    void                 setListener(const sp<TCamListener>& listener);
 
    static int           getNumberOfCameras();
 
    static status_t      getCameraInfo(int cameraId,
                                       /*out*/
                                       struct CameraInfo* cameraInfo);
 
    static status_t      addServiceListener(
                                    const sp<ICameraServiceListener>& listener);
 
    static status_t      removeServiceListener(
                                    const sp<ICameraServiceListener>& listener);
 
    sp<TCamUser>         remote();
 
    // Status is set to 'UNKNOWN_ERROR' after successful (re)connection
    status_t             getStatus();
 
protected:
    CameraBase(int cameraId);
    virtual              ~CameraBase();
 
    ////////////////////////////////////////////////////////
    // TCamCallbacks implementation
    ////////////////////////////////////////////////////////
    virtual void         notifyCallback(int32_t msgType, int32_t ext,
                                        int32_t ext2);
 
    ////////////////////////////////////////////////////////
    // Common instance variables
    ////////////////////////////////////////////////////////
    Mutex                            mLock;
 
    virtual void                     binderDied(const wp<IBinder>& who);
 
    // helper function to obtain camera service handle
    static const sp<ICameraService>& getCameraService();
 
    sp<TCamUser>                     mCamera;
    status_t                         mStatus;
 
    sp<TCamListener>                 mListener;
 
    const int                        mCameraId;
 
    typedef CameraBase<TCam>         CameraBaseT;
};
 
cs

/frameworks/av/camera/CameraBase.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
template <typename TCam, typename TCamTraits>
sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId,
                                               const String16& clientPackageName,
                                               int clientUid)
{
    ALOGV("%s: connect", __FUNCTION__);
    sp<TCam> c = new TCam(cameraId);
    sp<TCamCallbacks> cl = c;
    status_t status = NO_ERROR;
    const sp<ICameraService>& cs = getCameraService();
 
    if (cs != 0) {
        TCamConnectService fnConnectService = TCamTraits::fnConnectService;
        status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,
                                             /*out*/ c->mCamera);
    }
    if (status == OK && c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        ALOGW("An error occurred while connecting to camera: %d", cameraId);
        c.clear();
    }
    return c;
}
cs


 드디어 가장 중요한 CameraService와 IPC통신을 할 수 있는 부분까지 나온 것을 확인하게 되었습니다. Camera에서 Open() 함수를 좇다 CameraService와의 연계점을 직접 보게 된 상황입니다.


 우선 여기까지 잘 따라와 주신 분이라면 Camera 클래스가 IPC로 연결되기 바로 직전의 부분에 이르렀음을 말씀드리고 싶네요. 다음 시간에는 CameraService와 Camera가 연결되는 과정을 살펴보도록 하겠습니다.


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

http://elecs.tistory.com/102

300x250

안드로이드 Native 코드 분석 : sp<> - Smart Pointer

 안드로이드 카메라 동작 원리에 대해 공부를 하면서 안드로이드 소스코드를 분석하고 있습니다. 옆에 C++ 서적을 읽어가며 보다보면 어느 정도 이해하고 넘어갈 수 있는 부분들이 많이 있었습니다만 책에서도 찾아보기 어려운 내용이 들이닥치더군요. 그 부분이 바로 sp<T> 구문이었습니다.


/framework/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sp<Camera> get_native_camera(JNIEnv *env, jobject thiz, JNICameraContext** pContext)
{
    sp<Camera> camera;
    Mutex::Autolock _l(sLock);
    JNICameraContext* context = reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz, fields.context));
    if (context != NULL) {
        camera = context->getCamera();
    }
    ALOGV("get_native_camera: context=%p, camera=%p", context, camera.get());
    if (camera == 0) {
        jniThrowRuntimeException(env, "Method called after release()");
    }
 
    if (pContext != NULL) *pContext = context;
    return camera;
}
cs


 코드 내부를 살펴보면 뜬금없이 등장하는 'sp<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
#define LOG_TAG "Camera-JNI"
#include <utils/Log.h>
 
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <android_runtime/android_view_Surface.h>
 
#include <cutils/properties.h>
#include <utils/Vector.h>
 
#include <gui/GLConsumer.h>
#include <gui/Surface.h>
#include <camera/Camera.h>
#include <binder/IMemory.h>
cs

위 헤더파일들 중 #include<binder/IMemory.h>로 이동해 봅니다.


/frameworks/native/include/binder/IMemory.h

1
2
3
4
5
6
7
8
9
10
#ifndef ANDROID_IMEMORY_H
#define ANDROID_IMEMORY_H
 
#include <stdint.h>
#include <sys/types.h>
#include <sys/mman.h>
 
#include <utils/RefBase.h>
#include <utils/Errors.h>
#include <binder/IInterface.h>
cs


 다음으로 안드로이드 JNI의 핵심을 이해할 수 있는 #include<utils/RefBase.h> 로 이동하신 후


/system/core/include/utils/RefBase.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef ANDROID_REF_BASE_H
#define ANDROID_REF_BASE_H
 
#include <cutils/atomic.h>
 
#include <stdint.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
 
#include <utils/StrongPointer.h>
#include <utils/TypeHelpers.h>
cs


끝으로 #include<utils/StongPointer.h> 로 이동해 주시면

/system/core/include/utils/StrongPointer.h

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
template<typename T>
class sp {
public:
    inline sp() : m_ptr(0) { }
 
    sp(T* other);
    sp(const sp<T>& other);
    template<typename U> sp(U* other);
    template<typename U> sp(const sp<U>& other);
 
    ~sp();
 
    // Assignment
 
    sp& operator = (T* other);
    sp& operator = (const sp<T>& other);
 
    template<typename U> sp& operator = (const sp<U>& other);
    template<typename U> sp& operator = (U* other);
 
    //! Special optimization for use by ProcessState (and nobody else).
    void force_set(T* other);
 
    // Reset
 
    void clear();
 
    // Accessors
 
    inline  T&      operator* () const  { return *m_ptr; }
    inline  T*      operator-> () const { return m_ptr;  }
    inline  T*      get() const         { return m_ptr; }
 
    // Operators
 
    COMPARE(==)
    COMPARE(!=)
    COMPARE(>)
    COMPARE(<)
    COMPARE(<=)
    COMPARE(>=)
 
private:    
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;
    void set_pointer(T* ptr);
    T* m_ptr;
};
cs


드디어 우리들이 찾고 있던 sp<T>가 구현된 StrongPointer를 찾아내실 수 있습니다. 과연 이 녀석은 무슨 역할을 하는 것일까요?


 안드로이드 소스코드에 사용되고 있는 sp<T>는 스마트포인터(Smart Pointer)를 말하는 것이며 보통 Struct나 int같이 C에서 기본적으로 제공되고 있는 것들은 *을 통해 간단하게 포인터로 사용하는 것이 가능합니다. sp<T>는 이를 객체인 Class를 포인터처럼 사용할 수 있도록 하기 위해 사용되는 도구라고 설명드릴 수 있겠습니다. 이를 사용하면 사용자는 포인터로 지정된 객체를 실제 객체를 다루듯이 사용하실 수 있다는 장점을 가지고 있습니다.


 Strong Pointer를 쓰는 가장 중요한 이유는 포인터 객체의 할당과 삭제를 직접 제어할 수 있다는 것입니다. Java와 같이 Gabage Collector 기능이 있는 경우 객체를 할당 해준 후 사용하지 않으면 알아서 할당에서 사라지지만 C/C++의 경우 해당 기능이 존재하지 않습니다. 그렇기 때문에 sp<T>를 활용해 메모리를 관리해 준다면 좀 더 쾌적한 프로그램을 만들 수 있을 것입니다.


 위 코드에서 47번째 줄의 내용을 주목해 봅시다. m_ptr이라는 이름의 변수가 있는데 이 변수의 타입은 T, 즉 sp에 전달되는 클래스 자신의 주소값을 저장하는 상황임을 알 수 있습니다.


 그렇다면 여기서 잠시 코드의 내부롤 살짝 살펴보도록 하겠습니다.


1
2
3
4
5
6
template<typename T>
sp<T>::sp(T* other)
        : m_ptr(other) {
    if (other)
        other->incStrong(this);
}
cs

객체의 스마트포인터가 할당된 경우 입니다. 기존에 해당 객체가 존재할 경우 incStrong()를 통해 참조 갯수를 늘립니다.


1
2
3
4
5
template<typename T>
sp<T>::~sp() {
    if (m_ptr)
        m_ptr->decStrong(this);
}
cs

객체의 스마트포인터 하나가 할당이 해제된 경우입니다. decString()를 통해 참조 갯수를 줄입니다.



1
2
3
4
5
6
7
template<typename T>
void sp<T>::clear() {
    if (m_ptr) {
        m_ptr->decStrong(this);
        m_ptr = 0;
    }
}
cs

객체 자체의 할당을 해제합니다. 해당 포인터를 0으로 하여 할당을 해제합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <typename T>
class sp
{
public:
    inline sp() : m_ptr(0) { }
....
    // Accessors
 
    inline  T&      operator* () const  { return *m_ptr; }
    inline  T*      operator-> () const { return m_ptr;  }
    inline  T*      get() const         { return m_ptr; }
....
private:
    template<typename Y> friend class sp;
    template<typename Y> friend class wp;
    void set_pointer(T* ptr);
    T* m_ptr;
};
 
 
cs

 앞에서도 제시되었었던 Smart Pointer인 sp 클래스 내부의 모습입니다. Accessors 주석 아래에 작성된 부분은 Smart Pointer에 연결된 포인터에 접근에 관하여 다루고 있는 모습입니다.


 안드로이드 소스코드를 이해하는 데에는 이 정도의 지식만 알고 넘어가시면 될 듯 합니다. 좀 더 자세한 내용을 알고 싶으신 분들은 위에서 언급한 코드를 직접 살펴보시면 좋을 것입니다.


300x250

SurfaceView에서 SurfaceHolder의 동작원리(Principle of SurfaceHolder in SurfaceView)

안드로이드/프레임워크 2015. 2. 26. 22:52

 짫은 시간에 화면에 다양한 변화를 나타내는 데에 사용되는 SurfaceView에서 사용되는 SurfaceHolder이 어떻게 동작하는 지에 대해 궁금해서 안드로이드 소스를 이리저리 살펴보게 되었습니다. 본 포스팅에서는 실행 방법을 제 나름대로 탐색한 결과에 대해 작성하였습니다.


 시작하기에 앞서 SurfaceView에서 가장 많이 사용되는 Camera 애플리케이션에 대해 알아봅시다. 제가 이전에 작성한 카메라 작동에 관한 포스팅은 아래 링크를 참조해 주시길 바랍니다.


http://elecs.tistory.com/69


 SurfaceView를 사용하기 위해서는 SurfaceHolder를 사용해야 합니다. SurfaceHolder에 사용하고자 하는 기능을 넣은 후 이를 등록하면 SurfaceView에서는 이를 토대로 동작을 수행하게 됩니다.


1
2
3
4
5
6
7
8
9
10
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        
        surfaceView = (SurfaceView)findViewById(R.id.surfaceView);
        
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(surfaceListener);
    }
cs


위 코드에서 확인하는 바와 같이 SurfaceView를 XML로 생성한 SurfaceView와 연결한 후

해당 SurfaceView에 SurfaceHolder를 연결한 후 해당 Holder에 surfaceListener를 추가하는 과정입니다.


여기서 surfaceListener는 다음과 같이 선언됩니다.

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
    private SurfaceHolder.Callback surfaceListener = new SurfaceHolder.Callback() {
        
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera.release();
            
        }
        
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            camera = Camera.open();
            try {
                camera.setPreviewDisplay(holder);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        }
        
        @Override
        public void surfaceChanged(SurfaceHolder holder, int formatint width, int height) {
            // TODO Auto-generated method stub
            Camera.Parameters parameters = camera.getParameters();
            parameters.setPreviewSize(width, height);
            camera.startPreview();
            
        }
    };
cs


 위에서 보시는 것과 같이 SurfaceHolder 내에 있는 Callback 인터페이스 내의 3개의 Method를 선언한 것을 보실 수 있습니다. 이 내용이 SurfaceView 내의 Holder에 적용되는 것이지요.


 이번에는 SurfaceView에서 getHolder()가 동작하는 방식에 대해 보도록 하겠습니다.


/frameworks/base/core/java/android/view/SurfaceView.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
public class SurfaceView extends View {
 
    ....
 
    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
 
        private static final String LOG_TAG = "SurfaceHolder";
 
        @Override
        public boolean isCreating() {
            return mIsCreating;
        }
 
        @Override
        public void addCallback(Callback callback) {
            synchronized (mCallbacks) {
                // This is a linear search, but in practice we'll
                // have only a couple callbacks, so it doesn't matter.
                if (mCallbacks.contains(callback) == false) {
                    mCallbacks.add(callback);
                }
            }
        }
 
    ....
 
    }
 
    ....
 
    /**
     * Return the SurfaceHolder providing access and control over this
     * SurfaceView's underlying surface.
     *
     * @return SurfaceHolder The holder of the surface.
     */
    public SurfaceHolder getHolder() {
        return mSurfaceHolder;
    }
 
}
cs


 SurfaceView 클래스를 선언한 후 getHolder() 함수를 호출하게 되면 위의 소스에서 보는 바와 같이 SurfaceView 클래스 내에 있는 mSurfaceHolder 객체를 return 해주는 구조임을 알 수 있습니다.


 mSurfaceHolder는 SurfaceView 클래스 내부에 Interface인 SurafceHolder.Callback을 정의하여 선언하였음을 확인하실 수 있습니다. 해당 Holder 내에 addCallbacks()가 정의되어있어 Application 단계에서 설정하였던 surfaceListener를 등록하고 있는 모습을 보실 수 있습니다.


 그렇다면 SurfaceView 클래스 내부에 있는 mCallbacks 객체에 등록된 surfaceListener는 어떤 방식으로 동작하는지 살펴보도록 하겠습니다. mCallbacks 또한 SurfaceView.java 내부에서 동작되는 모습을 확인하실 수 있습니다.


 아래 코드의 내용들이 상당히 많습니다만 한 줄 한 줄이 상당히 중요한 관계로 관련 코드를 모두 올려봅니다. 물론 핵심 코드는 볼드체 및 밑줄로 눈에 띄도록 표기하였습니다.


/frameworks/base/core/java/android/view/SurfaceView.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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public class SurfaceView extends View {
 
    static private final String TAG = "SurfaceView";
 
    static private final boolean DEBUG = false;
 
    final ArrayList<SurfaceHolder.Callback> mCallbacks
            = new ArrayList<SurfaceHolder.Callback>();
    ....
 
    private void updateWindow(boolean force, boolean redrawNeeded) {
    if (!mHaveFrame) {
            return;
        }
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null) {
            mTranslator = viewRoot.mTranslator;
        }
 
        if (mTranslator != null) {
            mSurface.setCompatibilityTranslator(mTranslator);
        }
 
        int myWidth = mRequestedWidth;
        if (myWidth <= 0) myWidth = getWidth();
        int myHeight = mRequestedHeight;
        if (myHeight <= 0) myHeight = getHeight();
 
        getLocationInWindow(mLocation);
        final boolean creating = mWindow == null;
        final boolean formatChanged = mFormat != mRequestedFormat;
        final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
        final boolean visibleChanged = mVisible != mRequestedVisible;
 
        if (force || creating || formatChanged || sizeChanged || visibleChanged
            || mLeft != mLocation[0|| mTop != mLocation[1]
            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
 
            if (DEBUG) Log.i(TAG, "Changes: creating=" + creating
                    + " format=" + formatChanged + " size=" + sizeChanged
                    + " visible=" + visibleChanged
                    + " left=" + (mLeft != mLocation[0])
                    + " top=" + (mTop != mLocation[1]));
 
            ....
            try {
                final boolean visible = mVisible = mRequestedVisible;
                mLeft = mLocation[0];
                mTop = mLocation[1];
                mWidth = myWidth;
                mHeight = myHeight;
                mFormat = mRequestedFormat;
 
        ....
 
 
                mSurfaceLock.lock();
        try {
                    redrawNeeded |= creating | reportDrawNeeded;
 
                    SurfaceHolder.Callback callbacks[] = null;
 
                    final boolean surfaceChanged = (relayoutResult
                            & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0;
                    if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
                        mSurfaceCreated = false;
                        if (mSurface.isValid()) {
                            if (DEBUG) Log.i(TAG, "visibleChanged -- surfaceDestroyed");
                            callbacks = getSurfaceCallbacks();
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceDestroyed(mSurfaceHolder);
                            }
                        }
                    }
 
                    mSurface.transferFrom(mNewSurface);
 
                    if (visible && mSurface.isValid()) {
                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                            mSurfaceCreated = true;
                            mIsCreating = true;
                            if (DEBUG) Log.i(TAG, "visibleChanged -- surfaceCreated");
                            if (callbacks == null) {
                                callbacks = getSurfaceCallbacks();
                            }
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceCreated(mSurfaceHolder);
                            }
                        }
                        if (creating || formatChanged || sizeChanged
                                || visibleChanged || realSizeChanged) {
                            if (DEBUG) Log.i(TAG, "surfaceChanged -- format=" + mFormat
                                    + " w=" + myWidth + " h=" + myHeight);
                            if (callbacks == null) {
                                callbacks = getSurfaceCallbacks();
                            }
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
                            }
                        }
                        if (redrawNeeded) {
                            if (DEBUG) Log.i(TAG, "surfaceRedrawNeeded");
                            if (callbacks == null) {
                                callbacks = getSurfaceCallbacks();
                            }
                            for (SurfaceHolder.Callback c : callbacks) {
                                if (c instanceof SurfaceHolder.Callback2) {
                                    ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
                                            mSurfaceHolder);
                                }
                            }
                        }
                    }
                } finally {
                    mIsCreating = false;
                    if (redrawNeeded) {
                        if (DEBUG) Log.i(TAG, "finishedDrawing");
                        mSession.finishDrawing(mWindow);
                    }
                    mSession.performDeferredDestroy(mWindow);
                }
            } catch (RemoteException ex) {
            }
            if (DEBUG) Log.v(
                TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
                " w=" + mLayout.width + " h=" + mLayout.height +
                ", frame=" + mSurfaceFrame);
        }  
    ....
    private SurfaceHolder.Callback[] getSurfaceCallbacks() {
        SurfaceHolder.Callback callbacks[];
        synchronized (mCallbacks) {
            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
            mCallbacks.toArray(callbacks);
        }
        return callbacks;
    }
    ....
}
cs

 위 코드를 처음 보는 사람이라면 코드의 양이 다소 방대하여 이해하기 힘드실 수 있습니다. 일단 위에서 강조한 부분에 대해 좀 더 자세히 설명드리도록 하겠습니다.


 final ArrayList<SurfaceHolder.Callback> mCallbacks  = new ArrayList<SurfaceHolder.Callback>();

 mCallBacks는 Java에서 제공하는 ArrayList로 구성된 Object입니다. 이 ArrayList 안에 Application에서 호출했던 addCallbacks()를 통해 Listener가 등록됩니다.


 private void updateWindow(boolean force, boolean redrawNeeded)

 본 함수를 통해 SurfaceView의 화면이 변경될 수 있도록 할 수 있습니다. 해당 함수를 잘 보시면 SurfaceHolder.Callback 인터페이스를 통해 선언한 함수들이 실행되는 것을 보실 수 있습니다.


300x250

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

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

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

안드로이드/프레임워크 2015. 2. 16. 23:31

 안드로이드 소스코드를 추가하는 과정에서 기존에 작성된 소스코드를 수정하거나 새로 작성된 소스코드를 추가하는 등의 작업을 하는 경우가 있습니다. 기존에 작성된 코드를 수정하는 경우 약간의 응용만 거치면 되니 상당히 쉬운 일입니다만 새로 작성된 소스코드를 추가하는 경우 예상치 못한 경우들이 발생하는 사태가 벌어집니다. 특히 C언어에 익숙하신 분이 C로 구성된 JNI 코드를 안드로이드 프레임워크의 Native System에 적용하려 했더니 C++로 작성된 코드들 사이에 끼워 넣는 과정이 상당히 복잡할 겁니다.


 이번 포스팅에서는 안드로이드 Native 프레임워크에 작성된 C++ 코드들 사이에 C로 작성된 새로운 System Service의 Native 코드를 작성한 후 이  C코드를 기존에 C++로 작성된 코드에서 호출할 수 있는 환경을 구성해 보도록 하겠습니다.


※본 포스팅은 이전에 작성된 JNI 코드를 활용한 예제를 확장하여 다루어 볼 예정입니다. 안드로이드 JNI에 대해 자세히 알고 싶으신 분이나 이전에 작성된 코드에 대해 알고 싶으신 분은 아래 링크를 참조해 주시기 바랍니다.

http://elecs.tistory.com/71


 

 Framework에 Java로 작성된 System Service에 대응하는 Native 코드를 작성한 후 이를 JNI 폴더에 저장만 한다고 해서 해당 Native 코드를 곧바로 사용할 수 없습니다. 해당 폴더 내에 있는 onload.cpp에 해당 코드 내에 있는 함수를 등록해야 사용할 수 있는데 만약 자신이 작성한 코드가 C일 경우 바로 연동할 수 없어 조금은 난감한 일이 벌어집니다.


아래 소스코드는 기존에 JNI로 만든 안드로이드 Native Service 코드입니다.


/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!");
}
cs



아래 소스코드는 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
#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;
}
 
cs


 위 두 코드를 통해 현재 C와 C++ 소스코드의 차이를 확연히 아실 수 있을 겁니다. C로 작성되었다면 extern을 활용해 외부의 함수를 호출할 수 있는 방법이 있겠습니다만, C++의 경우 namespace 라는 개념까지 가지고 있기 때문에 C++ 코드를 C코드 짜듯이 작성했다간 error가 작렬하는 상황이 벌어집니다.


 C++에서 C코드를 호출하는 것을 구현하기 위해 우선 두 소스코드가 공유하는 header 파일을 작성합니다.


/frameworks/base/services/jni/com_android_server_TestService.h

1
2
3
4
5
6
7
8
9
#include "jni.h"
#include "JNIHelp.h"
 
#ifndef _ANDROID_SERVER_TESTSERVICE_H
#define _ANDROID_SERVER_TESTSERVICE_H
 
int register_android_server_TestService(JNIEnv* env);
 
#endif
cs


작성된 header 파일을 각 소스코드에 include 해 주신 후 C++ 코드로부터 호출될 C 코드를 구현해 봅니다.


/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
#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)
{
    int res = jniRegisterNativeMethods(env, "com/android/server/TestService",
            gMethods, NELEM(gMethods));
 
    return 0;
}
cs

C 코드 내부에서 C++ 코드에서 호출될 int register_android_server_TestService(JNIEnv* env) 함수를 구현하였습니다. 이제 해당 함수를 C++에서 불러들이는 방법을 알아보도록 하겠습니다.


 C와 C++이 서로의 함수를 바로 사용할 수 없는 가장 큰 이유는 코드를 컴파일 할 때 C와 C++이 자신의 함수를 Linking 하는 방식이 다르기 때문입니다. C++의 경우 Overriding이 가능하여 같은 이름을 가진 함수 여러 개를 만들 수 있으나 C의 경우 단 하나의 유일한 이름의 함수를 가질 수 밖에 없습니다. 이렇게 기본적인 개념에 대한 차이가 크기 때문에 C++에서 C로 작성된 코드를 사용하기 위해서는 extern "C"를 사용해야 합니다.


1
2
3
extern "C" {
#include "com_android_server_TestService.h"
}
cs

 위의 경우 헤더파일이 C로 작성되어 있는데 해당 헤더파일이 C++에 적용되면 C++ 문법에 기반하여 컴파일이 동작하기 때문에 C로 작성된 함수를 호출할 수 없습니다. C++ 코드 내에 extern "C"라 작성된 부분은 해당 부분은 C언어에서 처럼 동작시키겠다는 의미로 동작하게 되므로 코드 내부에 해당 Header 파일을 위와 같은 방식으로 적용해 주시면 되겠습니다.


아래는 위에 작성된 코드를 적용한 후 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
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
#include "JNIHelp.h"
#include "jni.h"
extern "C" {
#include "com_android_server_TestService.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_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




위와 같이 코드를 작성하시면 C++에서 C로 작성된 코드를 손쉽게 사용할 수 있도록 구성하실 수 있습니다.

300x250

안드로이드 기기 간에 Wifi Socket 통신하기


※해당 포스팅의 내용은 Java로 작성되어 있습니다. 본 내용을 Kotlin으로 다시 설계하여 보다 향상된 버전의 내용을 확인하고 싶으신 분들은 아래의 주소로 이동해주세요.

https://elecs.tistory.com/345


 최근 사물인터넷이 주목을 받게 되면서 휴대전화 이외의 기기에 안드로이드 OS가 적용되는 사례가 증가하고 있습니다. 이로 인해 기존 휴대기기에서 사용되지 않던 안드로이드 Server라는 개념이 등장하기도 합니다. 이번 포스팅에서는 Wifi를 활용한 간단한 Wifi 통신에 대해 다루어볼까 합니다. 아래의 예제를 통해서 안드로이드 기기에서 Wifi socket 통신을 즐겨보는 기회를 가져보도록 하겠습니다.


먼저 AndroidMenifest.xml에 통신 관련 권한을 추가해 줍니다.


AndroidMenifest.xml

1
2
3
4
5
6
7
    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
cs


string.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<resources>
 
    <string name="app_name">FW</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="ip">IP</string>
    <string name="port">PORT</string>
    <string name="name">NAME</string>
    <string name="button1">Connect!</string>
    <string name="button2">Disconnect!</string>
    <string name="button3">Set Server!</string>
    <string name="button4">close Server!</string>
    <string name="button5">View info!</string> 
    <string name="button6">Msg</string> 
 
</resources>
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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    android:orientation="vertical" >
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <TextView
            android:id="@+id/textView1"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:text="@string/ip" />
 
        <EditText
            android:id="@+id/editText1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint=""           
            />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <TextView
            android:id="@+id/textView2"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:text="@string/port" />
        
        <EditText
            android:id="@+id/editText2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint=""           
            />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <TextView
            android:id="@+id/textView3"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:text="@string/name" />
        
        <EditText
            android:id="@+id/editText3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint=""           
            />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button1" />
 
        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button2" />
 
        <Button
            android:id="@+id/button6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button6" />
 
    </LinearLayout>
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
 
        <Button
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button3" />
 
        <Button
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button4" />
 
        <Button
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="OnClick"
            android:text="@string/button5" />
 
    </LinearLayout>
 
    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView" />
 
</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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package com.example.fw;
 
import android.app.Activity;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.text.format.Formatter;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
 
public class MainActivity extends Activity {
    private EditText et1, et2, et3;
    private TextView tv4;
    private Socket socket;
    private DataOutputStream writeSocket;
    private DataInputStream readSocket;
    private Handler mHandler = new Handler();
 
    private ConnectivityManager cManager;
    private NetworkInfo wifi;
    private ServerSocket serverSocket;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        et1 = (EditText) findViewById(R.id.editText1);
        et2 = (EditText) findViewById(R.id.editText2);
        et3 = (EditText) findViewById(R.id.editText3);
 
        tv4 = (TextView) findViewById(R.id.textView4);
 
        cManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
 
    }
 
    @SuppressWarnings("deprecation")
    public void OnClick(View v) throws Exception {
        switch (v.getId()) {
            case R.id.button1:
                (new Connect()).start();
                break;
            case R.id.button2:
                (new Disconnect()).start();
                break;
            case R.id.button3:
                (new SetServer()).start();
                break;
            case R.id.button4:
                (new CloseServer()).start();
                break;
            case R.id.button5:
                wifi = cManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
                if (wifi.isConnected()) {
                    WifiManager wManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
                    WifiInfo info = wManager.getConnectionInfo();
                    tv4.setText("IP Address : " + Formatter.formatIpAddress(info.getIpAddress()));
                } else {
                    tv4.setText("Disconnected");
                }
                break;
            case R.id.button6:
                (new sendMessage()).start();
        }
    }
 
    class Connect extends Thread {
        public void run() {
            Log.d("Connect""Run Connect");
            String ip = null;
            int port = 0;
 
            try {
                ip = et1.getText().toString();
                port = Integer.parseInt(et2.getText().toString());
            } catch (Exception e) {
                final String recvInput = "정확히 입력하세요!";
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
                });
            }
            try {
                socket = new Socket(ip, port);
                writeSocket = new DataOutputStream(socket.getOutputStream());
                readSocket = new DataInputStream(socket.getInputStream());
 
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast("연결에 성공하였습니다.");
                    }
 
                });
                (new recvSocket()).start();
            } catch (Exception e) {
                final String recvInput = "연결에 실패하였습니다.";
                Log.d("Connect", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    class Disconnect extends Thread {
        public void run() {
            try {
                if (socket != null) {
                    socket.close();
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast("연결이 종료되었습니다.");
                        }
                    });
 
                }
 
            } catch (Exception e) {
                final String recvInput = "연결에 실패하였습니다.";
                Log.d("Connect", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    class SetServer extends Thread {
 
        public void run() {
            try {
                int port = Integer.parseInt(et2.getText().toString());
                serverSocket = new ServerSocket(port);
                final String result = "서버 포트 " + port + " 가 준비되었습니다.";
 
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(result);
                    }
                });
 
                socket = serverSocket.accept();
                writeSocket = new DataOutputStream(socket.getOutputStream());
                readSocket = new DataInputStream(socket.getInputStream());
 
                while (true) {
                    byte[] b = new byte[100];
                    int ac = readSocket.read(b, 0, b.length);
                    String input = new String(b, 0, b.length);
                    final String recvInput = input.trim();
                    
                    if(ac==-1)
                        break;
 
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast(recvInput);
                        }
 
                    });
                }
                mHandler.post(new Runnable(){
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast("연결이 종료되었습니다.");
                    }
                    
                });
                serverSocket.close();
                socket.close();
            } catch (Exception e) {
                final String recvInput = "서버 준비에 실패하였습니다.";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
    
    class recvSocket extends Thread {
 
        public void run() {
            try {
                readSocket = new DataInputStream(socket.getInputStream());
 
                while (true) {
                    byte[] b = new byte[100];
                    int ac = readSocket.read(b, 0, b.length);
                    String input = new String(b, 0, b.length);
                    final String recvInput = input.trim();
                    
                    if(ac==-1)
                        break;
 
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast(recvInput);
                        }
 
                    });
                }
                mHandler.post(new Runnable(){
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast("연결이 종료되었습니다.");
                    }
                    
                });
            } catch (Exception e) {
                final String recvInput = "연결에 문제가 발생하여 종료되었습니다..";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    class CloseServer extends Thread {
        public void run() {
            try {
                if (serverSocket != null) {
                    serverSocket.close();
                    socket.close();
 
                    mHandler.post(new Runnable() {
 
                        @Override
                        public void run() {
                            // TODO Auto-generated method stub
                            setToast("서버가 종료되었습니다..");
                        }
                    });
                }
            } catch (Exception e) {
                final String recvInput = "서버 준비에 실패하였습니다.";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
    
    class sendMessage extends Thread {
        public void run() {
            try {
                byte[] b = new byte[100];
                b = "Hello, World!".getBytes();
                writeSocket.write(b);
                
            } catch (Exception e) {
                final String recvInput = "메시지 전송에 실패하였습니다.";
                Log.d("SetServer", e.getMessage());
                mHandler.post(new Runnable() {
 
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        setToast(recvInput);
                    }
 
                });
 
            }
 
        }
    }
 
    void setToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
 
    @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




※자신의 안드로이드기기를 Server로 설정하고 싶을떼

1.PORT에 자신이 설정하고자 하는 PORT 번호를 입력한다.

2.'Set Server!' 버튼을 누르면 해당 안드로이드 기기가 Server의 역할을 맡게 된다.

3.'View info!' 버튼을 눌러 해당 안드로이드 기기의 IP 주소를 확인한다.


※자신의 안드로이드 기기를 Client로 설정하고 싶을때

1.위에서 확인한 Server로 설정한 안드로이드 기기의 IP주소를 확인한 후 IP 칸에 해당 주소를 입력한다.

2.Server에서 설정한 Port 번호를 입력한다.

3.'Connect!'버튼을 눌러 접속을 시도한다.

300x250

USB를 연결한 후 Logcat이 바로 보이지 않을 때 해결방법

 간혹가다 안드로이드 기기를 USB에서 분리하였다가 연결한 후 Logcat을 보려 하였을 때 Logcat이 보이지 않는 경우가 종종 있습니다. 이 경우 DDMS를 키신 후 자신의 Device를 선택하는 메뉴에서 클릭을 하시면 손쉽게 해결하실 수 있습니다.




 eclipse 오른쪽 윗 부분의 아이콘을 보시면 Open Perspective라는 메시지가 보이는 아이콘이 있습니다. 해당 아이콘을 클릭하신 후


여기서 DDMS를 선택하신 후 확인 버튼을 누르시면 DDMS 선택메뉴가 추가된 것을 확인하실 수 있습니다. DDMS를 선택하시면 현재 컴퓨터에 연결된 기기의 리스트를 보실 수 있습니다. 만약 이 때 자신의 기기가 보이지 않을 경우 아래와 같이 view Menu를 클릭하신 후 'Reset adb'를 선택하시면 접속된 안드로이드 기기 리스트가 갱신되는 것을 확인하실 수 있습니다.



 이후 자신이 Logcat을 보고자 하는 기기를 선택하시면 Logcat이 정상적으로 동작하고 있는 모습을 확인하실 수 있습니다.



300x250

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

 안드로이드 프레임워크를 다루다 보면 Java로 작성된 서비스 부분 뿐 아니라 C/C++로 작성된 Native 서비스 부분을 건드려야 하는 경우가 있습니다. 이를 위해서는 C/C++과 Java 사이의 매개체인 JNI를 알아야 할 필요가 있습니다. Java를 활용한 프로그래밍이 상당히 편하기는 합니다만 Java는 가상머신 위에서 동작하기 때문에 하드웨어를 직접 컨트롤 하기에는 적합하지 못하다는 단점이 있습니다. JNI는 Java 소스코드로 C/C++로 작성된 코드를 호출할 수 있기 때문에 NDK에서 JNI는 필수요소라 보아도 될 겁니다.


 아래의 예제는 Java에서 C로 작성된 코드의 String을 받아 TextView에 띄우는 Java -> C 호출 방법과 C에서 Java로 작성된 코드를 호출하여 Toast를 작동시키는 C -> Java 호출 방법에 대해 알아보겠습니다.



CallJava.zip

위 코드는 안드로이드 4.4(API 19)를 기준으로 작성되었으며 적용된 기기는 LG Nexus7 Kitkat 4.4.4임을 알립니다.


먼저 MainActivity를 다음과 같이 작성합니다.


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
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;
    
    static {
        System.loadLibrary("hello");
    }
    
    public native String stringFromJNI();
    public native void callJava();
 
    @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();
        }
    }
    
    public static void testToast(){
        Toast.makeText(mContext, "Hello, Toast!", Toast.LENGTH_SHORT).show();
    }
    
}
 
 
 
cs

코드를 작성하실 때 주의하실 점은

안드로이드에서 C -> Java를 호출할 때 Java에서 호출될 함수는 static으로 설정하여야 합니다.


다음으로 Project 폴더에 jni 폴더를 추가합니다.



다음으로 jni 폴더 내에 Android.mk 파일과 C 소스코드를 생성한 후 다음과 같이 입력합니다.


Android.mk

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello
LOCAL_SRC_FILES    := hello.c

include $(BUILD_SHARED_LIBRARY)
cs


다음으로 C 코드를 작성해 보도록 합니다.

진행하기에 앞서 자신이 호출하고자 하는 Java 함수의 Signature를 알 필요가 있습니다. 이를 확인하기 위해 Terminal을 여신 후 자신의 Class가 있는 폴더로 이동하신 후 다음과 같은 명령어를 입력합니다.


$ javap -s MainActivity


아래에서 보시는 바와 같이 해당 함수의 Signature를 확인하실 수 있습니다. 이를 자신의 코드에 적용하시면 되겠습니다.




hello.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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!");
}
cs


다음으로 Eclipse에서 JNI를 NDK-BUILD할 수 있는 환경을 구축합니다.

이는 아래의 포스팅을 참조해 주시길 바랍니다.

http://dsnight.tistory.com/entry/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Eclipse%EC%97%90%EC%84%9C-NDKbuild-%ED%95%98%EA%B8%B0



위의 과정까지 진행하셨다면 다음과 같은 결과를 얻으실 수 있습니다.





300x250