Android(安卓)Audio代码分析15 - testPlaybackHeadPositionAfterFlush
16lz
2021-01-25
上次看到的testPlaybackHeadPositionIncrease函数中,先play了一会,然后获取position。
今天看个复杂点的,先play,然后stop,之后在flush,此时再获取position会是什么情况呢?
*****************************************源码*************************************************
**********************************************************************************************
源码路径:
frameworks\base\media\tests\mediaframeworktest\src\com\android\mediaframeworktest\functional\MediaAudioTrackTest.java
#######################说明################################
###########################################################
&&&&&&&&&&&&&&&&&&&&&&&总结&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
stop的时候:
若AudioTrack不是active的,在将其audio_track_cblk_t中的user, server等清0。
若是direct output,则将其close。
flush的时候:
会将audio_track_cblk_t中的user, server等清0。
因此,flush 之后再get position的话,肯定是0.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
今天看个复杂点的,先play,然后stop,之后在flush,此时再获取position会是什么情况呢?
*****************************************源码*************************************************
//Test case 3: getPlaybackHeadPosition() is 0 after flush(); @LargeTest public void testPlaybackHeadPositionAfterFlush() throws Exception { // constants for test final String TEST_NAME = "testPlaybackHeadPositionAfterFlush"; final int TEST_SR = 22050; final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; final int TEST_MODE = AudioTrack.MODE_STREAM; final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; //-------- initialization -------------- int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE); byte data[] = new byte[minBuffSize/2]; //-------- test -------------- assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); track.write(data, 0, data.length); track.write(data, 0, data.length); track.play(); Thread.sleep(100); track.stop(); track.flush(); log(TEST_NAME, "position ="+ track.getPlaybackHeadPosition()); assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0); //-------- tear down -------------- track.release(); }
**********************************************************************************************
源码路径:
frameworks\base\media\tests\mediaframeworktest\src\com\android\mediaframeworktest\functional\MediaAudioTrackTest.java
#######################说明################################
//Test case 3: getPlaybackHeadPosition() is 0 after flush(); @LargeTest public void testPlaybackHeadPositionAfterFlush() throws Exception { // constants for test final String TEST_NAME = "testPlaybackHeadPositionAfterFlush"; final int TEST_SR = 22050; final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; final int TEST_MODE = AudioTrack.MODE_STREAM; final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; //-------- initialization -------------- int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE); byte data[] = new byte[minBuffSize/2]; //-------- test -------------- assumeTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); track.write(data, 0, data.length); track.write(data, 0, data.length); track.play(); Thread.sleep(100); track.stop();// ++++++++++++++++++++++++++++stop++++++++++++++++++++++++++++++++++++ /** * Stops playing the audio data. * @throws IllegalStateException */ public void stop() throws IllegalStateException { if (mState != STATE_INITIALIZED) { throw(new IllegalStateException("stop() called on uninitialized AudioTrack.")); } // stop playing synchronized(mPlayStateLock) { native_stop();// ++++++++++++++++++++++++++++++android_media_AudioTrack_stop++++++++++++++++++++++++++++++++++static voidandroid_media_AudioTrack_stop(JNIEnv *env, jobject thiz){ AudioTrack *lpTrack = (AudioTrack *)env->GetIntField( thiz, javaAudioTrackFields.nativeTrackInJavaObj); if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for stop()"); return; } lpTrack->stop();// +++++++++++++++++++++++++++++++AudioTrack::stop+++++++++++++++++++++++++++++++++void AudioTrack::stop(){// mAudioTrackThread在函数AudioTrack::set中被赋值。// mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava); sp<AudioTrackThread> t = mAudioTrackThread; LOGV("stop %p", this); if (t != 0) { t->mLock.lock(); } if (android_atomic_and(~1, &mActive) == 1) { mCblk->cv.signal();// mAudioTrack在函数AudioTrack::createTrack中被赋值,其最终指向的其实是一个TrackHandle对象 mAudioTrack->stop();// ++++++++++++++++++++++++++++++AudioFlinger::TrackHandle::stop++++++++++++++++++++++++++++++++++void AudioFlinger::TrackHandle::stop() { mTrack->stop();// +++++++++++++++++++++++++++++AudioFlinger::PlaybackThread::Track::stop+++++++++++++++++++++++++++++++++++void AudioFlinger::PlaybackThread::Track::stop(){ LOGV("stop(%d), calling thread %d", mName, IPCThreadState::self()->getCallingPid());// 在Track的构造函数中赋值,指向创建该Track对象的PlaybackThread sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); int state = mState; if (mState > STOPPED) {// ++++++++++++++++++++++++++++++++track_state++++++++++++++++++++++++++++++++ enum track_state { IDLE, TERMINATED, STOPPED, RESUMING, ACTIVE, PAUSING, PAUSED };// --------------------------------track_state-------------------------------- mState = STOPPED; // If the track is not active (PAUSED and buffers full), flush buffers PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); if (playbackThread->mActiveTracks.indexOf(this) < 0) { reset();// ++++++++++++++++++++++++++++++AudioFlinger::PlaybackThread::Track::reset++++++++++++++++++++++++++++++++++void AudioFlinger::PlaybackThread::Track::reset(){ // Do not reset twice to avoid discarding data written just after a flush and before // the audioflinger thread detects the track is stopped. if (!mResetDone) { TrackBase::reset();// ++++++++++++++++++++++++++++++AudioFlinger::ThreadBase::TrackBase::reset++++++++++++++++++++++++++++++++++void AudioFlinger::ThreadBase::TrackBase::reset() { audio_track_cblk_t* cblk = this->cblk(); cblk->user = 0; cblk->server = 0; cblk->userBase = 0; cblk->serverBase = 0; mFlags &= (uint32_t)(~SYSTEM_FLAGS_MASK); LOGV("TrackBase::reset");}// ------------------------------AudioFlinger::ThreadBase::TrackBase::reset---------------------------------- // Force underrun condition to avoid false underrun callback until first data is // written to buffer mCblk->flags |= CBLK_UNDERRUN_ON; mCblk->flags &= ~CBLK_FORCEREADY_MSK; mFillingUpStatus = FS_FILLING; mResetDone = true; }}// ------------------------------AudioFlinger::PlaybackThread::Track::reset---------------------------------- } LOGV("(> STOPPED) => STOPPED (%d) on thread %p", mName, playbackThread); } if (!isOutputTrack() && (state == ACTIVE || state == RESUMING)) { thread->mLock.unlock(); AudioSystem::stopOutput(thread->id(), (AudioSystem::stream_type)mStreamType, mSessionId);// ++++++++++++++++++++++++++++++AudioSystem::stopOutput++++++++++++++++++++++++++++++++++status_t AudioSystem::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream, int session){ const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service(); if (aps == 0) return PERMISSION_DENIED; return aps->stopOutput(output, stream, session);// +++++++++++++++++++++++++++++++++AudioPolicyService::stopOutput+++++++++++++++++++++++++++++++status_t AudioPolicyService::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream, int session){ if (mpPolicyManager == NULL) { return NO_INIT; } LOGV("stopOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); return mpPolicyManager->stopOutput(output, stream, session);// ++++++++++++++++++++++++++++++AudioPolicyManagerBase::stopOutput++++++++++++++++++++++++++++++++++status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, AudioSystem::stream_type stream, int session){ LOGV("stopOutput() output %d, stream %d, session %d", output, stream, session); ssize_t index = mOutputs.indexOfKey(output); if (index < 0) { LOGW("stopOutput() unknow output %d", output); return BAD_VALUE; } AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); // handle special case for sonification while in call if (isInCall()) { handleIncallSonification(stream, false, false); } if (outputDesc->mRefCount[stream] > 0) { // decrement usage count of this stream on the output outputDesc->changeRefCount(stream, -1);// ++++++++++++++++++++++++++++AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount++++++++++++++++++++++++++++++++++++void AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta){ // forward usage count change to attached outputs if (isDuplicated()) { mOutput1->changeRefCount(stream, delta); mOutput2->changeRefCount(stream, delta); } if ((delta + (int)mRefCount[stream]) < 0) { LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]); mRefCount[stream] = 0; return; }// 函数AudioPolicyManagerBase::AudioOutputDescriptor::refCount会使用mRefCount mRefCount[stream] += delta;// +++++++++++++++++++++++++++++AudioPolicyManagerBase::AudioOutputDescriptor::refCount+++++++++++++++++++++++++++++++++++uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::refCount(){ uint32_t refcount = 0; for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { refcount += mRefCount[i]; } return refcount;}// 函数AudioPolicyManagerBase::releaseOutput中有调用函数AudioPolicyManagerBase::AudioOutputDescriptor::refCount。// +++++++++++++++++++++++++++++AudioPolicyManagerBase::releaseOutput+++++++++++++++++++++++++++++++++++void AudioPolicyManagerBase::releaseOutput(audio_io_handle_t output){ LOGV("releaseOutput() %d", output); ssize_t index = mOutputs.indexOfKey(output); if (index < 0) { LOGW("releaseOutput() releasing unknown output %d", output); return; }#ifdef AUDIO_POLICY_TEST int testIndex = testOutputIndex(output); if (testIndex != 0) { AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); if (outputDesc->refCount() == 0) { mpClientInterface->closeOutput(output); delete mOutputs.valueAt(index); mOutputs.removeItem(output); mTestOutputs[testIndex] = 0; } return; }#endif //AUDIO_POLICY_TEST// 如果没定义AUDIO_POLICY_TEST,只对设置了OUTPUT_FLAG_DIRECT标志的output做delete处理// 原因,其实在以前我们看AudioPolicyManagerBase::getOutput函数时已经看到了,// 在get output的时候,只有需要一个direct output的时候,才会调用函数AudioPolicyService::openOutput函数来打开一个output// 否则,只是将成员变量(在构造函数中打开的output)返回// 当然,我们没有考虑AUDIO_POLICY_TEST时的情况 if (mOutputs.valueAt(index)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT) {// ++++++++++++++++++++++++++++++output_flags++++++++++++++++++++++++++++++++++ // request to open a direct output with getOutput() (by opposition to sharing an output with other AudioTracks) enum output_flags { OUTPUT_FLAG_INDIRECT = 0x0, OUTPUT_FLAG_DIRECT = 0x1 };// ------------------------------output_flags---------------------------------- mpClientInterface->closeOutput(output);// +++++++++++++++++++++++++++++AudioPolicyService::closeOutput+++++++++++++++++++++++++++++++++++status_t AudioPolicyService::closeOutput(audio_io_handle_t output){ sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); if (af == 0) return PERMISSION_DENIED; return af->closeOutput(output);// +++++++++++++++++++++++++++AudioFlinger::closeOutput++++++++++++++++++++++++++++++++++++status_t AudioFlinger::closeOutput(int output){ // keep strong reference on the playback thread so that // it is not destroyed while exit() is executed sp <PlaybackThread> thread; { Mutex::Autolock _l(mLock); thread = checkPlaybackThread_l(output); if (thread == NULL) { return BAD_VALUE; } LOGV("closeOutput() %d", output);// 如果thread 为mixer// 删除所有DUPLICATING thread中包含的该thread if (thread->type() == PlaybackThread::MIXER) { for (size_t i = 0; i < mPlaybackThreads.size(); i++) { if (mPlaybackThreads.valueAt(i)->type() == PlaybackThread::DUPLICATING) { DuplicatingThread *dupThread = (DuplicatingThread *)mPlaybackThreads.valueAt(i).get(); dupThread->removeOutputTrack((MixerThread *)thread.get()); } } } void *param2 = 0; audioConfigChanged_l(AudioSystem::OUTPUT_CLOSED, output, param2); mPlaybackThreads.removeItem(output); } thread->exit();// ++++++++++++++++++++++++++++AudioFlinger::ThreadBase::exit++++++++++++++++++++++++++++++++++++void AudioFlinger::ThreadBase::exit(){ // keep a strong ref on ourself so that we wont get // destroyed in the middle of requestExitAndWait() sp <ThreadBase> strongMe = this; LOGV("ThreadBase::exit"); { AutoMutex lock(&mLock); mExiting = true; requestExit();// +++++++++++++++++++++++++++++Thread::requestExit+++++++++++++++++++++++++++++++++++void Thread::requestExit(){// threadloop函数中会判断该成员变量,以判断是否要结束线程 mExitPending = true;}// -----------------------------Thread::requestExit---------------------------------- mWaitWorkCV.signal(); } requestExitAndWait();// +++++++++++++++++++++++++++++Thread::requestExitAndWait+++++++++++++++++++++++++++++++++++status_t Thread::requestExitAndWait(){ if (mThread == getThreadId()) { LOGW( "Thread (this=%p): don't call waitForExit() from this " "Thread object's thread. It's a guaranteed deadlock!", this); return WOULD_BLOCK; } requestExit(); Mutex::Autolock _l(mLock); while (mRunning == true) { mThreadExitedCondition.wait(mLock); } mExitPending = false; return mStatus;}// -----------------------------Thread::requestExitAndWait-----------------------------------}// ----------------------------AudioFlinger::ThreadBase::exit------------------------------------ if (thread->type() != PlaybackThread::DUPLICATING) {// output是在函数AudioFlinger::openOutput中调用函数mAudioHardware->openOutputStream打开的。 mAudioHardware->closeOutputStream(thread->getOutput());// +++++++++++++++++++++++++++AudioHardwareALSA::closeOutputStream+++++++++++++++++++++++++++++++++++++voidAudioHardwareALSA::closeOutputStream(AudioStreamOut* out){ AutoMutex lock(mLock); delete out;}// ---------------------------AudioHardwareALSA::closeOutputStream------------------------------------- } return NO_ERROR;}// ----------------------------AudioFlinger::closeOutput------------------------------------}// -----------------------------AudioPolicyService::closeOutput----------------------------------- delete mOutputs.valueAt(index); mOutputs.removeItem(output); }}// -----------------------------AudioPolicyManagerBase::releaseOutput-----------------------------------// -----------------------------AudioPolicyManagerBase::AudioOutputDescriptor::refCount----------------------------------- LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]);}// ----------------------------AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount------------------------------------ // store time at which the last music track was stopped - see computeVolume() if (stream == AudioSystem::MUSIC) { mMusicStopTime = systemTime(); } setOutputDevice(output, getNewDevice(output));#ifdef WITH_A2DP if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) { setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput, mOutputs.valueFor(mHardwareOutput)->mLatency*2); }#endif if (output != mHardwareOutput) { setOutputDevice(mHardwareOutput, getNewDevice(mHardwareOutput), true); } return NO_ERROR; } else { LOGW("stopOutput() refcount is already 0 for output %d", output); return INVALID_OPERATION; }}// ------------------------------AudioPolicyManagerBase::stopOutput----------------------------------}// ---------------------------------AudioPolicyService::stopOutput-------------------------------}// ------------------------------AudioSystem::stopOutput---------------------------------- thread->mLock.lock(); } }}// -----------------------------AudioFlinger::PlaybackThread::Track::stop-----------------------------------}// ------------------------------AudioFlinger::TrackHandle::stop---------------------------------- // Cancel loops (If we are in the middle of a loop, playback // would not stop until loopCount reaches 0). setLoop(0, 0, 0);// +++++++++++++++++++++++++++++AudioTrack::setLoop+++++++++++++++++++++++++++++++++++status_t AudioTrack::setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount){ audio_track_cblk_t* cblk = mCblk; Mutex::Autolock _l(cblk->lock); if (loopCount == 0) { cblk->loopStart = ULLONG_MAX; cblk->loopEnd = ULLONG_MAX; cblk->loopCount = 0; mLoopCount = 0; return NO_ERROR; } if (loopStart >= loopEnd || loopEnd - loopStart > cblk->frameCount) { LOGE("setLoop invalid value: loopStart %d, loopEnd %d, loopCount %d, framecount %d, user %lld", loopStart, loopEnd, loopCount, cblk->frameCount, cblk->user); return BAD_VALUE; } if ((mSharedBuffer != 0) && (loopEnd > cblk->frameCount)) { LOGE("setLoop invalid value: loop markers beyond data: loopStart %d, loopEnd %d, framecount %d", loopStart, loopEnd, cblk->frameCount); return BAD_VALUE; } cblk->loopStart = loopStart; cblk->loopEnd = loopEnd; cblk->loopCount = loopCount; mLoopCount = loopCount; return NO_ERROR;}// -----------------------------AudioTrack::setLoop----------------------------------- // the playback head position will reset to 0, so if a marker is set, we need // to activate it again mMarkerReached = false; // Force flush if a shared buffer is used otherwise audioflinger // will not stop before end of buffer is reached. if (mSharedBuffer != 0) { flush();// ++++++++++++++++++++++++++++AudioTrack::flush++++++++++++++++++++++++++++++++++++void AudioTrack::flush(){ LOGV("flush"); // clear playback marker and periodic update counter mMarkerPosition = 0; mMarkerReached = false; mUpdatePeriod = 0; if (!mActive) { mAudioTrack->flush();// ++++++++++++++++++++++++++++++AudioFlinger::TrackHandle::flush++++++++++++++++++++++++++++++++++void AudioFlinger::TrackHandle::flush() { mTrack->flush();// ++++++++++++++++++++++++++++++AudioFlinger::PlaybackThread::Track::flush++++++++++++++++++++++++++++++++++void AudioFlinger::PlaybackThread::Track::flush(){ LOGV("flush(%d)", mName); sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); if (mState != STOPPED && mState != PAUSED && mState != PAUSING) { return; } // No point remaining in PAUSED state after a flush => go to // STOPPED state mState = STOPPED; mCblk->lock.lock(); // NOTE: reset() will reset cblk->user and cblk->server with // the risk that at the same time, the AudioMixer is trying to read // data. In this case, getNextBuffer() would return a NULL pointer // as audio buffer => the AudioMixer code MUST always test that pointer // returned by getNextBuffer() is not NULL!// reset函数在stop函数中已经看过 reset(); mCblk->lock.unlock(); }}// ------------------------------AudioFlinger::PlaybackThread::Track::flush----------------------------------}// ------------------------------AudioFlinger::TrackHandle::flush---------------------------------- // Release AudioTrack callback thread in case it was waiting for new buffers // in AudioTrack::obtainBuffer() mCblk->cv.signal(); }}// ----------------------------AudioTrack::flush------------------------------------ } if (t != 0) { t->requestExit(); } else { setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_NORMAL); } } if (t != 0) { t->mLock.unlock(); }}// -------------------------------AudioTrack::stop---------------------------------}// ------------------------------android_media_AudioTrack_stop---------------------------------- mPlayState = PLAYSTATE_STOPPED; } }// ----------------------------stop------------------------------------ track.flush();// ++++++++++++++++++++++++++++flush++++++++++++++++++++++++++++++++++++ /** * Flushes the audio data currently queued for playback. */ public void flush() { if (mState == STATE_INITIALIZED) { // flush the data in native layer native_flush();// ++++++++++++++++++++++++++++android_media_AudioTrack_flush++++++++++++++++++++++++++++++++++++static voidandroid_media_AudioTrack_flush(JNIEnv *env, jobject thiz){ AudioTrack *lpTrack = (AudioTrack *)env->GetIntField( thiz, javaAudioTrackFields.nativeTrackInJavaObj); if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for flush()"); return; } lpTrack->flush();// +++++++++++++++++++++++++++++AudioTrack::flush+++++++++++++++++++++++++++++++++++void AudioTrack::flush(){ LOGV("flush"); // clear playback marker and periodic update counter mMarkerPosition = 0; mMarkerReached = false; mUpdatePeriod = 0; if (!mActive) { mAudioTrack->flush();// ++++++++++++++++++++++++++++AudioFlinger::TrackHandle::flush++++++++++++++++++++++++++++++++++++void AudioFlinger::TrackHandle::flush() {// 函数AudioFlinger::PlaybackThread::Track::flush在上面已经看过 mTrack->flush();}// ----------------------------AudioFlinger::TrackHandle::flush------------------------------------ // Release AudioTrack callback thread in case it was waiting for new buffers // in AudioTrack::obtainBuffer() mCblk->cv.signal(); }}// -----------------------------AudioTrack::flush-----------------------------------}// ----------------------------android_media_AudioTrack_flush------------------------------------ } }// ----------------------------flush------------------------------------ log(TEST_NAME, "position ="+ track.getPlaybackHeadPosition()); assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0); //-------- tear down -------------- track.release(); }
###########################################################
&&&&&&&&&&&&&&&&&&&&&&&总结&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
stop的时候:
若AudioTrack不是active的,在将其audio_track_cblk_t中的user, server等清0。
若是direct output,则将其close。
flush的时候:
会将audio_track_cblk_t中的user, server等清0。
因此,flush 之后再get position的话,肯定是0.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
更多相关文章
- 最新android视频地址及android4.2环境地址
- android全屏问题(隐藏虚拟按键)
- zygote服务启动
- android常用的工具类——将图形变成圆形
- binder机制底层驱动浅析
- android sqlite用法
- avd安装apk
- [置顶] Android(安卓)NDK中结合汇编分析Crash行为
- Android(安卓)Binder机制(3) 本地服务注册过程