안드로이드 프레임워크 프로그래밍(17) [Binder 드라이버 호출과정]

 안드로이드 Binder 부분을 연구하는데 상당히 많은 지식이 필요하군요. 이제는 안드로이드 Application 단계에서는 상상도 못했던 linux 시스템 프로그래밍 단계까지 내려와보니 분석 난이도도 많이 높아졌음을 실감하고 있습니다.


 이번 포스팅에서는 안드로이드 IPC 통신의 핵심이라 할 수 있는 Binder 드라이버가 호출되는 과정을 살펴보겠습니다. 본 포스팅을 이해하기 위해서는 linux 단계에서의 시스템 프로그래밍을 이해하셔야 이해가 수월하실 듯 합니다. 그럼 분석을 시작해 보도록 하겠습니다.


 Binder 드라이버는 PrecessState가 생성될 때 함께 생성되는 구조로 되어 있습니다. 아래 코드를 보면서 이해해보도록 합시다.


/frameworks/native/libs/binder/ProcessState.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
ProcessState::ProcessState()
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        // XXX Ideally, there should be a specific define for whether we
        // have mmap (or whether we could possibly have the kernel module
        // availabla).
#if !defined(HAVE_WIN32_IPC)
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
#else
        mDriverFD = -1;
#endif
    }
 
    LOG_ALWAYS_FATAL_IF(mDriverFD < 0"Binder driver could not be opened.  Terminating.");
}
cs

  현재 ProcessSate가 생성되면서 Binder 드라이버와 System Service를 등록하기 위한 mVMStart 부분을 보고 계십니다. 여기서 우리는 바인더를 호출하는 open_driver() 부분을 살펴볼 것입니다.


mDriverFD(open_driver())

 open_driver() 함수는 binder의 file descriptor을 반환해줍니다. 이 함수를 통해 Binder가 호출됩니다.


/frameworks/native/libs/binder/ProcessState.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
static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR);
    if (fd >= 0) {
        fcntl(fd, F_SETFD, FD_CLOEXEC);
        int vers;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        if (result == -1) {
            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
            close(fd);
            fd = -1;
        }
        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
            ALOGE("Binder driver protocol does not match user space protocol!");
            close(fd);
            fd = -1;
        }
        size_t maxThreads = 15;
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
        if (result == -1) {
            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
        }
    } else {
        ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
    }
    return fd;
}
cs

 오늘 제가 주로 설명하게 될 코드입니다. 중요한 부분마다 자세하게 설명을 해 보도록 하겠습니다.


int fd = open("/dev/binder", O_RDWR);

 open() 함수를 통해 binder 파일을 생성합니다. 보시면 아시듯이 binder는 하나의 파일에 불과했다는 것을 아실 수 있습니다.


fcntl(fd, F_SETFD, FD_CLOEXEC);

 fd에 설정된 Binder를 Control 하는 함수 입니다. F_SETFD는 Binder인 fd에 FD_CLOEXEC 플래그를 설정하라는 의미입니다. 이를 설정해주었을 경우 exec를 통해 프로그램이 실행될 때 Binder가 close 되도록 해주는 역할을 합니다.


 FD_CLOEXEC 설정에 대해 좀 더 자세히 알고 싶으신 분은 아래 링크를 통해 확인바랍니다.(영문)

http://stackoverflow.com/questions/6125068/what-does-the-fd-cloexec-fcntl-flag-do


status_t result = ioctl(fd, BINDER_VERSION, &vers);

 Binder 의 입출력 설정을 바꾸어주는 역할을 합니다. BINDER_VERSION은 요청 코드 번호를 말하며, &vers는 포인터를 보내 값을 얻기 위해 사용합니다.


/bionic/libc/kernel/common/linux/binder.h

1
2
3
4
5
6
struct binder_version {
 signed long protocol_version;
};
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
....
#define BINDER_VERSION _IOWR('b'9struct binder_version)
cs


/bionic/libc/kernel/common/asm-generic/ioctl.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
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_SIZEBITS 14
#define _IOC_DIRBITS 2
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
#define _IOC_NONE 0U
#define _IOC_WRITE 1U
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define _IOC_READ 2U
#define _IOC(dir,type,nr,size)   (((dir) << _IOC_DIRSHIFT) |   ((type) << _IOC_TYPESHIFT) |   ((nr) << _IOC_NRSHIFT) |   ((size) << _IOC_SIZESHIFT))
extern unsigned int __invalid_size_argument_for_IOC;
#define _IOC_TYPECHECK(t)   ((sizeof(t) == sizeof(t[1]) &&   sizeof(t) < (1 << _IOC_SIZEBITS)) ?   sizeof(t) : __invalid_size_argument_for_IOC)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
....
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
cs

size_t maxThreads = 15;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
 Binder의 최대 쓰레드 수를 15개로 한 후 이를 ioctl을 통해 설정해주는 모습입니다.

return fd;

return을 통해 Binder의 File Description을 반환해 줍니다.

안드로이드 프레임워크 프로그래밍(16) [Native 단계에서의 Parcel 전송(2)]

  지난 포스팅에서 Native 단계에서 Parcel 클래스가 전송되는 과정에 대하여 다루어 보았었습니다. 이번 포스팅은 전송된 Parcel을 수신하고 해당 Parcel을 풀어보는 과정에 대해 분석해 보도록 하겠습니다.

 Parcel이 Native 단계에서 전송되는 과정에 대한 자세한 사항은 이전 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/90


지난 포스팅에서 Parcel이 전송되는 과정에 이어 Parcel이 Binder를 통해 수신되는 과정을 살펴보겠습니다.


/frameworks/native/libs/binder/IPCThreadState.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
60
61
62
63
64
65
66
67
68
69
70
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;
 
    while (1) {
        if ((err=talkWithDriver()< NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0continue;
        
        cmd = mIn.readInt32();
        
        IF_LOG_COMMANDS() {
            alog << "Processing waitForResponse Command: "
                << getReturnString(cmd) << endl;
        }
 
        switch (cmd) {    
....
        case BR_REPLY:
            {
                binder_transaction_data tr;
                err = mIn.read(&tr, sizeof(tr));
                ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
                if (err != NO_ERROR) goto finish;
 
                if (reply) {
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                        reply->ipcSetDataReference(
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(size_t),
                            freeBuffer, this);
                    } else {
                        err = *static_cast<const status_t*>(tr.data.ptr.buffer);
                        freeBuffer(NULL,
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(size_t), this);
                    }
                } else {
                    freeBuffer(NULL,
                        reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                        tr.data_size,
                        reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                        tr.offsets_size/sizeof(size_t), this);
                    continue;
                }
            }
            goto finish;
 
        default:
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }
    }
 
finish:
    if (err != NO_ERROR) {
        if (acquireResult) *acquireResult = err;
        if (reply) reply->setError(err);
        mLastError = err;
    }
    
    return err;
}
cs

 위에서 작성된 코드들 중 중요한 부분들에 대해 살펴보겠습니다.


err=talkWithDriver()

바인더 IPC 데이터가 저장된 mOut을 Binder에 전송하며 또한 수신된 데이터를 mIn에 저장하는 과정입니다.


cmd = mIn.readInt32();

 수신된 Binder를 확인하는 부분입니다. min 내에 저장된 값을 불러내어 Parcel의 상태값을 얻어냅니다. 이후 switch()문을 통해 수신된 Parcel를 처리하는 과정을 거치게 됩니다.


 cmd에는 바인더 드라이버로부터 온 Parcel 클래스를 읽어 오게 되며 해당 부분에는 BR_REPLY가 저장되 있습니다. switch() 문을 통해 BR_REPLY가 실행되며 binder_transaction_data의 값을 수신하게 됩니다.


reply->ipcSetDataReference()

포인터로 선언되었던 reply Parcel에 데이터를 저장하는 과정입니다. 해당 함수에 대해 자세히 들여다보도록 합니다.


/frameworks/native/libs/binder/Parcel.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
void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
    const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)
{
    size_t minOffset = 0;
    freeDataNoInit();
    mError = NO_ERROR;
    mData = const_cast<uint8_t*>(data);
    mDataSize = mDataCapacity = dataSize;
    //ALOGI("setDataReference Setting data size of %p to %lu (pid=%d)\n", this, mDataSize, getpid());
    mDataPos = 0;
    ALOGV("setDataReference Setting data pos of %p to %d\n"this, mDataPos);
    mObjects = const_cast<size_t*>(objects);
    mObjectsSize = mObjectsCapacity = objectsCount;
    mNextObjectHint = 0;
    mOwner = relFunc;
    mOwnerCookie = relCookie;
    for (size_t i = 0; i < mObjectsSize; i++) {
        size_t offset = mObjects[i];
        if (offset < minOffset) {
            ALOGE("%s: bad object offset %zu < %zu\n",
                  __func__, offset, minOffset);
            mObjectsSize = 0;
            break;
        }
        minOffset = offset + sizeof(flat_binder_object);
    }
    scanForFds();
}
 
 
cs


위 과정을 통해 reply Parcel의 값이 Binder에 의해 저장이 되었음을 확인하실 수 있습니다.

여기까지 실행하게 되었다면 이제 우리는 IServiceManager에서 transact() 함수를 호출했던 시점으로 다시 돌아가봅니다.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BpServiceManager : public BpInterface<IServiceManager>
{
public:
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }
....
    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

transact() 함수가 실행이 왼료되면 마지막으로 return 과정을 거치게 됩니다.


/frameworks/native/libs/binder/Parcel.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int32_t Parcel::readExceptionCode() const
{
  int32_t exception_code = readAligned<int32_t>();
  if (exception_code == EX_HAS_REPLY_HEADER) {
    int32_t header_start = dataPosition();
    int32_t header_size = readAligned<int32_t>();
    // Skip over fat responses headers.  Not used (or propagated) in
    // native code
    setDataPosition(header_start + header_size);
    // And fat response headers are currently only used when there are no
    // exceptions, so return no error:
    return 0;
  }
  return exception_code;
}
cs


 이로서 addService()가 Parcel을 마침으로서 하나의 Native System Service가 등록되는 과정을 살펴보았습니다. Parcel은 안드로이드 IPC통신의 핵심인 Binder에서 필히 사용되는 부분이므로 공부하시는 분들께서는 잘 참고해주셨으면 하네요.


 본 포스팅은 사실상 코드만 소개하다 보니 해당 내용이 상당히 복잡하게 보이실 겁니다. 해당 코드에 관련된 이미지 및 자세한 설명은 아래 출저의 책을 참조해주셨으면 합니다.


참고문헌 : 인사이드 안드로이드, 송형주 외 4인 저, 위키북스, 2010



안드로이드 프레임워크 프로그래밍(15) [Native 단계에서의 Parcel 전송(1)]


 지난 포스팅에서 ServiceManager에 등록이 되는 과정에 대해 살펴보았습니다. 이번 포스팅에서는 프로세스간에 데이터를 전송하는 기능인 Parcel에 대해서 알아보도록 하겠습니다.


 시작하기에 앞서 아래 링크를 클릭하여 Java 단계에서의 Parcel 전송에 대해 이해하신 후 본 포스팅을 보신다면 이해가 좀 더 쉬워질 것입니다.


 http://arsviator.blogspot.kr/2010/10/parcelable%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%A0%84%EB%8B%AC-object.html


 Parcel은 안드로이드에서 사용되는 프로세스간 데이터 전송 방식으로 자신의 프로세스에서 제작된 Parcel을 상대방의 프로세스에 전송한 후 전송받은 프로세스에서 받은 Parcel을 풀어서 안에 들어있는 데이터를 사용하는 방식이라 할 수 있습니다.

 

 A프로세스에서 Parcel을 통해 자신이 보내고자 하는 데이터를 만든 후 이를 IPC통신을 통해 B프로세스에서 Parcel 데이터를 읽을 수 있게 하고 이를 받은 B프로세서에서는 Parcel을 해체하는 작업을 거치게 됩니다. 이 때 주의할 점은 A프로세스에서 Parcel을 묶었을 때의 순서에 따라 진행해야 한다는 점입니다.


 대략적인 개념은 이 정도 한 후 실제 코드를 보면서 부연 설명을 덧붙여보도록 하겠습니다.


 지난 포스팅에서 우리는 ServiceManager에 등록되는 과정에서 Parcel이 사용되는 것을 보셨을 것입니다. 이제 Parcel이 해당 코드에서는 어떤 방식으로 사용되고 있는지 분석해봅니다.


/frameworks/native/libs/binder/IServiceManager.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BpServiceManager : public BpInterface<IServiceManager>
{
public:
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }
....
    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

 본 코드의 addService()는 안드로이드에 사용되는 서비스를 다른 프로세스에서 사용할 수 있게 Service Manager에 등록을 하는 과정을 나타냅니다. 이 과정을 거치면 Binder를 통해 등록된 Service를 사용할 수 있게 됩니다.


 위의 코드들을 대뜸 보면 아시듯이 data 라는 명의 Parcel 클래스에 write를 통하여 정보를 순서대로 집어넣고 있는 상황을 보고 계십니다. 여기서 각각의 줄마다 어떤 기능이 사용되고 있는지 확인해 보도록 합시다.


data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());

ServiceManager에 등록될 서비스의 RPC 헤더를 기록하는 부분입니다.

이 때 쓰이는 IServiceManager::getInterfaceDescriptor()은 해당 소스코드 내에서는 확인하기 어렵습니다. 그 이유는 IInterface.h 헤더 파일에 #define 매크로를 통해 정의되어 있기 때문인데요 이 부분에 대해서는 아래 포스팅을 참조해 주셨으면 합니다.


http://elecs.tistory.com/87


위에서 사용된 writeInterfaceToken() 함수는 Parcel 클래스에서 ServiceManager의 RPC 헤더를 저장하게 됩니다.

/frameworks/native/libs/binder/Parcel.cpp

1
2
3
4
5
6
7
8
// Write RPC headers.  (previously just the interface token)
status_t Parcel::writeInterfaceToken(const String16& interface)
{
    writeInt32(IPCThreadState::self()->getStrictModePolicy() |
               STRICT_MODE_PENALTY_GATHER);
    // currently the interface identification token is just its name as a string
    return writeString16(interface);
}
cs


data.writeString16(name);

해당 Service의 이름이 이 과정을 통해 등록됩니다.


/frameworks/native/libs/binder/Parcel.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
status_t Parcel::writeString16(const String16& str)
{
    return writeString16(str.string(), str.size());
}
 
status_t Parcel::writeString16(const char16_t* str, size_t len)
{
    if (str == NULL) return writeInt32(-1);
    
    status_t err = writeInt32(len);
    if (err == NO_ERROR) {
        len *= sizeof(char16_t);
        uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char16_t));
        if (data) {
            memcpy(data, str, len);
            *reinterpret_cast<char16_t*>(data+len) = 0;
            return NO_ERROR;
        }
        err = mError;
    }
    return err;
}
cs

data.writeStrongBinder(service);

Service Manager에 바인더를 등록하는 과정입니다.


/frameworks/native/libs/binder/Parcel.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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}
 
status_t flatten_binder(const sp<ProcessState>& proc,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;
    
    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    if (binder != NULL) {
        IBinder *local = binder->localBinder();
        if (!local) {
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.handle = handle;
            obj.cookie = NULL;
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = local->getWeakRefs();
            obj.cookie = local;
        }
    } else {
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = NULL;
        obj.cookie = NULL;
    }
    
    return finish_flatten_binder(binder, obj, out);
}
 
inline static status_t finish_flatten_binder(
    const sp<IBinder>& binder, const flat_binder_object& flat, Parcel* out)
{
    return out->writeObject(flat, false);
}
 
 
status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
    const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
    const bool enoughObjects = mObjectsSize < mObjectsCapacity;
    if (enoughData && enoughObjects) {
restart_write:
        *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;
        
        // Need to write meta-data?
        if (nullMetaData || val.binder != NULL) {
            mObjects[mObjectsSize] = mDataPos;
            acquire_object(ProcessState::self(), val, this);
            mObjectsSize++;
        }
        
        // remember if it's a file descriptor
        if (val.type == BINDER_TYPE_FD) {
            if (!mAllowFds) {
                return FDS_NOT_ALLOWED;
            }
            mHasFds = mFdsKnown = true;
        }
 
        return finishWrite(sizeof(flat_binder_object));
    }
 
    if (!enoughData) {
        const status_t err = growData(sizeof(val));
        if (err != NO_ERROR) return err;
    }
    if (!enoughObjects) {
        size_t newSize = ((mObjectsSize+2)*3)/2;
        size_t* objects = (size_t*)realloc(mObjects, newSize*sizeof(size_t));
        if (objects == NULL) return NO_MEMORY;
        mObjects = objects;
        mObjectsCapacity = newSize;
    }
    
    goto restart_write;
}
cs


data.writeInt32(allowIsolated ? 1 : 0);

allowIsolated 변수의 초기값은 False로 설정되어 있습니다.


/frameworks/native/libs/binder/Parcel.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
status_t Parcel::writeInt32(int32_t val)
{
    return writeAligned(val);
}
 
template<class T>
status_t Parcel::writeAligned(T val) {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
 
    if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
        *reinterpret_cast<T*>(mData+mDataPos) = val;
        return finishWrite(sizeof(val));
    }
 
    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}
cs


status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);

 transact() 함수를 실행함으로서 Parcel을 전송하게 됩니다. 전송보내게 될 data와 전송받게 될 reply 인 Parcel 클래스를 설정하신 후 실행하게 되면 시스템 서비스에 등록이 성공합니다.


/frameworks/native/include/binder/Binder.h

1
2
3
4
5
6
7
8
class BpRefBase : public virtual RefBase
{
protected:
....
    inline  IBinder*        remote()                { return mRemote; }
    inline  IBinder*        remote() const          { return mRemote; }
....
};
cs


위 과정을 통해 mRemote에 저장되어 있던 Binder가 반환됩니다.

여기서 ServiceManager는 BpBinder를 반환하게 됩니다.

해당 과정에 대해 자세히 알고 싶으신 분께서는 아래 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/89


BpBinder 클래스에서 transact() 함수가 동작되는 과정을 살펴보도록 하겠습니다.


/frameworks/native/include/binder/BpBinder.h
1
2
3
4
5
6
7
8
9
10
11
class BpBinder : public IBinder
{
public:
                        BpBinder(int32_t handle);
....
    virtual status_t    transact(   uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
....
}
cs


/frameworks/native/libs/binder/BpBinder.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
BpBinder::BpBinder(int32_t handle)
    : mHandle(handle)
    , mAlive(1)
    , mObitsSent(0)
    , mObituaries(NULL)
{
    ALOGV("Creating BpBinder %p handle %d\n"this, mHandle);
 
    extendObjectLifetime(OBJECT_LIFETIME_WEAK);
    IPCThreadState::self()->incWeakHandle(handle);
}
 
status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
 
    return DEAD_OBJECT;
}
cs

 위에서 보시게 되는 Parcel 클래스인 data는 정보를 받는 역할을, reply는 정보를 보내는 역할로 보시면 되겠습니다. 이 곳에서는 이번엔 IPCThreadState 클래스를 통해 Transact() 함수가 실행되고 있는 모습을 보고 계십니다. 해당 함수를 추적해 봅시다.


/frameworks/native/libs/binder/IPCThreadState.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
60
61
status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();
 
    flags |= TF_ACCEPT_FDS;
 
    IF_LOG_TRANSACTIONS() {
        TextOutput::Bundle _b(alog);
        alog << "BC_TRANSACTION thr " << (void*)pthread_self() << " / hand "
            << handle << " / code " << TypeCode(code) << ": "
            << indent << data << dedent << endl;
    }
    
    if (err == NO_ERROR) {
        LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
            (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }
    
    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }
    
    if ((flags & TF_ONE_WAY) == 0) {
        #if 0
        if (code == 4) { // relayout
            ALOGI(">>>>>> CALLING transaction 4");
        } else {
            ALOGI(">>>>>> CALLING transaction %d", code);
        }
        #endif
        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        #if 0
        if (code == 4) { // relayout
            ALOGI("<<<<<< RETURNING transaction 4");
        } else {
            ALOGI("<<<<<< RETURNING transaction %d", code);
        }
        #endif
        
        IF_LOG_TRANSACTIONS() {
            TextOutput::Bundle _b(alog);
            alog << "BR_REPLY thr " << (void*)pthread_self() << " / hand "
                << handle << ": ";
            if (reply) alog << indent << *reply << dedent << endl;
            else alog << "(none requested)" << endl;
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }
    
    return err;
}
cs


위 코드에서 Parcel과 연관된 기능에 대해 살펴보도록 하겠습니다.

writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);

바인더를 통해 Parcel을 전송할 준비를 해주는 함수로 보시면 되겠습니다.


/frameworks/native/libs/binder/IPCThreadState.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
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;
 
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;
    
    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } else if (statusBuffer) {
        tr.flags |= TF_STATUS_CODE;
        *statusBuffer = err;
        tr.data_size = sizeof(status_t);
        tr.data.ptr.buffer = statusBuffer;
        tr.offsets_size = 0;
        tr.data.ptr.offsets = NULL;
    } else {
        return (mLastError = err);
    }
    
    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));
    
    return NO_ERROR;
}
cs

 위의 코드에서 보시는 대로 본 함수에 전달된 인자값의 내용들이 binder_transaction_data 구조체 안에 저장되는 모습을 보실 수 있습니다.


 30번째 줄에 등장하는 mOut은 Parcel 클래스로 해당 클래스는 송신 용도로 쓰이는 Parcel 클래스로 이와 반대 역할을 하는 수신 용도로 쓰이는 Parcel 클래스인 mIn 클래스가 존재합니다.


/frameworks/native/include/binder/IPCThreadState.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class IPCThreadState
{
public:
    static  IPCThreadState*     self();
    static  IPCThreadState*     selfOrNull();  // self(), but won't instantiate
....
private:
                                IPCThreadState();
                                ~IPCThreadState();
....            
            Parcel              mIn;
            Parcel              mOut;
            status_t            mLastError;
            pid_t               mCallingPid;
            uid_t               mCallingUid;
            int32_t             mStrictModePolicy;
            int32_t             mLastTransactionBinderFlags;
....
};
 
 
cs


waitForResponse(NULL, NULL);

본 함수가 실행됨으로서 바인더를 통해 Parcel이 전송됩니다!


/frameworks/native/libs/binder/IPCThreadState.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
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
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;
 
    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0continue;
        
        cmd = mIn.readInt32();
        
        IF_LOG_COMMANDS() {
            alog << "Processing waitForResponse Command: "
                << getReturnString(cmd) << endl;
        }
 
        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
        
        case BR_DEAD_REPLY:
            err = DEAD_OBJECT;
            goto finish;
 
        case BR_FAILED_REPLY:
            err = FAILED_TRANSACTION;
            goto finish;
        
        case BR_ACQUIRE_RESULT:
            {
                ALOG_ASSERT(acquireResult != NULL, "Unexpected brACQUIRE_RESULT");
                const int32_t result = mIn.readInt32();
                if (!acquireResult) continue;
                *acquireResult = result ? NO_ERROR : INVALID_OPERATION;
            }
            goto finish;
        
        case BR_REPLY:
            {
                binder_transaction_data tr;
                err = mIn.read(&tr, sizeof(tr));
                ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
                if (err != NO_ERROR) goto finish;
 
                if (reply) {
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                        reply->ipcSetDataReference(
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(size_t),
                            freeBuffer, this);
                    } else {
                        err = *static_cast<const status_t*>(tr.data.ptr.buffer);
                        freeBuffer(NULL,
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(size_t), this);
                    }
                } else {
                    freeBuffer(NULL,
                        reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                        tr.data_size,
                        reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                        tr.offsets_size/sizeof(size_t), this);
                    continue;
                }
            }
            goto finish;
 
        default:
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }
    }
 
finish:
    if (err != NO_ERROR) {
        if (acquireResult) *acquireResult = err;
        if (reply) reply->setError(err);
        mLastError = err;
    }
    
    return err;
}
cs

 이번 코드는 전체적인 맥락에서 상당히 중요하다 판단하여 모두 기재해 보았습니다. 해당 함수에서 중요한 부분에 대해 자세히 설명해 드리도록 하겠습니다.

err=talkWithDriver()

바인더 IPC 데이터가 저장된 mOut을 Binder에 전송하며 또한 수신된 데이터를 mIn에 저장하는 과정입니다.

아래는 talkWithDriver() 함수가 구현된 모습입니다. 상당히 복잡하게 구성되어 있으므로 전체적인 기능만 보시고 넘어가시면 되겠습니다.


/frameworks/native/libs/binder/IPCThreadState.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
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
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }
    
    binder_write_read bwr;
    
    // Is the read buffer empty?
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    
    // We don't want to write anything if we are still reading
    // from data left in the input buffer and the caller
    // has requested to read the next data.
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
    
    bwr.write_size = outAvail;
    bwr.write_buffer = (long unsigned int)mOut.data();
 
    // This is what we'll read.
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (long unsigned int)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }
 
    IF_LOG_COMMANDS() {
        TextOutput::Bundle _b(alog);
        if (outAvail != 0) {
            alog << "Sending commands to driver: " << indent;
            const void* cmds = (const void*)bwr.write_buffer;
            const void* end = ((const uint8_t*)cmds)+bwr.write_size;
            alog << HexDump(cmds, bwr.write_size) << endl;
            while (cmds < end) cmds = printCommand(alog, cmds);
            alog << dedent;
        }
        alog << "Size of receive buffer: " << bwr.read_size
            << ", needRead: " << needRead << ", doReceive: " << doReceive << endl;
    }
    
    // Return immediately if there is nothing to do.
    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
 
    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        IF_LOG_COMMANDS() {
            alog << "About to read/write, write size = " << mOut.dataSize() << endl;
        }
#if defined(HAVE_ANDROID_OS)
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
#else
        err = INVALID_OPERATION;
#endif
        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }
        IF_LOG_COMMANDS() {
            alog << "Finished read/write, write size = " << mOut.dataSize() << endl;
        }
    } while (err == -EINTR);
 
    IF_LOG_COMMANDS() {
        alog << "Our err: " << (void*)err << ", write consumed: "
            << bwr.write_consumed << " (of " << mOut.dataSize()
                        << "), read consumed: " << bwr.read_consumed << endl;
    }
 
    if (err >= NO_ERROR) {
        if (bwr.write_consumed > 0) {
            if (bwr.write_consumed < (ssize_t)mOut.dataSize())
                mOut.remove(0, bwr.write_consumed);
            else
                mOut.setDataSize(0);
        }
        if (bwr.read_consumed > 0) {
            mIn.setDataSize(bwr.read_consumed);
            mIn.setDataPosition(0);
        }
        IF_LOG_COMMANDS() {
            TextOutput::Bundle _b(alog);
            alog << "Remaining data size: " << mOut.dataSize() << endl;
            alog << "Received commands from driver: " << indent;
            const void* cmds = mIn.data();
            const void* end = mIn.data() + mIn.dataSize();
            alog << HexDump(cmds, mIn.dataSize()) << endl;
            while (cmds < end) cmds = printReturnCommand(alog, cmds);
            alog << dedent;
        }
        return NO_ERROR;
    }
    
    return err;
}
cs


cmd = mIn.readInt32();

 수신된 Binder를 확인하는 부분입니다. min 내에 저장된 값을 불러내어 Parcel의 상태값을 얻어냅니다. 이후 switch()문을 통해 수신된 Parcel를 처리하는 과정을 거치게 됩니다.


=====

 이번 포스팅을 작성하다보니 내용이 상당히 길어져 버렸군요. 수신 과정만 분석하는데만 무려 1주일이라는 시간이 지나버렸군요. 사실 이 부분까지 접근을 하다보면 필연적으로 안드로이드 내의 linux 커널 부분까지 접근하게 되는데요 커널을 다루는 것은 본 포스팅에서 다루지 않도록 하겠습니다. 안드로이드 커널과 관련된 부분은 전문 서적등을 통해 정보를 얻으시면 좀 더 정확하고 빠른 이해가 가능하실 것이라 생각합니다.


다음포스팅에서는 전송된 Parcel을 수신한 후 처리하는 과정에 대해 알아보도록 하겠습니다.

http://elecs.tistory.com/91

안드로이드 프레임워크 프로그래밍(14) [서비스 매니저 생성 및 등록과정]

 이번 포스팅에서는 CameraService와 같이 중요한 Native System Service를 담당하는 ServiceManager가 생성되어 동작되는 과정에 대해 살펴보도록 하겠습니다.

 

 Native 단계에서 동작하는 ServiceManager가 초기에 생성되는 과정을 살펴보던 도중 이러한 코드를 발견하게 되었습니다.


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

1
2
3
4
5
6
7
8
9
class BpServiceManager : public BpInterface<IServiceManager>
{
public:
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }
....
}
cs

 위에서 보시는 바와 같이 BpServiceManager가 public으로 Constructor가 선언되어 있는 것을 보실 수 있습니다. 이로 보아 어딘가에서 이를 통해 ServiceManager를 생성할 것으로 추정하였으나 좀처럼 쉽지가 않았습니다.


 그렇다면 ServiceManager는 어떠한 방식으로 생성이 되는지 차근차근 살펴보도록 하겠습니다.


 먼저 우리는 이전 포스팅에서 CameraService 클래스가 ServiceManager에 등록되어지는 과정을 확인한 바 있습니다.


http://elecs.tistory.com/83 - 안드로이드 프레임워크 프로그래밍(12) [IServiceManager 등록과정]


사실 위 코드에서는 ServiceManager의 생성과정이 포함되어 있음을 확인할 수 있었습니다.

/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
int main(int argc, char** argv)
{
....
    if (doLog && (childPid = fork()) != 0) {
....
    } else {
        // all other services
        if (doLog) {
            prctl(PR_SET_PDEATHSIG, SIGKILL);   
            setpgid(00);                      
        }
        sp<ProcessState> proc(ProcessState::self());
        sp<IServiceManager> sm = defaultServiceManager();
        ALOGI("ServiceManager: %p", sm.get());
        AudioFlinger::instantiate();
        MediaPlayerService::instantiate();
        CameraService::instantiate();
        AudioPolicyService::instantiate();
        registerExtensions();
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
    }
}
cs

위에서 중요한 코드 별로 하나씩 살펴 보도록 하겠습니다.


sp<ProcessState> proc(ProcessState::self());

 ProcessState 클래스 변수를 선언합니다. 이 때 ProcessState의 Constructor의 선언은 public이 아니기 때문에 따로 마련된 함수 self()를 통해 선언합니다.


/frameworks/native/libs/binder/ProcessState.cpp

1
2
3
4
5
6
7
8
9
sp<ProcessState> ProcessState::self()
{
    Mutex::Autolock _l(gProcessMutex);
    if (gProcess != NULL) {
        return gProcess;
    }
    gProcess = new ProcessState;
    return gProcess;
}
cs

 위의 방식으로 ProcessState가 존재하면 바로 return으로 값을 넘기고 없을 경우 Constructor를 새로 생성해냅니다.


/frameworks/native/libs/binder/ProcessState.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
ProcessState::ProcessState()
    : mDriverFD(open_driver())
    , mVMStart(MAP_FAILED)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        // XXX Ideally, there should be a specific define for whether we
        // have mmap (or whether we could possibly have the kernel module
        // availabla).
#if !defined(HAVE_WIN32_IPC)
        // mmap the binder, providing a chunk of virtual address space to receive transactions.
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
#else
        mDriverFD = -1;
#endif
    }
 
    LOG_ALWAYS_FATAL_IF(mDriverFD < 0"Binder driver could not be opened.  Terminating.");
}
cs

 위 코드를 살펴보았을 때 mmap() 를 통하여 메모리를 할당하고 있는 모습을 보실 수 있는데, 이 과정을 통해 바인더 드라이버가 프로세스에게 바인더 RPC 데이터를 저장할 때 사용하는 영역을 확보하실 수 있습니다.


sp<IServiceManager> sm = defaultServiceManager();

 위 코드를 통하여 BpServiceManager를 생성하게 됩니다. 해당 과정을 좀 더 자세히 살펴보면 다음과 같습니다.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
    
    {
        AutoMutex _l(gDefaultServiceManagerLock);
        while (gDefaultServiceManager == NULL) {
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
            if (gDefaultServiceManager == NULL)
                sleep(1);
        }
    }
    
    return gDefaultServiceManager;
}
cs

 위에서 볼 수 있는 gDefaultServiceManager는 ServiceManager의 값을 가지는 변수입니다. 이 변수에 BpServiceManager가 저장되는데 해당 코드가 구현되는 과정을 면밀히 살펴보도록 하겠습니다.


ProcessState::self()->getContextObject(NULL)

/frameworks/native/libs/binder/ProcessState.cpp

1
2
3
4
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)
{
    return getStrongProxyForHandle(0);
}
cs

 내부를 살펴보니 또다시 return 값으로 함수가 주어져 있는 것을 보실 수 있습니다.


getStrongProxyForHandle(0);

/frameworks/native/libs/binder/ProcessState.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
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
 
    AutoMutex _l(mLock);
 
    handle_entry* e = lookupHandleLocked(handle);
 
    if (e != NULL) {
        // We need to create a new BpBinder if there isn't currently one, OR we
        // are unable to acquire a weak reference on this current one.  See comment
        // in getWeakProxyForHandle() for more info about this.
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            if (handle == 0) {
                // Special case for context manager...
                // The context manager is the only object for which we create
                // a BpBinder proxy without already holding a reference.
                // Perform a dummy transaction to ensure the context manager
                // is registered before we create the first local reference
                // to it (which will occur when creating the BpBinder).
                // If a local reference is created for the BpBinder when the
                // context manager is not present, the driver will fail to
                // provide a reference to the context manager, but the
                // driver API does not return status.
                //
                // Note that this is not race-free if the context manager
                // dies while this code runs.
                //
                // TODO: add a driver API to wait for context manager, or
                // stop special casing handle 0 for context manager and add
                // a driver API to get a handle to the context manager with
                // proper reference counting.
 
                Parcel data;
                status_t status = IPCThreadState::self()->transact(
                        0, IBinder::PING_TRANSACTION, data, NULL, 0);
                if (status == DEAD_OBJECT)
                   return NULL;
            }
 
            = new BpBinder(handle); 
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            // This little bit of nastyness is to allow us to add a primary
            // reference to the remote proxy when this team doesn't have one
            // but another team is sending the handle to us.
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }
 
    return result;
}
cs

/frameworks/native/include/binder/ProcessState.h

1
2
3
4
5
6
7
8
9
10
11
12
13
            struct handle_entry {
                IBinder* binder;
                RefBase::weakref_type* refs;
            };
            
            handle_entry*       lookupHandleLocked(int32_t handle);
 
            int                 mDriverFD;
            void*               mVMStart;
            
    mutable Mutex               mLock;  // protects everything below.
            
            Vector<handle_entry>mHandleToObject;
cs


/frameworks/native/libs/binder/ProcessState.cpp

1
2
3
4
5
6
7
8
9
10
11
12
ProcessState::handle_entry* ProcessState::lookupHandleLocked(int32_t handle)
{
    const size_t N=mHandleToObject.size();
    if (N <= (size_t)handle) {
        handle_entry e;
        e.binder = NULL;
        e.refs = NULL;
        status_t err = mHandleToObject.insertAt(e, N, handle+1-N);
        if (err < NO_ERROR) return NULL;
    }
    return &mHandleToObject.editItemAt(handle);
}
cs

 위 함수는 인자값으로 서비스의 handle값을 전달해 호출하는 역할을 하고 있습니다. 해당 코드에서 사용된 핸들값인 0은 ServiceManager에 고정된 서비스의 핸들입니다. 이 함수를 통해 ServiceManager의 Service Handle을 가진 BpBinder의 instance를 생성한 후에 포인터를 반환하는 작업이 이루어집니다. 즉, 해당 함수를 호출함으로서 mHandle 변수의 값이 0인 BpBinder 객체가 생성되는 것이지요.


 일단 위 과정을 통해 ProcessState::self()->getContextObject(NULL)이 BpBinder가 생성되었음을 직접 확인할 수 있었습니다. 이번에는 BpServiceManager가 생성되는 과정에 대해 알아보도록 하겠습니다.


gDefaultServiceManager = interface_cast<IServiceManager>(

                ProcessState::self()->getContextObject(NULL));


 이전에 보았던 IServiceManager에서 작성되었던 코드입니다. 이번에는 이 곳에서 interface_cast() 코드 부분을 살펴보도록 하겠습니다.

/frameworks/native/include/binder/IInterface.h

1
2
3
4
5
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}
cs

 현재 interface_cast() 함수가 template로 선언되어 있는 모습을 보고 계십니다. 여기서 INTERFACE는 정황상 IServiceManager이고, 즉 이 곳에서는 IServiceManager의 asInterface() 함수가 실행되고 있는 모습을 보실 수 있습니다. asInterface() 함수가 실행되는 과정에 대해서는 아래 링크를 참조해 주시길 바랍니다.


http://elecs.tistory.com/87

http://elecs.tistory.com/88


/frameworks/native/include/binder/IServiceManager.h

 static android::sp<IServiceManager> asInterface(                      

            const android::sp<android::IBinder>& obj);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android::sp<IServiceManager> IServiceManager::asInterface(                           
    const android::sp<android::IBinder>& obj)                       
    {
        android::sp<IServiceManager> intr;
        if (obj != NULL) {
            intr = static_cast<IServiceManager*>(
                obj->queryLocalInterface(
                    IServiceManager::descriptor).get());
        if (intr == NULL) {
            intr = new BpServiceManager(obj);
        }
    }
    return intr;
cs

위 과정을 통해 ServiceManager가 BpServiceManager의 상태로 서비스에 등록되고 있는 과정을 확인하였습니다.

여기서 잠시 맨 처음에 제시하였던 코드로 다시 돌아가 보도록 하겠습니다.


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

1
2
3
4
5
6
7
8
9
class BpServiceManager : public BpInterface<IServiceManager>
{
public:
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }
....
}
cs

 위에서 선언하였던 BpServiceManager() 생성자가 new 를 통해 생성되고 있는데 이 이후의 과정을 살펴보도록 합시다.


/frameworks/native/include/binder/IInterface.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
                                BpInterface(const sp<IBinder>& remote);
 
protected:
    virtual IBinder*            onAsBinder();
};
 
template<typename INTERFACE>
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
    : BpRefBase(remote)
{
}
cs

/frameworks/native/libs/binder/Binder.cpp
1
2
3
4
5
6
7
8
9
10
BpRefBase::BpRefBase(const sp<IBinder>& o)
    : mRemote(o.get()), mRefs(NULL), mState(0)
{
    extendObjectLifetime(OBJECT_LIFETIME_WEAK);
 
    if (mRemote) {
        mRemote->incStrong(this);           // Removed on first IncStrong().
        mRefs = mRemote->createWeak(this);  // Held for our entire lifetime.
    }
}
cs


위 BpRefBase에서 mRemote가 초기화 되고 있는 모습을 보실 수 있습니다. 이 과정을 통해 BpBinder가 mRemote에 저장됩니다. 이때

mRemote(o.get())

 에서 get() 함수는 sp<>인 Smart Pointer입니다. Smart Pointer에 대한 자세한 사항은 아래 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/79


/frameworks/rs/cpp/util/StrongPointer.h

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 클래스 내의 함수 get()의 정의 대로 Smart Pointer 내에 저장되어 있는 BpBinder의 포인터를 mRemote에 저장하고 있는 상황인 것을 아실 수 있습니다.


참고문헌 : 인사이드 안드로이드, 송형주 외 4인 저, 위키북스, 2010

안드로이드 Native 코드 분석 : DECLARE_META_INTERFACE()

 안드로이드 Binder 부분에 대해 공부를 하시다 보면 다음과 같은 함수를 만나게 되는 경우가 있습니다.

/frameworks/native/include/binder/IServiceManager.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
class IServiceManager : public IInterface
{
public:
    DECLARE_META_INTERFACE(ServiceManager);
 
    /**
     * Retrieve an existing service, blocking for a few seconds
     * if it doesn't yet exist.
     */
    virtual sp<IBinder>         getService( const String16& name) const = 0;
 
    /**
     * Retrieve an existing service, non-blocking.
     */
    virtual sp<IBinder>         checkService( const String16& name) const = 0;
 
    /**
     * Register a service.
     */
    virtual status_t            addService( const String16& name,
                                            const sp<IBinder>& service,
                                            bool allowIsolated = false= 0;
 
    /**
     * Return list of all existing services.
     */
    virtual Vector<String16>    listServices() = 0;
 
    enum {
        GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
        CHECK_SERVICE_TRANSACTION,
        ADD_SERVICE_TRANSACTION,
        LIST_SERVICES_TRANSACTION,
    };
};
cs

 위 함수를 처음 만났을 때 많은 분들께서 처음에는 상당히 의야해 하실 것으로 예상합니다. 사실 저것은 함수가 아니라 #define으로 정의된 매크로 함수입니다.

 해당 부분에서 MACRO 함수를 사용하는 이유는 Native의 System Service들의 Binder를 설계하다 보면 각 서비스 별로 바인더를 설계해야 하는 경우가 발생하게 됩니다. 즉, 위에 사용된 매크로 함수는 해당 서비스들의 코드를 작성하기엔 양이 많기 때문에 위와 같은 방법을 사용하여 코드 용량을 줄인 것이라고 이해하셔도 될 듯 합니다.

 이와 같은 용도로 IMPLEMENT_META_INTERFACE() 함수도 안드로이드에서는 쓰이고 있습니다. 그렇다면 DECLARE_META_INTERFACE() 함수가 어떻게 선언되어 있는지 살펴보도록 하겠습니다.


/frameworks/native/include/binder/IInterface.h

1
2
3
4
5
6
7
#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();                                            \
cs


위 Macro 코드가 위 코드에서는 다음과 같이 적용됨을 알 수 있습니다.

/frameworks/native/include/binder/IServiceManager.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
class IServiceManager : public IInterface
{
public:
    static const android::String16 descriptor;                         
    static android::sp<IServiceManager> asInterface(                      
            const android::sp<android::IBinder>& obj);                 
    virtual const android::String16& getInterfaceDescriptor() const;   
    IServiceManager();                                                    
    virtual ~IServiceManager();                                          
 
    /**
     * Retrieve an existing service, blocking for a few seconds
     * if it doesn't yet exist.
     */
    virtual sp<IBinder>         getService( const String16& name) const = 0;
 
    /**
     * Retrieve an existing service, non-blocking.
     */
    virtual sp<IBinder>         checkService( const String16& name) const = 0;
 
    /**
     * Register a service.
     */
    virtual status_t            addService( const String16& name,
                                            const sp<IBinder>& service,
                                            bool allowIsolated = false= 0;
 
    /**
     * Return list of all existing services.
     */
    virtual Vector<String16>    listServices() = 0;
 
    enum {
        GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
        CHECK_SERVICE_TRANSACTION,
        ADD_SERVICE_TRANSACTION,
        LIST_SERVICES_TRANSACTION,
    };
};
cs


 위와 같은 원리로 안드로이드 Binder 관련 MACRO 함수로 IMPLEMENT_META_INTERFACE() 함수가 있습니다. 해당 매크로함수에 대한 정리는 아래 포스팅을 확인해주시기 바랍니다.

http://elecs.tistory.com/87

안드로이드 Native 코드 분석 : IMPLEMENT_META_INTERFACE()

 안드로이드 프레임워크를 분석하는 과정에서 시스템 서비스와 관련된 부분에 대해 분석을 하다보면 다음과 같은 이름의 함수를 만나보실 수 있을 것입니다.


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

IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager");


 위 함수는 겉으로 보기에는 뭔가 거창한 듯한 함수처럼 보입니다만 사실은 #define으로 정의된 매크로입니다. 이 매크로는 header 파일인 IInterface.h에 선언되어 있음을 확인하실 수 있습니다.


/frameworks/native/include/binder/IInterface.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
    I##INTERFACE::~I##INTERFACE() { }                                   \
 
cs

 보시는 바와 같이 #define을 통해 정의되어 있는 것을 확인하실 수 있습니다. 이 매크로에서 맨 오른쪽 부분에 BackSlash가 처리되어 있는 것은 해당 매크로 #define이 한 줄에서 끝나지 않고 있음을 의미합니다. 그리고 내용을 확인하시다 보면 다음과 같은 내용이 있습니다.


I##INTERFACELLI##INTERFACE() { }


 여기서 '##'이란 해당 기호 앞뒤에 있는 글자를 곧바로 붙여쓰라는 의미로 이해해 주신다면 쉽게 넘어가실 수 있으리라 생각합니다.

 INTERFACE가 'ServiceManager', Name이 '"android.os.IServiceManager"'로 정의되었다는 가정하에 Macro의 결과물은 다음과 같이 나타낼 수 있습니다.


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    const android::String16 IServiceManager::descriptor("android.os.IServiceManager");
    const android::String16&                                            
            IServiceManager::getInterfaceDescriptor() const {              
        return IServiceManager::descriptor;                                
    }                                                                   
    android::sp<IServiceManager> IServiceManager::asInterface(                
            const android::sp<android::IBinder>& obj)                   
    {                                                                   
        android::sp<IServiceManager> intr;                                 
        if (obj != NULL) {                                              
            intr = static_cast<IServiceManager*>(                          
                obj->queryLocalInterface(                               
                        IServiceManager::descriptor).get());               
            if (intr == NULL) {                                         
                intr = new BpServiceManager(obj);                          
            }                                                           
        }                                                               
        return intr;                                                    
    }                                                                   
    IServiceManager::IServiceManager() { }                                    
    IServiceManager::~IServiceManager() { }                                  
cs

Macro를 해독하면 다음과 같은 코드가 적용되고 있음을 알 수 있습니다.

 이 함수가 어떤 부분에서 중요하다는 의미인지 지금까지 설명드린 것 만으로는 충분히 이해하실 수 없으실 겁니다. 하지만 아래의 코드를 확인해 보신다면 단박에 위 매크로의 용도를 눈치채실 수 있을 겁니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BpServiceManager : public BpInterface<IServiceManager>
{
public:
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }
....
 
    virtual sp<IBinder> checkService( const String16& name) const
    {
        Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
        return reply.readStrongBinder();
    }
....
}
cs


 처음에는 저 IServiceManager 내에 있는 getInterfaceDescriptor() 함수를 찾아보았는데 전혀 찾아낼 수가 없어서 다른 곳에서 이리저리 찾아봤건만 저렇게 Macro를 통해 구현되어 있다는 것은 꿈에도 생각하지 못했습니다! 참으로 안드로이드의 세계는 알거 같으면서도 어려운 구조로군요.

 안드로이드 프레임워크 소스 내에는 위에서 소개드렸던 매크로 외에도 다음과 같이 2종류의 Macro가 존재합니다.


#define DECLARE_META_INTERFACE(INTERFACE) 

#define CHECK_INTERFACE(interface, data, reply)


 위 두 매크로 또한 제가 위에서 설명드린 바와 같이 해석을 해두신다면 원리를 쉽게 파악하실 수 있을 것입니다!

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

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


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



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


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


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


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


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

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

 처음 안드로이드 프레임워크를 분석하게 되었을 때엔 적어도 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






  • 범블비 2015.03.25 08:19 ADDR 수정/삭제 답글

    안녕하세요
    오늘도 좋은정보 감사합니다.
    안드로이드 프레임워크 관련 블루투스 분석은 안하시나요??^^

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

 어느덧 안드로이드 프레임워크를 연구한지 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


  • 범블비 2015.03.19 20:26 ADDR 수정/삭제 답글

    안녕하세요 ^^
    프레임워크 관련 글 잘보았습니다.
    질문이 있는데 안드로드 프레임워크 수정을 해서 블루투스 핸드프리 시험을 하려고합니다.
    즉, 블루투스 핸드프리 프로파일은 전화 걸기 전화 받기 음성통화 기능이 있는데 일반 스마트폰 같은경우는 유심이 있어 기능 구현이 가능하지만 유심이 없는 핸드폰에서는 위와 같은 기능을 구현 하려면 할어떻게 해야할지 고민중입니다.
    염치 없지만 조언좀 얻을수 있을까요??
    복받으실겁니다.~~

    • Justin T. 2015.03.19 21:20 신고 수정/삭제

      환영합니다!
      블루투스는 안드로이드에게 있어 상당히 이상적인 통신방식 중 하나라고 생각합니다. 별다른 기기를 거치지 않고도 안드로이드와 직접 통신을 할 수 있다는 점이 저에게는 인상깊었지요.

      사실 블루투스에 연결되는 기기들은 SIM카드 유무와는 상관없이 사용하는 것이 가능합니다. 거의 대부분의 핸즈프리가 안드로이드 기기 내에 설계된 프로그램이 SIM 카드 유무에 따라 동작을 수행하도록 되어 있기에 그런겁니다.

      시작하기에 앞서 핸즈프리를 이용하는 프로그램을 DDMS를 통해 traceview를 하셔서 어떻게 동작하는지를 살펴보셨으면 합니다. 프로그램 수행 도중 SIM 카드 유무에 대해 판단하는 부분을 발견하실 수 있으실 것이고 해당 부분을 잘 처리 해주신다면 SIM 없이도 핸드프리를 활용한 프로그램을 제작하시는 데에 어려움은 없으실 것이라 생각합니다.

안드로이드 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에 연결된 포인터에 접근에 관하여 다루고 있는 모습입니다.


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


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

 짫은 시간에 화면에 다양한 변화를 나타내는 데에 사용되는 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 인터페이스를 통해 선언한 함수들이 실행되는 것을 보실 수 있습니다.


  • 노태규 2016.02.28 12:33 ADDR 수정/삭제 답글

    원론적인걸 좋아하는 저에겐 너무나도 좋은 자료네요

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

 지금까지 프레임워크 프로그래밍을 진행하면서 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를 호출하는 기능을 구현하는데 성공하였음을 확인하실 수 있습니다.