当初跟踪Camera的代码中的时候一直追到了HAL层,而在Framework中的代码看见了若干个Thread。它们普遍的特点就是有一个threadLoop方法。按照字面的意思应该是这个线程能够循环处理数据。对应我想到到了java上层中的HandlerThread,这个估计也差不多,但当时心里总有一个疙瘩,想弄清楚它到底是怎么循环起来的。

Android中java世界的Thread

先来看看java是怎么创建一个线程的。这个是最舒服的,也是我最熟悉的。

new Thread(new Runnable() {        @Override        public void run() {            // TODO Auto-generated method stub                ...        }}).start();

当然,你也可以在android中创建一个消息循环的HandlerThread

HandlerThread mThread = new HandlerThread("test");mThread.start();Handler mHandler = new Handler(mThread.getLooper()){        @Override        public void handleMessage(Message msg) {            // TODO Auto-generated method stub            super.handleMessage(msg);        }};

上面中通过mHandler发送消息就可以在mThread中处理了,并且这个mThread不是UIThread,不会阻塞主线程。

Linux下c语言的Thread

java世界的Thread很方便,那么c呢?
Android基于linux所以,多线程编程也应该基于linux下的多线程。linux下的c语言用pthread。大家可以看这篇文章。
linux下C/C++,多线程pthread

我把里面的例子改良了一下
test.c

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>void *test(void *ptr){    int i;    for(i=0;i<8;i++)    {        printf("the pthread running ,count: %d\n",i);        sleep(1);     }}int main(void){    pthread_t pId;    int i,ret;    ret = pthread_create(&pId,NULL,test,NULL);    if(ret != 0)    {        printf("create pthread error!\n");        exit(1);    }    for(i=0;i < 5;i++)    {        printf("main thread running ,count : %d\n",i);        sleep(1);    }    printf("main thread will exit when pthread is over\n");    pthread_join(pId,NULL);    printf("main thread exit\n");    return 0;}

然后编译

gcc -o test test.c -lpthread./test

运行结果如下

main thread running ,count : 0the pthread running ,count: 0main thread running ,count : 1the pthread running ,count: 1main thread running ,count : 2the pthread running ,count: 2main thread running ,count : 3the pthread running ,count: 3main thread running ,count : 4the pthread running ,count: 4main thread will exit when pthread is overthe pthread running ,count: 5the pthread running ,count: 6the pthread running ,count: 7main thread  exit

例子比较简单,主要是创建一个线程,然后主线程等待子线程运行完毕再退出。

Android Framework中的Thread

下面焦点回到文章的主题当中,我们来看看Framework中常用的Thread是个何种形态。
先看看活生生的例子。
在源码中搜索threadLoop,当然也可以搜索thread,然后随便挑选一个Thread子类进行研究。这里挑了
/frameworks/av/services/audioflinger/AudioWatchdog.h

#ifndef AUDIO_WATCHDOG_H#define AUDIO_WATCHDOG_H#include <time.h>#include <utils/Thread.h>namespace android {......class AudioWatchdog : public Thread {public:    AudioWatchdog(unsigned periodMs = 50) : Thread(false /*canCallJava*/), mPaused(false),            mPeriodNs(periodMs * 1000000), mMaxCycleNs(mPeriodNs * 2),            // mOldTs            // mLogTs initialized below            mOldTsValid(false), mUnderruns(0), mLogs(0), mDump(&mDummyDump)        {#define MIN_TIME_BETWEEN_LOGS_SEC 60            // force an immediate log on first underrun            mLogTs.tv_sec = MIN_TIME_BETWEEN_LOGS_SEC;            mLogTs.tv_nsec = 0;        }    virtual         ~AudioWatchdog() { }     // Do not call Thread::requestExitAndWait() without first calling requestExit().    // Thread::requestExitAndWait() is not virtual, and the implementation doesn't do enough.    virtual void        requestExit();    // FIXME merge API and implementation with AudioTrackThread    void            pause();        // suspend thread from execution at next loop boundary    void            resume();       // allow thread to execute, if not requested to exit    // Where to store the dump, or NULL to not update    void            setDump(AudioWatchdogDump* dump);private:    virtual bool    threadLoop();    Mutex           mMyLock;        // Thread::mLock is private    Condition       mMyCond;        // Thread::mThreadExitedCondition is private    bool            mPaused;        // whether thread is currently paused    ......};}   // namespace android#endif // AUDIO_WATCHDOG_H

我们可以看到AudioWatchDog确实是Thread的子类,那好,下面看实现。
/frameworks/av/services/audioflinger/AudioWatchdog.cpp

#define LOG_TAG "AudioWatchdog"//#define LOG_NDEBUG 0#include <utils/Log.h>#include "AudioWatchdog.h"namespace android {void AudioWatchdogDump::dump(int fd){    char buf[32];    if (mMostRecent != 0) {        // includes NUL terminator        ctime_r(&mMostRecent, buf);    } else {        strcpy(buf, "N/A\n");    }    fdprintf(fd, "Watchdog: underruns=%u, logs=%u, most recent underrun log at %s",            mUnderruns, mLogs, buf);}bool AudioWatchdog::threadLoop(){    {        AutoMutex _l(mMyLock);        if (mPaused) {            mMyCond.wait(mMyLock);            // ignore previous timestamp after resume()            mOldTsValid = false;            // force an immediate log on first underrun after resume()            mLogTs.tv_sec = MIN_TIME_BETWEEN_LOGS_SEC;            mLogTs.tv_nsec = 0;            // caller will check for exitPending()            return true;        }    }    struct timespec newTs;    int rc = clock_gettime(CLOCK_MONOTONIC, &newTs);    if (rc != 0) {        pause();        return false;    }    if (!mOldTsValid) {        mOldTs = newTs;        mOldTsValid = true;        return true;    }    time_t sec = newTs.tv_sec - mOldTs.tv_sec;    long nsec = newTs.tv_nsec - mOldTs.tv_nsec;    if (nsec < 0) {        --sec;        nsec += 1000000000;    }    mOldTs = newTs;    // cycleNs is same as sec*1e9 + nsec, but limited to about 4 seconds    uint32_t cycleNs = nsec;    if (sec > 0) {        if (sec < 4) {            cycleNs += sec * 1000000000;        } else {            cycleNs = 4000000000u;        }    }    mLogTs.tv_sec += sec;    if ((mLogTs.tv_nsec += nsec) >= 1000000000) {        mLogTs.tv_sec++;        mLogTs.tv_nsec -= 1000000000;    }    if (cycleNs > mMaxCycleNs) {        mDump->mUnderruns = ++mUnderruns;        if (mLogTs.tv_sec >= MIN_TIME_BETWEEN_LOGS_SEC) {            mDump->mLogs = ++mLogs;            mDump->mMostRecent = time(NULL);            ALOGW("Insufficient CPU for load: expected=%.1f actual=%.1f ms; underruns=%u logs=%u",                mPeriodNs * 1e-6, cycleNs * 1e-6, mUnderruns, mLogs);            mLogTs.tv_sec = 0;            mLogTs.tv_nsec = 0;        }    }    struct timespec req;    req.tv_sec = 0;    req.tv_nsec = mPeriodNs;    rc = nanosleep(&req, NULL);    if (!((rc == 0) || (rc == -1 && errno == EINTR))) {        pause();        return false;    }    return true;}void AudioWatchdog::requestExit(){    // must be in this order to avoid a race condition    Thread::requestExit();    resume();}void AudioWatchdog::pause(){    AutoMutex _l(mMyLock);    mPaused = true;}void AudioWatchdog::resume(){    AutoMutex _l(mMyLock);    if (mPaused) {        mPaused = false;        mMyCond.signal();    }}void AudioWatchdog::setDump(AudioWatchdogDump *dump){    mDump = dump != NULL ? dump : &mDummyDump;}}   // namespace android

很明显,它的核心方法就是threadLoop(),但是它是怎么启动的呢?又是怎么循环运行的呢?带着疑问我又在源码中搜索关键字AudioWatchdog
结果发现有两个地方引用了。

/frameworks/av/services/audioflinger/AudioFlinger.h/frameworks/av/services/audioflinger/AudioFlinger.cpp

在AudioFlinger.h中MixerThread中有个AudioWatchdog的sp对象

 class MixerThread : public PlaybackThread {    public:        MixerThread (const sp<AudioFlinger>& audioFlinger,                     AudioStreamOut* output,                     audio_io_handle_t id,                     audio_devices_t device,                     type_t type = MIXER);        virtual             ~MixerThread();    protected:                    AudioMixer* mAudioMixer;    // normal mixer    private:                    sp<AudioWatchdog> mAudioWatchdog; // non-0 if there is an audio watchdog thread    };

我们再看代码
/frameworks/av/services/audioflinger/AudioFlinger.cpp

AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,        audio_io_handle_t id, audio_devices_t device, type_t type)    :   PlaybackThread(audioFlinger, output, id, device, type),        // mAudioMixer below        // mFastMixer below        mFastMixerFutex(0)        // mOutputSink below        // mPipeSink below        // mNormalSink below{......#ifdef AUDIO_WATCHDOG        // create and start the watchdog        mAudioWatchdog = new AudioWatchdog();        mAudioWatchdog->setDump(&mAudioWatchdogDump);        mAudioWatchdog->run("AudioWatchdog", PRIORITY_URGENT_AUDIO);        tid = mAudioWatchdog->getTid();        err = requestPriority(getpid_cached, tid, kPriorityFastMixer);        if (err != 0) {            ALOGW("Policy SCHED_FIFO priority %d is unavailable for pid %d tid %d; error %d",                    kPriorityFastMixer, getpid_cached, tid, err);        }#endif......}

删掉不相关代码,我们看到AudioWatchdog对象确实创建了,并且调用了它的run方法。在java中Thread的run方法就是启动,这个也应该如此。但是如之前的源码所示AudioWatchdog.cpp中并没有实现run方法,怎么办呢?别紧张,它还有父类Thread.

/frameworks/native/include/utils/Thread.h

#ifndef _LIBS_UTILS_THREAD_H#define _LIBS_UTILS_THREAD_H#include <stdint.h>#include <sys/types.h>#include <time.h>#if defined(HAVE_PTHREADS)# include <pthread.h>#endif#include <utils/Condition.h>#include <utils/Errors.h>#include <utils/Mutex.h>#include <utils/RefBase.h>#include <utils/Timers.h>#include <utils/ThreadDefs.h>// ---------------------------------------------------------------------------namespace android {// ---------------------------------------------------------------------------class Thread : virtual public RefBase{public:    // Create a Thread object, but doesn't create or start the associated    // thread. See the run() method.                        Thread(bool canCallJava = true);    virtual             ~Thread();    // Start the thread in threadLoop() which needs to be implemented.    virtual status_t    run(    const char* name = 0,                                int32_t priority = PRIORITY_DEFAULT,                                size_t stack = 0);    // Ask this object's thread to exit. This function is asynchronous, when the    // function returns the thread might still be running. Of course, this    // function can be called from a different thread.    virtual void        requestExit();    // Good place to do one-time initializations    virtual status_t    readyToRun();    // Call requestExit() and wait until this object's thread exits.    // BE VERY CAREFUL of deadlocks. In particular, it would be silly to call    // this function from this object's thread. Will return WOULD_BLOCK in    // that case.            status_t    requestExitAndWait();    // Wait until this object's thread exits. Returns immediately if not yet running.    // Do not call from this object's thread; will return WOULD_BLOCK in that case.            status_t    join();#ifdef HAVE_ANDROID_OS    // Return the thread's kernel ID, same as the thread itself calling gettid() or    // androidGetTid(), or -1 if the thread is not running.            pid_t       getTid() const;#endifprotected:    // exitPending() returns true if requestExit() has been called.            bool        exitPending() const;private:    // Derived class must implement threadLoop(). The thread starts its life    // here. There are two ways of using the Thread object:    // 1) loop: if threadLoop() returns true, it will be called again if    //          requestExit() wasn't called.    // 2) once: if threadLoop() returns false, the thread will exit upon return.    virtual bool        threadLoop() = 0;private:    Thread& operator=(const Thread&);    static  int             _threadLoop(void* user);    const   bool            mCanCallJava;    // always hold mLock when reading or writing            thread_id_t     mThread;    mutable Mutex           mLock;            Condition       mThreadExitedCondition;            status_t        mStatus;    // note that all accesses of mExitPending and mRunning need to hold mLock    volatile bool           mExitPending;    volatile bool           mRunning;            sp<Thread>      mHoldSelf;#ifdef HAVE_ANDROID_OS    // legacy for debugging, not used by getTid() as it is set by the child thread    // and so is not initialized until the child reaches that point            pid_t           mTid;#endif};}; // namespace android// ---------------------------------------------------------------------------#endif // _LIBS_UTILS_THREAD_H// 

可以看到确实有run方法。

status_t Thread::run(const char* name, int32_t priority, size_t stack){    Mutex::Autolock _l(mLock);    if (mRunning) {        // thread already started        return INVALID_OPERATION;    }    // reset status and exitPending to their default value, so we can    // try again after an error happened (either below, or in readyToRun())    mStatus = NO_ERROR;    mExitPending = false;    mThread = thread_id_t(-1);    // hold a strong reference on ourself    mHoldSelf = this;    mRunning = true;    bool res;    if (mCanCallJava) {        res = createThreadEtc(_threadLoop,                this, name, priority, stack, &mThread);    } else {        res = androidCreateRawThreadEtc(_threadLoop,                this, name, priority, stack, &mThread);    }    if (res == false) {        mStatus = UNKNOWN_ERROR;   // something happened!        mRunning = false;        mThread = thread_id_t(-1);        mHoldSelf.clear();  // "this" may have gone away after this.        return UNKNOWN_ERROR;    }    // Do not refer to mStatus here: The thread is already running (may, in fact    // already have exited with a valid mStatus result). The NO_ERROR indication    // here merely indicates successfully starting the thread and does not    // imply successful termination/execution.    return NO_ERROR;    // Exiting scope of mLock is a memory barrier and allows new thread to run}

run()方法中有这么一段

if (mCanCallJava) {        res = createThreadEtc(_threadLoop,                this, name, priority, stack, &mThread);    } else {        res = androidCreateRawThreadEtc(_threadLoop,                this, name, priority, stack, &mThread);    }

mCanCallJava的意思是能不能被JNI层调用,然后根据值去创建Thread,这里有两个分支,我们就选择createThreadEtc()
最终代码会走到这里

int androidCreateRawThreadEtc(android_thread_func_t entryFunction,                               void *userData,                               const char* threadName,                               int32_t threadPriority,                               size_t threadStackSize,                               android_thread_id_t *threadId){    ......        entryFunction = (android_thread_func_t)&thread_data_t::trampoline;        userData = t;    }#endif    if (threadStackSize) {        pthread_attr_setstacksize(&attr, threadStackSize);    }    errno = 0;    pthread_t thread;    int result = pthread_create(&thread, &attr,                    (android_pthread_entry)entryFunction, userData);    pthread_attr_destroy(&attr);    if (result != 0) {        ALOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, errno=%d)\n"             "(android threadPriority=%d)",            entryFunction, result, errno, threadPriority);        return 0;    }    ......    return 1;}

删除了不相关代码,大家看看是不是很熟悉啊。我在文章开始的部分就写出了linux下c语言pthread创建线程的例子,大家可以回头看看。也就是pthread_create()。这里面传进来的entryFunction是Thread中的_threadLoop()

int Thread::_threadLoop(void* user){    Thread* const self = static_cast<Thread*>(user);    sp<Thread> strong(self->mHoldSelf);    wp<Thread> weak(strong);    self->mHoldSelf.clear();#ifdef HAVE_ANDROID_OS    // this is very useful for debugging with gdb    self->mTid = gettid();#endif    bool first = true;    do {        bool result;        if (first) {            first = false;            self->mStatus = self->readyToRun();            result = (self->mStatus == NO_ERROR);            if (result && !self->exitPending()) {                // Binder threads (and maybe others) rely on threadLoop                // running at least once after a successful ::readyToRun()                // (unless, of course, the thread has already been asked to exit                // at that point).                // This is because threads are essentially used like this:                // (new ThreadSubclass())->run();                // The caller therefore does not retain a strong reference to                // the thread and the thread would simply disappear after the                // successful ::readyToRun() call instead of entering the                // threadLoop at least once.                result = self->threadLoop();            }        } else {            result = self->threadLoop();        }        // establish a scope for mLock        {        Mutex::Autolock _l(self->mLock);        if (result == false || self->mExitPending) {            self->mExitPending = true;            self->mRunning = false;            // clear thread ID so that requestExitAndWait() does not exit if            // called by a new thread using the same thread ID as this one.            self->mThread = thread_id_t(-1);            // note that interested observers blocked in requestExitAndWait are            // awoken by broadcast, but blocked on mLock until break exits scope            self->mThreadExitedCondition.broadcast();            break;        }        }        // Release our strong reference, to let a chance to the thread        // to die a peaceful death.        strong.clear();        // And immediately, re-acquire a strong reference for the next loop        strong = weak.promote();    } while(strong != 0);    return 0;}

_threadLoop()这个方法就是Thread的最大秘密,它是一个while循环。

1、创建线程时,会sp和wp一次线程本身。
2、如果是第一次执行会运行线程的readyToRun()方法,再执行threadLoop(),否则,直接运行threadLoop()。
3、threadLoop()方法有返回值,如果threadLoop()返回false的时候,线程会做清理工作,然后退出while循环,结束运行。

所以在这里,我开始时的疑问—为什么线程Thread中的threadLoop()能够循环处理数据就到此做了说明。Thread被创建,Thread中的run被调用,__threadLoop()被调用,readyToRun()被调用,然后循环调用threadLoop()。并且在threadLoop()返回false时,可以退出循环。

特殊情况

有的时候Android Framework中Thread的run()方法很难发现在哪里被调用。如SurfaceFlinger它也是一个Thread子类。在源码中搜索可以发现它的创建位置

class SurfaceFlinger : public BinderService<SurfaceFlinger>,                       public BnSurfaceComposer,                       private IBinder::DeathRecipient,                       private Thread,                       private HWComposer::EventHandler{public:    static char const* getServiceName() {        return "SurfaceFlinger";    }    SurfaceFlinger();    /* ------------------------------------------------------------------------ * Thread interface */    virtual bool threadLoop();    virtual status_t readyToRun();    virtual void onFirstRef();};// ---------------------------------------------------------------------------}; // namespace android#endif // ANDROID_SURFACE_FLINGER_H

去找它创建的地方
/frameworks/base/cmds/system_server/library/system_init.cpp

extern "C" status_t system_init(){    ALOGI("Entered system_init()");    sp<ProcessState> proc(ProcessState::self());    sp<IServiceManager> sm = defaultServiceManager();    ALOGI("ServiceManager: %p\n", sm.get());    char propBuf[PROPERTY_VALUE_MAX];    property_get("system_init.startsurfaceflinger", propBuf, "1");    if (strcmp(propBuf, "1") == 0) {        // Start the SurfaceFlinger        SurfaceFlinger::instantiate();    }    // And now start the Android runtime. We have to do this bit    // of nastiness because the Android runtime initialization requires    // some of the core system services to already be started.    // All other servers should just start the Android runtime at    // the beginning of their processes's main(), before calling    // the init function.    ALOGI("System server: starting Android runtime.\n");    AndroidRuntime* runtime = AndroidRuntime::getRuntime();    ALOGI("System server: starting Android services.\n");    JNIEnv* env = runtime->getJNIEnv();    if (env == NULL) {        return UNKNOWN_ERROR;    }    jclass clazz = env->FindClass("com/android/server/SystemServer");    if (clazz == NULL) {        return UNKNOWN_ERROR;    }    jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V");    if (methodId == NULL) {        return UNKNOWN_ERROR;    }    env->CallStaticVoidMethod(clazz, methodId);    ALOGI("System server: entering thread pool.\n");    ProcessState::self()->startThreadPool();    IPCThreadState::self()->joinThreadPool();    ALOGI("System server: exiting thread pool.\n");    return NO_ERROR;}

我们可以看到

 SurfaceFlinger::instantiate();

但它本身并没有实现instantiate()方法,那之类找它的父类了。
/frameworks/native/include/binder/BinderService.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) {        sp<IServiceManager> sm(defaultServiceManager());        sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated);        ProcessState::self()->startThreadPool();        IPCThreadState::self()->joinThreadPool();    }    static void instantiate() { publish(); }    static status_t shutdown() {        return NO_ERROR;    }};}; // namespace android// ---------------------------------------------------------------------------#endif // ANDROID_BINDER_SERVICE_H

会调用publish()方法。
而SERVICE在这里是一个模板类。在这里SERVICE自然对应SurfaceFlinger
所以publish()会向ServiceManager添加一个Service这个Service就是Surfaceflinger。

然后我们看SurfaceFlinger的构造函数
/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

SurfaceFlinger::SurfaceFlinger()    :   BnSurfaceComposer(), Thread(false),        mTransactionFlags(0),        mTransactionPending(false),        mAnimTransactionPending(false),        mLayersRemoved(false),        mRepaintEverything(0),        mBootTime(systemTime()),        mVisibleRegionsDirty(false),        mHwWorkListDirty(false),        mDebugRegion(0),        mDebugDDMS(0),        mDebugDisableHWC(0),        mDebugDisableTransformHint(0),        mDebugInSwapBuffers(0),        mLastSwapBufferTime(0),        mDebugInTransaction(0),        mLastTransactionTime(0),        mBootFinished(false){    ALOGI("SurfaceFlinger is starting");    // debugging stuff...    char value[PROPERTY_VALUE_MAX];    property_get("debug.sf.showupdates", value, "0");    mDebugRegion = atoi(value);    property_get("debug.sf.ddms", value, "0");    mDebugDDMS = atoi(value);    if (mDebugDDMS) {        if (!startDdmConnection()) {            // start failed, and DDMS debugging not enabled            mDebugDDMS = 0;        }    }    ALOGI_IF(mDebugRegion, "showupdates enabled");    ALOGI_IF(mDebugDDMS, "DDMS debugging enabled");}

可还是没有发现run()方法的影踪,没有办法只得去父类构造方法看
结果发现也没有!!!

没有办法,继续在源码中搜索SurfaceFlinger,结果发现与之相关的信息大多是sp<SurfaceFlinger>
就看看sp吧。
sp是Android在c++中搞得类似java中弱引用、强引用的一套指针概念,那应该是方便回收吧。
而Android Framework中的c++世界,RefBase这个类有点像java中的Object.
而sp是一个模板类。这部分内容,请看点击下面链接

Android中的sp和wp指针

总之调用sp<SurfaceFlinger>时会调用SurfaceFlinger的onFirstRef()方法。
那好,看代码吧

void SurfaceFlinger::onFirstRef(){    mEventQueue.init(this);    run("SurfaceFlinger", PRIORITY_URGENT_DISPLAY);    // Wait for the main thread to be done with its initialization    mReadyToRunBarrier.wait();}

看见没有?run()方法在这里调用了。

所以,在Framework中如果你找不到一个Thread在何处被启动,那么去它的onFirstRef()方法中去看看吧

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. Android(安卓)View 随手指移动
  3. AndroidCameraHAL3-MultiCamera-CameraX
  4. 编译 Android(安卓)版本的 Opus 音频编解码库的方法
  5. android中进程与线程
  6. Android官方Toolbar自定义高度最靠谱的解决办法「Android」
  7. 老罗牛文二、在Ubuntu上下载、编译和安装Android最新源代码
  8. Android(安卓)下拉刷新框架实现、仿新浪微博、QQ好友动态滑到底
  9. Service与Android系统设计(2)-- Parcel

随机推荐

  1. Android 通过Matrix来对图片进行缩放,旋转
  2. Ubuntu 设置Android adb 环境变量
  3. Android的MediaPlayer—3
  4. Android:Dialog 遇到的问题
  5. android 多媒体部分学习笔记十 可视化频
  6. Android动态设置主题(使用RxBus模式)
  7. Android使用AsyncTask实现多线程下载的方
  8. Android 7.0 如何将应用添加设置列表
  9. getSharedPreferences和getDefaultShared
  10. 苹果二手手机换购计划很快支持 Android(