티스토리 블로그를 반년간 하면서...

공대생의 팁 2015. 3. 12. 00:28

 안녕하세요. '늦깎이 공대생의 좌충우돌 이야기'를 연재하고 있는 흔한 공대생입니다!


 블로그를 시작한 지도 어느덧 반년이 넘어가고 있습니다. 그 동안 글을 쓰면서 많은 생각을 해 보았고 어떻게 하면 사람들에게 나의 주장을 좀 더 쉽게 이해할 수 있게 적을 수 있을까 하는 고민도 해보기도 합니다. 글을 쓰면서도 이 글이 과연 사람들에게 도움을 줄 수는 있을까 하는 생각도 해보고 내가 쓴 글을 통해 사람들이 제 글을 읽어주시길 바라는 마음도 들기도 했습니다.


 지금껏 블로그를 운영하면서 이렇게 블로그에 큰 애정을 쏟아본 것도 처음이고 블로그라는 사람들과 소통할 수 있다는 것도 저에게는 큰 재미 중 하나 이기도 합니다. 초기에 블로그가 개설되었을 당시에는 이틀에 한 명 들어올까 말까 하던 제 블로그가 현재는 하루에 50명 정도 방문해 주시고 계십니다. 지금까지 작성된 포스팅이 80개 정도 되는 것을 생각한다면 참으로 많은 분들에 제 블로그에 와주신다는 것에 가끔은 행복하기도 합니다.


 비록 하루에 만 명 단위로 사람들이 오는 블로그들에 비하면 초라하지만 제 포스팅이 하나 둘 늘어날 수록 방문해주시는 분들이 점점 늘어나고 있는 모습을 볼 때마다 항상 행복한 생각이 많이 듭니다. 이 자리를 빌어 제 블로그를 찾아와주신 분들께 진심으로 감사의 말씀을 드리고 싶습니다!


 처음 블로그를 만들던 당시에는 제 취미 중 하나인 여행기도 함께 포스팅을 해보려 했습니다만, 역시 공대생 답게 공부한 내용들에 대한 포스팅이 태반인건 다소 아쉬운 느낌이 듭니다. 저도 가끔은 계속 미루기만 하던 여행기를 다른 분들이 생각치 못한 관점으로 사람들에게 보여주고자 하는 생각도 조금은 들기도 합니다.


 그 덕분인지 제 포스팅을 읽고 제게 도움을 청하시는 공대생 분들의 사연을 접하기도 합니다. 심지어 같은 학교에 다니는 같은 작품을 만들고 있는 동료가 저에게 도움을 요청하였던 재미있는 일도 있었습니다. 저 또한 그 분들의 심정을 매우 잘 알기에 자신의 능력 내에서 많은 도움이 될 수 있게 도와주는 것도 어떻게 보면 블로그를 통한 소통의 방법이 아닐까 하는 생각이 듭니다.


 티스토리, 저에게 티스토리는 정말로 좋은 추억을 만들어 주었습니다. 다른 사람들에게 제 지식을 피력할 수 있었던 곳이었고, 저에게 다른 사람들의 이야기를 들려주는 장소가 되어주기도 했었습니다. 저에게 있어 티스토리는 제 일상에서 보지 못했던 사람의 일상을 보여주는 곳이 아닌가 생각합니다.


 비록 지금 저의 필력은 많이 모자릅니다만, 티스토리를 통해 글을 쓰는 데에 좀 더 많은 노력을 하게 되는 기회가 되었으면 합니다. 그리고 좀 더 많은 사람들과 교류하며 다양한 사람사는 이야기를 공유할 수 있는 곳이 되어 주기를 바랍니다!


 ※제게 큰 즐거움을 주셨던 티스토리 블로거 여러분들께 진심으로 감사드립니다!

 

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

[VMware] NX / XD is required. The processor must support and it must be enabled in the BIOS.

공대생의 팁 2015. 2. 21. 21:24

 올해 말에 정식으로 공개될 Windows 10의 체험판을 사용해 보기 위해 VMware를 설치한 후 Windows 10를 설치해보려고 하였을 때 다음과 같은 에러를 맞이하게 되었습니다.



 평소 VirtualBox만 사용하다 보니 이러한 오류는 생전 처음 보는 상황이었습니다. 확인해보니 자신의 CPU 설정에 문제가 있어 위와 같은 상황이 발생한 것이었습니다. Intel사의 CPU의 경우 XD, AMD사의 CPU의 경우 NX 비트 설정때문에 저러한 상황이 발생합니다.

 NX(Not eXecute)와 XD(eXecute Disable)은 모두 같은 것을 의미하는 것으로 프로그램 실행중 특정 비트를 통해 해당 프로그램을 실행하지 않도록 설정하는 Bit를 마련할 것인지를 묻는 것을 의미합니다.


 위와 같은 경고문이 뜨면서 VMware가 더이상 진행이 되지 않는 이유는 자신의 컴퓨터 CPU가 해당 기능을 설정하지 않았기 때문에 발생합니다. 이 설정은 자신의 컴퓨터의 BIOS 설정 모드를 통해 바꾸어 주실 수 있습니다. 컴퓨터를 켜는 순간 바로 BIOS 설정모드(대부분 F2 또는 F10)으로 들어가셔서 CPU 관련 설정 부분을 보시면 해당 설정 유무를 결정할 수 있는 메뉴를 찾으실 수 있습니다. 해당 메뉴를 Enabled로 설정하신 후 컴퓨터를 재부팅 하시면 VMware가 정상적으로 실행되는 것을 보실 수 있습니다.



 위 사진은 Samsung Magic Station에서의 BIOS 설정 화면을 나타낸 것입니다. 보이시는 대로 No Execute Protection을 Enabled로 설정해주시면 되겠습니다.

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

xml graphical layout가 정상적으로 동작하지 않을 때

 안드로이드 애플리케이션을 개발하던 도중 XML을 편집하고 graphicl layout을 확인한 순간 다음과 같은 메시지를 보게 되었습니다.



Exception raised during rendering:java.util.Locale.toLanguageTag()Ljava/lang/String;

Exception details are logged in Window > Show View > Error Log


이러한 경우 대개 2가지 경우로 보실 수 있습니다.

1.자신이 Target으로 한 버전의 SDK가 설치되어 있지 않아 Graphical Layout가 지원되지 않는 경우

2.다른 무언가에 의해 충돌이 될 경우


 거의 대부분의 경우가 1번의 경우가 많은 것으로 보입니다. 이를 해결하는 방법은 상당히 간단합니다. SDK Manager를 실행하여 자신의 프로젝트에 해당되는 프로젝트를 다운로드하여 설치하신 뒤 Eclipse를 다시 실행하시면 Graphical Layout가 정상적으로 동작하는 것을 확인해 보실 수 있습니다.



300x250