上次看到的testPlaybackHeadPositionIncrease函数中,先play了一会,然后获取position。
今天看个复杂点的,先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.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

更多相关文章

  1. 最新android视频地址及android4.2环境地址
  2. android全屏问题(隐藏虚拟按键)
  3. zygote服务启动
  4. android常用的工具类——将图形变成圆形
  5. binder机制底层驱动浅析
  6. android sqlite用法
  7. avd安装apk
  8. [置顶] Android(安卓)NDK中结合汇编分析Crash行为
  9. Android(安卓)Binder机制(3) 本地服务注册过程

随机推荐

  1. 利用xml文件创建Menu
  2. 在Android上做List Remove的时候遇到的异
  3. android adb shel l命令使用 解决 Read-o
  4. Android(安卓)Studio与 Android(安卓)SDK
  5. Android用http协议上传文件
  6. 第一篇 GridView控件
  7. Android(安卓)CollapsingToolbarLayout:将
  8. Android资源管理框架-------之总述(一)
  9. android设置wallpaper
  10. 读取SIM卡信息