검색결과 리스트
글
안드로이드 프레임워크 프로그래밍(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(0, 0); } 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; } b = 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>(
이전에 보았던 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() 함수가 실행되는 과정에 대해서는 아래 링크를 참조해 주시길 바랍니다.
/frameworks/native/include/binder/IServiceManager.h
static android::sp<IServiceManager> asInterface(
/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에 대한 자세한 사항은 아래 포스팅을 참조해 주시기 바랍니다.
/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
'안드로이드 > 프레임워크' 카테고리의 다른 글
안드로이드 프레임워크 프로그래밍(16) [Native 단계에서의 Parcel 전송(2)] (0) | 2015.03.31 |
---|---|
안드로이드 프레임워크 프로그래밍(15) [Native 단계에서의 Parcel 전송(1)] (0) | 2015.03.30 |
안드로이드 Native 코드 분석 : DECLARE_META_INTERFACE() (0) | 2015.03.28 |
안드로이드 Native 코드 분석 : IMPLEMENT_META_INTERFACE() (0) | 2015.03.27 |
안드로이드 프레임워크 프로그래밍(13) [커널이미지 적용후 부팅화면만 나올 때 대처법] (0) | 2015.03.24 |
설정
트랙백
댓글
글
안드로이드 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() 함수가 있습니다. 해당 매크로함수에 대한 정리는 아래 포스팅을 확인해주시기 바랍니다.
'안드로이드 > 프레임워크' 카테고리의 다른 글
안드로이드 프레임워크 프로그래밍(15) [Native 단계에서의 Parcel 전송(1)] (0) | 2015.03.30 |
---|---|
안드로이드 프레임워크 프로그래밍(14) [서비스 매니저 생성 및 등록과정] (0) | 2015.03.29 |
안드로이드 Native 코드 분석 : IMPLEMENT_META_INTERFACE() (0) | 2015.03.27 |
안드로이드 프레임워크 프로그래밍(13) [커널이미지 적용후 부팅화면만 나올 때 대처법] (0) | 2015.03.24 |
안드로이드 프레임워크 프로그래밍(12) [IServiceManager 등록과정] (1) | 2015.03.23 |
설정
트랙백
댓글
글
안드로이드 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)
위 두 매크로 또한 제가 위에서 설명드린 바와 같이 해석을 해두신다면 원리를 쉽게 파악하실 수 있을 것입니다!
'안드로이드 > 프레임워크' 카테고리의 다른 글
안드로이드 프레임워크 프로그래밍(14) [서비스 매니저 생성 및 등록과정] (0) | 2015.03.29 |
---|---|
안드로이드 Native 코드 분석 : DECLARE_META_INTERFACE() (0) | 2015.03.28 |
안드로이드 프레임워크 프로그래밍(13) [커널이미지 적용후 부팅화면만 나올 때 대처법] (0) | 2015.03.24 |
안드로이드 프레임워크 프로그래밍(12) [IServiceManager 등록과정] (1) | 2015.03.23 |
안드로이드 프레임워크 프로그래밍(11) [JAVA 프레임워크와 Native 프레임워크 연결 원리] (2) | 2015.03.18 |