IPC 메커니즘 Binder 분석
o Ibinder 커넥터
Ibinder 인터페이스는 여러 프로세스의 객체에 대한 추상화입니다.일반 대상은 현재 프로세스에서 접근할 수 있습니다. 대상이 다른 프로세스에 접근하기를 원한다면 Ibinder 인터페이스를 실현해야 합니다.Ibinder 인터페이스는 로컬 대상을 가리킬 수도 있고 원격 대상을 가리킬 수도 있으며 호출자는 가리키는 대상이 로컬인지 원격인지 신경 쓸 필요가 없다.
transact는 Ibinder 인터페이스에서 가장 중요한 함수로, 이 함수의 원형은 다음과 같습니다.
virtual status_t transact(
uint32_t code,
const
Parcel&
data,
Parcel*
reply,
uint32_t flags =
0
)
=
0
;
android의 IPC 기본 모델은 클라이언트/서버(C/S) 구조를 바탕으로 한다.
클라이언트
커널 모듈을 통해 중계 요청
서비스 단말기
만약 Ibinder가 클라이언트 에이전트를 가리킨다면,transact는 서버에 요청만 보낼 것입니다.서비스 측의 Ibinder의transact는 실제 서비스를 제공했다.
o 클라이언트
BpBinder는 현재 프로세스에서 원격 대상을 위한 에이전트입니다. Ibinder 인터페이스를 실현합니다.transact 함수는 다음과 같습니다.
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;
}
매개변수 설명:
transact는 IPCthreadState::self ()의transact만 간단하게 호출하고 IPCthreadState::transact에서:
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
(
reply)
{
err =
waitForResponse(
reply)
;
}
else
{
Parcel fakeReply;
err =
waitForResponse(
&
fakeReply)
;
}
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;
}
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
(
)
==
0
)
continue
;
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:
{
LOG_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)
)
;
LOG_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(
tr.data
.ptr
.buffer
)
,
tr.data_size
,
reinterpret_cast(
tr.data
.ptr
.offsets
)
,
tr.offsets_size
/
sizeof
(
size_t)
,
freeBuffer,
this)
;
}
else
{
err =
*
static_cast(
tr.data
.ptr
.buffer
)
;
freeBuffer(
NULL,
reinterpret_cast(
tr.data
.ptr
.buffer
)
,
tr.data_size
,
reinterpret_cast(
tr.data
.ptr
.offsets
)
,
tr.offsets_size
/
sizeof
(
size_t)
,
this)
;
}
}
else
{
freeBuffer(
NULL,
reinterpret_cast(
tr.data
.ptr
.buffer
)
,
tr.data_size
,
reinterpret_cast(
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;
}
여기transact는 요청을 내부 모듈을 통해 서버에 보냈습니다. 서버에서 요청을 처리한 후 원래의 경로를 따라 결과를 호출자에게 되돌려줍니다.요청이 동기화 작업임을 알 수 있습니다. 결과가 돌아올 때까지 기다립니다.
BpBinder 위에서 간단한 포장을 하면 우리는 서비스 대상과 같은 인터페이스를 얻을 수 있고 호출자는 호출 대상이 원격인지 로컬인지에 관심을 가질 필요가 없다.Service Manager의 경우: (frameworks/base/libs/utils/IServiceManager.cpp)
class BpServiceManager :
public BpInterface
{
public:
BpServiceManager(
const
sp&
impl)
:
BpInterface(
impl)
{
}
...
virtual
status_t addService(
const
String16&
name,
const
sp&
service)
{
Parcel data,
reply;
data.writeInterfaceToken
(
IServiceManager::
getInterfaceDescriptor
(
)
)
;
data.writeString16
(
name)
;
data.writeStrongBinder
(
service)
;
status_t err =
remote(
)
->
transact(
ADD_SERVICE_TRANSACTION,
data,
&
reply)
;
return
err ==
NO_ERROR ?
reply.readInt32
(
)
:
err;
}
...
}
;
BpService Manager는 IService Manager와 Ibinder 두 인터페이스를 실현하고 호출자는 BpService Manager의 대상을 하나의 IService Manager 대상이나 Ibinder 대상으로 볼 수 있다.호출자가 BpService Manager 대상을 IService Manager 대상으로 사용할 때, 모든 요청은 BpBinder::transact에 대한 봉인일 뿐입니다.이러한 봉인으로 인해 호출자는 IService Manager의 대상이 로컬인지 원격인지 신경 쓸 필요가 없다.
고객은defaultService Manager 함수를 사용하여 BpService Manager 객체를 만듭니다. (frameworks/base/libs/utils/IServiceManager.cpp)
sp<
IServiceManager>
defaultServiceManager(
)
{
if
(
gDefaultServiceManager !=
NULL)
return
gDefaultServiceManager;
{
AutoMutex _l(
gDefaultServiceManagerLock)
;
if
(
gDefaultServiceManager ==
NULL)
{
gDefaultServiceManager =
interface_cast<
IServiceManager>
(
ProcessState::
self
(
)
->
getContextObject(
NULL)
)
;
}
}
return
gDefaultServiceManager;
}
ProcessState::self()->getContextObject(NULL)를 통해 Binder 대상을 만들고interfacecast 및 IMPLEMENTMETA_INTERFACE(Service Manager, android.os.IService Manager)는 Binder 대상을 IService Manager 대상으로 포장합니다.원리적으로 BpService Manager 객체가 만들어진 것과 같습니다.
ProcessState:::self()->getContextObject 호출ProcessState::getStrongProxyForHandle 프록시 객체 만들기:
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)
)
{
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;
}
handle이 비어 있으면 기본값은 context관리자 객체, context관리자는 실제로 Service Manager입니다.o 서버 서버도 Ibinder 인터페이스를 실현해야 한다. Bbinder 클래스는 Ibinder 인터페이스에 일부 기본적인 실현을 제공했다. 그 중에서transact의 실현은 다음과 같다.
status_t BBinder::
transact
(
uint32_t code,
const
Parcel&
data,
Parcel*
reply,
uint32_t flags)
{
data.setDataPosition
(
0
)
;
status_t err =
NO_ERROR;
switch
(
code)
{
case
PING_TRANSACTION:
reply->
writeInt32(
pingBinder(
)
)
;
break
;
default
:
err =
onTransact(
code,
data,
reply,
flags)
;
break
;
}
if
(
reply !=
NULL)
{
reply->
setDataPosition(
0
)
;
}
return
err;
}
PING_TRANSACTION은 대상이 존재하는지 확인하기 위해 요청합니다. 핑빈더의 반환 값을 호출자에게 간단하게 되돌려줍니다.다른 요청은 onTransact 처리에 맡깁니다.onTransact는 Bbinder에서 설명한 보호된 형식의 허함수입니다. 이것은 하위 클래스를 실현해야 합니다.예를 들어 Camera Service의 구현은 다음과 같습니다.
status_t CameraService::
onTransact
(
uint32_t code,
const
Parcel&
data,
Parcel*
reply,
uint32_t flags)
{
// permission checks...
switch
(
code)
{
case
BnCameraService::
CONNECT
:
IPCThreadState*
ipc =
IPCThreadState::
self
(
)
;
const
int
pid =
ipc->
getCallingPid(
)
;
const
int
self_pid =
getpid(
)
;
if
(
pid !=
self_pid)
{
// we're called from a different process, do the real check
if
(
!
checkCallingPermission(
String16(
"android.permission.CAMERA"
)
)
)
{
const
int
uid =
ipc->
getCallingUid(
)
;
LOGE(
"Permission Denial: "
"can't use the camera pid=%d, uid=%d"
,
pid,
uid)
;
return
PERMISSION_DENIED;
}
}
break
;
}
status_t err =
BnCameraService::
onTransact
(
code,
data,
reply,
flags)
;
LOGD(
"+++ onTransact err %d code %d"
,
err,
code)
;
if
(
err ==
UNKNOWN_TRANSACTION ||
err ==
PERMISSION_DENIED)
{
// the 'service' command interrogates this binder for its name, and then supplies it
// even for the debugging commands. that means we need to check for it here, using
// ISurfaceComposer (since we delegated the INTERFACE_TRANSACTION handling to
// BnSurfaceComposer before falling through to this code).
LOGD(
"+++ onTransact code %d"
,
code)
;
CHECK_INTERFACE(
ICameraService,
data,
reply)
;
switch
(
code)
{
case
1000
:
{
if
(
gWeakHeap !=
0
)
{
sp h =
gWeakHeap.promote
(
)
;
IMemoryHeap *
p =
gWeakHeap.unsafe_get
(
)
;
LOGD(
"CHECKING WEAK REFERENCE %p (%p)"
,
h.get
(
)
,
p)
;
if
(
h !=
0
)
h->
printRefs(
)
;
bool attempt_to_delete =
data.readInt32
(
)
==
1
;
if
(
attempt_to_delete)
{
// NOT SAFE!
LOGD(
"DELETING WEAK REFERENCE %p (%p)"
,
h.get
(
)
,
p)
;
if
(
p)
delete p;
}
return
NO_ERROR;
}
}
break
;
default
:
break
;
}
}
return
err;
}
이를 통해 알 수 있듯이 서비스 측의 onTransact는 요청 분배 함수로 요청 코드(code)에 따라 상응하는 처리를 한다.
o 메시지 순환
서버 (모든 프로세스가 서버로 사용할 수 있음) 는 클라이언트로부터 요청을 감청하고 이 요청을 순환적으로 처리합니다.
주 스레드에서 요청을 처리하는 경우 다음 함수를 직접 호출할 수 있습니다.
IPCThreadState::
self
(
)
->
joinThreadPool(
mIsMain)
;
주 스레드가 아닌 스레드에서 요청을 처리하려면 다음과 같이 하십시오.
sp
proc =
ProcessState::
self
(
)
;
if
(
proc->
supportsProcesses(
)
)
{
LOGV(
"App process: starting thread pool./n
"
)
;
proc->
startThreadPool(
)
;
}
startThreadPool의 실현 원리:
void
ProcessState::
startThreadPool
(
)
{
AutoMutex _l(
mLock)
;
if
(
!
mThreadPoolStarted)
{
mThreadPoolStarted =
true
;
spawnPooledThread(
true
)
;
}
}
void
ProcessState::
spawnPooledThread
(
bool isMain)
{
if
(
mThreadPoolStarted)
{
int32_t s =
android_atomic_add(
1
,
&
mThreadPoolSeq)
;
char
buf[
32
]
;
sprintf(
buf,
"Binder Thread #%d"
,
s)
;
LOGV(
"Spawning new pooled thread, name=%s/n
"
,
buf)
;
sp
t =
new PoolThread(
isMain)
;
t->
run(
buf)
;
}
}
PoolThread의 대상을 만들었는데 실현적으로 하나의 라인을 만들었습니다.모든 스레드 클래스는threadLoop 허함수를 실현해야 합니다.PoolThread의threadLoop의 구현은 다음과 같습니다.
virtual bool threadLoop(
)
{
IPCThreadState::
self
(
)
->
joinThreadPool(
mIsMain)
;
return
false
;
}
상술한 코드는 간단하게 말하면 하나의 라인을 만들고 라인에서 IPCthreadState::self()->joinThreadPool 함수를 호출하는 것이다.
다음은 JoinThreadPool의 실현을 살펴보겠습니다.
do
{
...
result
=
talkWithDriver(
)
;
if
(
result >=
NO_ERROR)
{
size_t IN =
mIn.dataAvail
(
)
;
if
(
IN <
sizeof
(
int32_t)
)
continue
;
cmd =
mIn.readInt32
(
)
;
IF_LOG_COMMANDS(
)
{
alog <<
"Processing top-level Command: "
<<
getReturnString(
cmd)
<<
endl;
}
result =
executeCommand(
cmd)
;
}
...
while
(
...)
;
이 함수는 주기에서 다음 작업을 반복합니다.
실제 객체를 다음과 같이 호출합니다.
if
(
tr.target
.ptr
)
{
sp<
BBinder>
b(
(
BBinder*
)
tr.cookie
)
;
const
status_t error =
b->
transact(
tr.code
,
buffer,
&
reply,
0
)
;
if
(
error <
NO_ERROR)
reply.setError
(
error)
;
}
else
{
const
status_t error =
the_context_object->
transact(
tr.code
,
buffer,
&
reply,
0
)
;
if
(
error <
NO_ERROR)
reply.setError
(
error)
;
}
만약tr.target.ptr가 비어 있지 않으면 tr.cookie를 Binder 대상으로 변환하고 transact 함수를 호출합니다.대상 대상이 없으면 the 호출context_object 대상의transact 함수입니다.이상하게도 아무도 thecontext_object 초기화, thecontext_object는 빈 포인터입니다.이유는 contextmgr의 요청이 서비스 관리자에게 보내졌기 때문에else 문장에 오지 않습니다.
o 코어 모듈
android는 프로세스 간의 메시지를 전달하기 위해 내장 모듈binder를 사용합니다.모듈 소스 코드는binder에 있습니다.c에서, 이것은 문자 드라이버로 주로 binder 를 통과한다ioctl은 사용자 공간의 프로세스와 데이터를 교환합니다.여기서 BINDERWRITE_READ는 데이터를 읽고 쓰는 데 사용되며, 패키지의 cmd 도메인은 여러 요청을 구분하는 데 사용됩니다.
binder 에서thread_write에서 binder 호출transaction에서 요청 및 반환 결과,bindertransaction의 구현은 다음과 같습니다.
요청 처리:
어떻게 context 가 됩니까mgr은요?커널 모듈은 BINDER를 제공합니다.SET_CONTEXT_MGR 호출:
static
long
binder_ioctl(
struct
file *
filp,
unsigned
int
cmd,
unsigned
long
arg)
{
...
case
BINDER_SET_CONTEXT_MGR:
if
(
binder_context_mgr_node !=
NULL)
{
printk(
KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set/n
"
)
;
ret =
-
EBUSY;
goto
err;
}
if
(
binder_context_mgr_uid !=
-
1
)
{
if
(
binder_context_mgr_uid !=
current->
euid)
{
printk(
KERN_ERR "binder: BINDER_SET_"
"CONTEXT_MGR bad uid %d != %d/n
"
,
current->
euid,
binder_context_mgr_uid)
;
ret =
-
EPERM;
goto
err;
}
}
else
binder_context_mgr_uid =
current->
euid;
binder_context_mgr_node =
binder_new_node(
proc,
NULL,
NULL)
;
if
(
binder_context_mgr_node ==
NULL)
{
ret =
-
ENOMEM;
goto
err;
}
binder_context_mgr_node->
local_weak_refs++;
binder_context_mgr_node->
local_strong_refs++;
binder_context_mgr_node->
has_strong_ref =
1
;
binder_context_mgr_node->
has_weak_ref =
1
;
break
;
Service Manager(frameworks/base/cmds/서비스 관리자)는 다음과 같은 방식으로 context 가 됩니다.mgr 프로세스:
int
binder_become_context_manager(
struct
binder_state *
bs)
{
return
ioctl(
bs->
fd,
BINDER_SET_CONTEXT_MGR,
0
)
;
}
int
main(
int
argc,
char
**
argv)
{
struct
binder_state *
bs;
void
*
svcmgr =
BINDER_SERVICE_MANAGER;
bs =
binder_open(
128
*
1024
)
;
if
(
binder_become_context_manager(
bs)
)
{
LOGE(
"cannot become context manager (%s)/n
"
,
strerror(
errno)
)
;
return
-
1
;
}
svcmgr_handle =
svcmgr;
binder_loop(
bs,
svcmgr_handler)
;
return
0
;
}
o 서비스 대상의handle을 어떻게 얻는가
o 서비스 대상의handle을 통해 서비스가 있는 프로세스를 어떻게 찾습니까
0은 서비스 관리자의handle을 표시하고 get 서비스는 시스템 서비스의handle을 찾을 수 있습니다.이handle은 서비스 대상을 대표할 뿐입니다. 내부 모듈은handle을 통해 서비스가 있는 프로세스를 어떻게 찾습니까?
off_end =
(
void
*
)
offp +
tr->
offsets_size;
for
(
;
offp <
off_end;
offp++
)
{
struct
flat_binder_object *
fp;
if
(
*
offp >
t->
buffer->
data_size -
sizeof
(
*
fp)
)
{
binder_user_error(
"binder: %d:%d got transaction with "
"invalid offset, %d/n
"
,
proc->
pid,
thread->
pid,
*
offp)
;
return_error =
BR_FAILED_REPLY;
goto
err_bad_offset;
}
fp =
(
struct
flat_binder_object *
)
(
t->
buffer->
data +
*
offp)
;
switch
(
fp->
type)
{
case
BINDER_TYPE_BINDER:
case
BINDER_TYPE_WEAK_BINDER:
{
struct
binder_ref *
ref;
struct
binder_node *
node =
binder_get_node(
proc,
fp->
binder)
;
if
(
node ==
NULL)
{
node =
binder_new_node(
proc,
fp->
binder,
fp->
cookie)
;
if
(
node ==
NULL)
{
return_error =
BR_FAILED_REPLY;
goto
err_binder_new_node_failed;
}
node->
min_priority =
fp->
flags &
FLAT_BINDER_FLAG_PRIORITY_MASK;
node->
accept_fds =
!!
(
fp->
flags &
FLAT_BINDER_FLAG_ACCEPTS_FDS)
;
}
if
(
fp->
cookie !=
node->
cookie)
{
binder_user_error(
"binder: %d:%d sending u%p "
"node %d, cookie mismatch %p != %p/n
"
,
proc->
pid,
thread->
pid,
fp->
binder,
node->
debug_id,
fp->
cookie,
node->
cookie)
;
goto
err_binder_get_ref_for_node_failed;
}
ref =
binder_get_ref_for_node(
target_proc,
node)
;
if
(
ref ==
NULL)
{
return_error =
BR_FAILED_REPLY;
goto
err_binder_get_ref_for_node_failed;
}
if
(
fp->
type ==
BINDER_TYPE_BINDER)
fp->
type =
BINDER_TYPE_HANDLE;
else
fp->
type =
BINDER_TYPE_WEAK_HANDLE;
fp->
handle =
ref->
desc;
binder_inc_ref(
ref,
fp->
type ==
BINDER_TYPE_HANDLE,
&
thread->
todo)
;
if
(
binder_debug_mask &
BINDER_DEBUG_TRANSACTION)
printk(
KERN_INFO " node %d u%p -> ref %d desc %d/n
"
,
node->
debug_id,
node->
ptr,
ref->
debug_id,
ref->
desc)
;
}
break
;
o C 호출 JAVA
앞에서 우리가 분석한 것은 C 코드의 처리이다.JAVA 코드의 경우 JAVA 호출 C의 함수는 JNI를 통해 호출하면 됩니다.커널에서 요청을 읽는 것은 C 코드(execute Command)에서 이루어졌는데, 어떻게 C 코드에서 JAVA로 이루어진 서비스를 호출합니까?
android_os_Binder_init의 JavaBbinder는 Java의 Binder 객체를 포장합니다.
JavaBbinder::onTransact에서 Java의 execTransact 함수를 호출합니다.
jboolean res =
env->
CallBooleanMethod(
mObject,
gBinderOffsets.mExecTransact
,
code,
(
int32_t)
&
data,
(
int32_t)
reply,
flags)
;
jthrowable excep =
env->
ExceptionOccurred(
)
;
if
(
excep)
{
report_exception(
env,
excep,
"*** Uncaught remote exception! "
"(Exceptions are not yet supported across processes.)"
)
;
res =
JNI_FALSE;
/* clean up JNI local ref -- we don't return to Java code */
env->
DeleteLocalRef(
excep)
;
}
o 메시지 브로드캐스트
binder는 브로드캐스트 메시지를 제공하지 않지만 Activity Manager 서비스로 브로드캐스트를 수행할 수 있습니다.(frameworks/base/core/java/android/app/ActivityManagerNative.java)
브로드캐스트 메시지를 수신하려면 인터페이스BroadcastReceiver를 실행한 다음ActivityManagerProxy::registerReceiver 등록을 호출해야 합니다.
ActivityManagerProxy:::broadcastIntent 를 트리거하여 호출합니다.(응용 프로그램은 그것을 직접 호출하지 않고 컨텍스트 포장을 호출한다)
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
언제가 아닌가프로그래밍 언어에서 null 참조가 수십억 달러의 실수라는 말을 이미 들었을 것입니다. Java의 유명하고 두려운 NullPointerException은 여러분이 알고 있거나 C의 분할 오류일 수 있습니다. 모든 상...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.