Binder 多线程分析

1. Native 层多线程分析

1.1 客户端线程相关初始化

在 [Binder 程序示例之C++篇]() 中,客户端是一个单线程程序:

int main(int argc, char const *argv[])
{
    //使用 ProcessState 类完成 binder 驱动的初始化
    sp<ProcessState> proc(ProcessState::self());
    //获取 hello 服务
    sp<IServiceManager> sm = defaultServiceManager();
    //返回的是 BpBinder 指针
    sp<IBinder> binder = sm->getService(String16("hello"));
    sp<IHelloService> service =
            interface_cast<IHelloService>(binder);

    if (binder == 0)
    {
        ALOGI("can't get hello service\n");
        return -1;
    }
    //发起远程调用
    service->sayHello();
    int cnt = service->sayHelloTo("nihao");
    ALOGI("client call sayhello_to, cnt = %d", cnt);


    return 0;
}

从 [Binder 服务注册过程情景分析之 C++ 篇]() 的分析中,我们知道 defaultServiceManager 会调用到如下函数:

status_t status = IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, nullptr, 0);

transact 最终会调用 ioctl , 陷入内核态,调用到 binder_ioclt 函数

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    //......
    //从 proc 的 threads 红黑树中找 pid 相同的 thread ,没有就新建一个
    thread = binder_get_thread(proc);
    //......
}

接着我们看下 binder_get_thread 的具体实现:

//参数是当前用户进程(Client 端)对应的 binder_proc
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
    struct binder_thread *thread;
    struct binder_thread *new_thread;

    binder_inner_proc_lock(proc);  //加锁
    //第一次执行
    thread = binder_get_thread_ilocked(proc, NULL);
    binder_inner_proc_unlock(proc);
    if (!thread) {
        //构造一个新的 binder_thread
        new_thread = kzalloc(sizeof(*thread), GFP_KERNEL);
        if (new_thread == NULL)
            return NULL;
        binder_inner_proc_lock(proc);
        //第二次执行
        thread = binder_get_thread_ilocked(proc, new_thread);
        binder_inner_proc_unlock(proc);
        if (thread != new_thread)
            kfree(new_thread);
    }
    return thread;
}

//第一次执行
static struct binder_thread *binder_get_thread_ilocked(
        struct binder_proc *proc, struct binder_thread *new_thread)
{
    struct binder_thread *thread = NULL;
    struct rb_node *parent = NULL;
    struct rb_node **p = &proc->threads.rb_node;

    printk("binder_get_thread_ilocked, *p is %p", );

    // 通过 pid 查找到 binder_thread 结构体,还没插入过数据,所以这里查找到的是 NULL
    while (*p) {
        parent = *p;
        thread = rb_entry(parent, struct binder_thread, rb_node);

        if (current->pid < thread->pid)
            p = &(*p)->rb_left;
        else if (current->pid > thread->pid)
            p = &(*p)->rb_right;
        else
            return thread;
    }
    if (!new_thread) //直接返回 NULL
        return NULL; 
    //......
}

//第二次执行
static struct binder_thread *binder_get_thread_ilocked(
        struct binder_proc *proc, struct binder_thread *new_thread)
{
    struct binder_thread *thread = NULL;
    struct rb_node *parent = NULL;
    struct rb_node **p = &proc->threads.rb_node;

    printk("binder_get_thread_ilocked, *p is %p", );

    //仍然查找不到
    while (*p) {
        parent = *p;
        thread = rb_entry(parent, struct binder_thread, rb_node);

        if (current->pid < thread->pid)
            p = &(*p)->rb_left;
        else if (current->pid > thread->pid)
            p = &(*p)->rb_right;
        else
            return thread;
    }
    if (!new_thread)
        return NULL;
    //配置新构建的 binder_proc
    thread = new_thread;
    binder_stats_created(BINDER_STAT_THREAD);
    thread->proc = proc;
    thread->pid = current->pid;
    get_task_struct(current);
    thread->task = current;
    atomic_set(&thread->tmp_ref, 0);
    init_waitqueue_head(&thread->wait);
    INIT_LIST_HEAD(&thread->todo);
    rb_link_node(&thread->rb_node, parent, p); //插入到 proc 的 threads 红黑树中
    rb_insert_color(&thread->rb_node, &proc->threads);
    thread->looper_need_return = true;
    thread->return_error.work.type = BINDER_WORK_RETURN_ERROR;
    thread->return_error.cmd = BR_OK;
    thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR;
    thread->reply_error.cmd = BR_OK;
    INIT_LIST_HEAD(&new_thread->waiting_thread_node);
    return thread;
}

通过源码分析,可以看出 binder_get_thread 函数:

  • 构建并初始化了一个 binder_thread 结构体,该结构体与用户线程相对应
  • 同时,将该结构体插入到了 binder_proc->threads 红黑树中

当我们调用 getServiceservice->sayHello() 发起远程调用时,和 defaultServiceManager 类似,也会陷入内核,进入 binder_ioctl 函数。不同的是,当调用到 binder_get_thread 函数时,会直接从红黑树中查找到之前插入的数据并返回。

1.2 服务端线程相关初始化

在 [Binder 程序示例之C++篇]() 中,服务端通过以下代码开启了线程池:

int main(int argc, char const *argv[])
{
    //使用 ProcessState 类完成 binder 驱动的初始化
    sp<ProcessState> proc(ProcessState::self());
    //注册服务
    sp<IServiceManager> sm = defaultServiceManager();
    sm->addService(String16("hello"), new BnHelloService());

    //开启 binder 线程池
    ProcessState::self()->startThreadPool();
    //加入线程池
    IPCThreadState::self()->joinThreadPool();

    return 0;
}

和客户端相同,当调用 defaultServiceManager 时,会回调到内核的 binder_ioctl 函数,进一步会调用到binder_get_thread 函数 , 在 binder_get_thread 函数中,构建新的 binder_thread 结构体,并插入 binder_proc->threads 红黑树中。

接着会调用 ProcessState::self()->startThreadPool() 开启线程池:

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;        
        spawnPooledThread(true);  //创建线程,此处的参数true表示主线程
    }
}

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName(); //Binder线程名称
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp<Thread> t = new PoolThread(isMain); //isMain为true表示主线程
        t->run(name.string()); 
    }
}

String8 ProcessState::makeBinderThreadName() {
    int32_t s = android_atomic_add(1, &mThreadPoolSeq);
    pid_t pid = getpid();
    String8 name;
    name.appendFormat("Binder:%d_%X", pid, s);//格式为Binder:pid_s(其中pid为进程号;s为序列号,每次累加1)
    return name;
}

//继承自 Thread,通过 run 启动,启动后会执行 threadLoop 函数
class PoolThread : public Thread
{
public:
    explicit PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }

protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain); //将当前线程加入线程池,mIsMain为true
        return false;
    }

    const bool mIsMain;
};

void IPCThreadState::joinThreadPool(bool isMain)
{
    //线程进入循环,isMain为true表示主线程, false表示Binder驱动通知应用进程创建的线程
    //告诉驱动,进入循环了
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    status_t result;
    do {
        processPendingDerefs(); // 清除队列的引用
        result = getAndExecuteCommand(); //获取并执行指令
        // ........
        // 非主线程且timeout,则跳出循环,结束线程(主线程不会退出)
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);
    ........
    mOut.writeInt32(BC_EXIT_LOOPER);// 通知Binder驱动线程退出
    talkWithDriver(false); // false表示不读Binder驱动数据,只写
}

接着我们来看 getAndExecuteCommand()

status_t IPCThreadState::getAndExecuteCommand()
{
    status_t result;
    int32_t cmd;
    //关注点1 读取数据,同时会写入 BC_ENTER_LOOPER
    result = talkWithDriver();
    if (result >= NO_ERROR) {
        size_t IN = mIn.dataAvail();
        if (IN < sizeof(int32_t)) return result;
        cmd = mIn.readInt32();
        IF_LOG_COMMANDS() {
            alog << "Processing top-level Command: "
                 << getReturnString(cmd) << endl;
        }

        pthread_mutex_lock(&mProcess->mThreadCountLock);
        mProcess->mExecutingThreadsCount++;
        if (mProcess->mExecutingThreadsCount >= mProcess->mMaxThreads &&
                mProcess->mStarvationStartTimeMs == 0) {
            mProcess->mStarvationStartTimeMs = uptimeMillis();
        }
        pthread_mutex_unlock(&mProcess->mThreadCountLock);
        //处理读到的数据
        result = executeCommand(cmd);

        pthread_mutex_lock(&mProcess->mThreadCountLock);
        mProcess->mExecutingThreadsCount--;
        if (mProcess->mExecutingThreadsCount < mProcess->mMaxThreads &&
                mProcess->mStarvationStartTimeMs != 0) {
            int64_t starvationTimeMs = uptimeMillis() - mProcess->mStarvationStartTimeMs;
            if (starvationTimeMs > 100) {
                ALOGE("binder thread pool (%zu threads) starved for %" PRId64 " ms",
                      mProcess->mMaxThreads, starvationTimeMs);
            }
            mProcess->mStarvationStartTimeMs = 0;
        }
        pthread_cond_broadcast(&mProcess->mThreadCountDecrement);
        pthread_mutex_unlock(&mProcess->mThreadCountLock);
    }

    return result;
}

接着看 talkWithDriver:

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }

    binder_write_read bwr;

    // Is the read buffer empty?
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();

    // We don't want to write anything if we are still reading
    // from data left in the input buffer and the caller
    // has requested to read the next data.
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

    bwr.write_size = outAvail;
    bwr.write_buffer = (uintptr_t)mOut.data();

    // This is what we'll read.
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }

    IF_LOG_COMMANDS() {
        TextOutput::Bundle _b(alog);
        if (outAvail != 0) {
            alog << "Sending commands to driver: " << indent;
            const void* cmds = (const void*)bwr.write_buffer;
            const void* end = ((const uint8_t*)cmds)+bwr.write_size;
            alog << HexDump(cmds, bwr.write_size) << endl;
            while (cmds < end) cmds = printCommand(alog, cmds);
            alog << dedent;
        }
        alog << "Size of receive buffer: " << bwr.read_size
            << ", needRead: " << needRead << ", doReceive: " << doReceive << endl;
    }

    // Return immediately if there is nothing to do.
    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        IF_LOG_COMMANDS() {
            alog << "About to read/write, write size = " << mOut.dataSize() << endl;
        }
#if defined(__ANDROID__)
        // 通过 ioctl 与驱动交互 
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
#else
        err = INVALID_OPERATION;
#endif
        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }
        IF_LOG_COMMANDS() {
            alog << "Finished read/write, write size = " << mOut.dataSize() << endl;
        }
    } while (err == -EINTR);

    IF_LOG_COMMANDS() {
        alog << "Our err: " << (void*)(intptr_t)err << ", write consumed: "
            << bwr.write_consumed << " (of " << mOut.dataSize()
                        << "), read consumed: " << bwr.read_consumed << endl;
    }

    if (err >= NO_ERROR) {
        if (bwr.write_consumed > 0) {
            if (bwr.write_consumed < mOut.dataSize())
                mOut.remove(0, bwr.write_consumed);
            else {
                mOut.setDataSize(0);
                processPostWriteDerefs();
            }
        }
        if (bwr.read_consumed > 0) {
            mIn.setDataSize(bwr.read_consumed);
            mIn.setDataPosition(0);
        }
        IF_LOG_COMMANDS() {
            TextOutput::Bundle _b(alog);
            alog << "Remaining data size: " << mOut.dataSize() << endl;
            alog << "Received commands from driver: " << indent;
            const void* cmds = mIn.data();
            const void* end = mIn.data() + mIn.dataSize();
            alog << HexDump(cmds, mIn.dataSize()) << endl;
            while (cmds < end) cmds = printReturnCommand(alog, cmds);
            alog << dedent;
        }
        return NO_ERROR;
    }

    return err;
} 

ioctl 进入内核,调用到 binder_ioctl,和之前介绍的情况一样,这里同样会创建一个 binder_thread,同时会插入 binder_proc->threads 红黑树。

接着会处理传入的 BC_ENTER_LOOPER 命令

static int binder_thread_write(struct binder_proc *proc,
            struct binder_thread *thread,
            binder_uintptr_t binder_buffer, size_t size,
            binder_size_t *consumed)
{
    uint32_t cmd;
    void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;
    while (ptr < end && thread->return_error == BR_OK) {
        //拷贝用户空间的cmd命令,此时为BC_ENTER_LOOPER
        if (get_user(cmd, (uint32_t __user *)ptr)) -EFAULT;
        ptr += sizeof(uint32_t);
        switch (cmd) {
          case BC_REGISTER_LOOPER:
              if (thread->looper & BINDER_LOOPER_STATE_ENTERED) {
                //出错原因:线程调用完 BC_ENTER_LOOPER,不能执行该分支
                thread->looper |= BINDER_LOOPER_STATE_INVALID;

              } else if (proc->requested_threads == 0) {
                //出错原因:没有请求就创建线程
                thread->looper |= BINDER_LOOPER_STATE_INVALID;

              } else {
                proc->requested_threads--;
                proc->requested_threads_started++;
              }
              thread->looper |= BINDER_LOOPER_STATE_REGISTERED;
              break;

          case BC_ENTER_LOOPER:
              if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {
                //出错原因:线程调用完BC_REGISTER_LOOPER,不能立刻执行该分支
                thread->looper |= BINDER_LOOPER_STATE_INVALID;
              }
              //创建Binder主线程
              thread->looper |= BINDER_LOOPER_STATE_ENTERED;
              break;

          case BC_EXIT_LOOPER:
              thread->looper |= BINDER_LOOPER_STATE_EXITED;
              break;
        }
        ...
    }
    *consumed = ptr - buffer;
  }
  return 0;
}

处理 BC_ENTER_LOOPER 就是设置 thread->looper |= BINDER_LOOPER_STATE_ENTERED

接着会调用 binder_thread_read 读数据:

static int binder_thread_read(struct binder_proc *proc,
                  struct binder_thread *thread,
                  binder_uintptr_t binder_buffer, size_t size,
                  binder_size_t *consumed, int non_block)
{
    void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;

    int ret = 0;
    int wait_for_proc_work;

    if (*consumed == 0) {
        if (put_user(BR_NOOP, (uint32_t __user *)ptr))
            return -EFAULT;
        ptr += sizeof(uint32_t);
    }

retry:
    binder_inner_proc_lock(proc);
    wait_for_proc_work = binder_available_for_proc_work_ilocked(thread);
    binder_inner_proc_unlock(proc);

    thread->looper |= BINDER_LOOPER_STATE_WAITING;

    trace_binder_wait_for_work(wait_for_proc_work,
                   !!thread->transaction_stack,
                   !binder_worklist_empty(proc, &thread->todo));
    if (wait_for_proc_work) {
        if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
                    BINDER_LOOPER_STATE_ENTERED))) {
            binder_user_error("%d:%d ERROR: Thread waiting for process work before calling BC_REGISTER_LOOPER or BC_ENTER_LOOPER (state %x)\n",
                proc->pid, thread->pid, thread->looper);
            wait_event_interruptible(binder_user_error_wait,
                         binder_stop_on_user_error < 2);
        }
        binder_restore_priority(current, proc->default_priority);
    }

    if (non_block) {
        if (!binder_has_work(thread, wait_for_proc_work))
            ret = -EAGAIN;
    } else {
        // 当前线程加入到 proc->waiting_threads,然后休眠,等待唤醒
        ret = binder_wait_for_work(thread, wait_for_proc_work);
    }

    thread->looper &= ~BINDER_LOOPER_STATE_WAITING;

    if (ret)
        return ret;

    while (1) {
        uint32_t cmd;
        struct binder_transaction_data_secctx tr;
        struct binder_transaction_data *trd = &tr.transaction_data;
        struct binder_work *w = NULL;
        struct list_head *list = NULL;
        struct binder_transaction *t = NULL;
        struct binder_thread *t_from;
        size_t trsize = sizeof(*trd);

        binder_inner_proc_lock(proc);
        if (!binder_worklist_empty_ilocked(&thread->todo))
            list = &thread->todo;
        else if (!binder_worklist_empty_ilocked(&proc->todo) &&
               wait_for_proc_work) //走这
            list = &proc->todo;
        else {
            binder_inner_proc_unlock(proc);

            /* no data added */
            if (ptr - buffer == 4 && !thread->looper_need_return)
                goto retry;
            break;
        }

        if (end - ptr < sizeof(tr) + 4) {
            binder_inner_proc_unlock(proc);
            break;
        }
        //取链表中第一个数据
        w = binder_dequeue_work_head_ilocked(list);
        if (binder_worklist_empty_ilocked(&thread->todo))
            thread->process_todo = false;

        switch (w->type) {
        case BINDER_WORK_TRANSACTION: { //走这
            binder_inner_proc_unlock(proc);
            //拿到 binder_transaction
            t = container_of(w, struct binder_transaction, work);
        } break;
        case BINDER_WORK_RETURN_ERROR: {
            struct binder_error *e = container_of(
                    w, struct binder_error, work);

            WARN_ON(e->cmd == BR_OK);
            binder_inner_proc_unlock(proc);
            if (put_user(e->cmd, (uint32_t __user *)ptr))
                return -EFAULT;
            cmd = e->cmd;
            e->cmd = BR_OK;
            ptr += sizeof(uint32_t);

            binder_stat_br(proc, thread, e->cmd);
        } break;
        case BINDER_WORK_TRANSACTION_COMPLETE: {
            binder_inner_proc_unlock(proc);
            cmd = BR_TRANSACTION_COMPLETE;
            if (put_user(cmd, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);

            binder_stat_br(proc, thread, cmd);
            binder_debug(BINDER_DEBUG_TRANSACTION_COMPLETE,
                     "%d:%d BR_TRANSACTION_COMPLETE\n",
                     proc->pid, thread->pid);
            kfree(w);
            binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
        } break;
        case BINDER_WORK_NODE: {
            struct binder_node *node = container_of(w, struct binder_node, work);
            int strong, weak;
            binder_uintptr_t node_ptr = node->ptr;
            binder_uintptr_t node_cookie = node->cookie;
            int node_debug_id = node->debug_id;
            int has_weak_ref;
            int has_strong_ref;
            void __user *orig_ptr = ptr;

            BUG_ON(proc != node->proc);
            strong = node->internal_strong_refs ||
                    node->local_strong_refs;
            weak = !hlist_empty(&node->refs) ||
                    node->local_weak_refs ||
                    node->tmp_refs || strong;
            has_strong_ref = node->has_strong_ref;
            has_weak_ref = node->has_weak_ref;

            if (weak && !has_weak_ref) {
                node->has_weak_ref = 1;
                node->pending_weak_ref = 1;
                node->local_weak_refs++;
            }
            if (strong && !has_strong_ref) {
                node->has_strong_ref = 1;
                node->pending_strong_ref = 1;
                node->local_strong_refs++;
            }
            if (!strong && has_strong_ref)
                node->has_strong_ref = 0;
            if (!weak && has_weak_ref)
                node->has_weak_ref = 0;
            if (!weak && !strong) {
                binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                         "%d:%d node %d u%016llx c%016llx deleted\n",
                         proc->pid, thread->pid,
                         node_debug_id,
                         (u64)node_ptr,
                         (u64)node_cookie);
                rb_erase(&node->rb_node, &proc->nodes);
                binder_inner_proc_unlock(proc);
                binder_node_lock(node);
                /*
                 * Acquire the node lock before freeing the
                 * node to serialize with other threads that
                 * may have been holding the node lock while
                 * decrementing this node (avoids race where
                 * this thread frees while the other thread
                 * is unlocking the node after the final
                 * decrement)
                 */
                binder_node_unlock(node);
                binder_free_node(node);
            } else
                binder_inner_proc_unlock(proc);

            if (weak && !has_weak_ref)
                ret = binder_put_node_cmd(
                        proc, thread, &ptr, node_ptr,
                        node_cookie, node_debug_id,
                        BR_INCREFS, "BR_INCREFS");
            if (!ret && strong && !has_strong_ref)
                ret = binder_put_node_cmd(
                        proc, thread, &ptr, node_ptr,
                        node_cookie, node_debug_id,
                        BR_ACQUIRE, "BR_ACQUIRE");
            if (!ret && !strong && has_strong_ref)
                ret = binder_put_node_cmd(
                        proc, thread, &ptr, node_ptr,
                        node_cookie, node_debug_id,
                        BR_RELEASE, "BR_RELEASE");
            if (!ret && !weak && has_weak_ref)
                ret = binder_put_node_cmd(
                        proc, thread, &ptr, node_ptr,
                        node_cookie, node_debug_id,
                        BR_DECREFS, "BR_DECREFS");
            if (orig_ptr == ptr)
                binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                         "%d:%d node %d u%016llx c%016llx state unchanged\n",
                         proc->pid, thread->pid,
                         node_debug_id,
                         (u64)node_ptr,
                         (u64)node_cookie);
            if (ret)
                return ret;
        } break;
        case BINDER_WORK_DEAD_BINDER:
        case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
        case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
            struct binder_ref_death *death;
            uint32_t cmd;
            binder_uintptr_t cookie;

            death = container_of(w, struct binder_ref_death, work);
            if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
                cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;
            else
                cmd = BR_DEAD_BINDER;
            cookie = death->cookie;

            binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION,
                     "%d:%d %s %016llx\n",
                      proc->pid, thread->pid,
                      cmd == BR_DEAD_BINDER ?
                      "BR_DEAD_BINDER" :
                      "BR_CLEAR_DEATH_NOTIFICATION_DONE",
                      (u64)cookie);
            if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) {
                binder_inner_proc_unlock(proc);
                kfree(death);
                binder_stats_deleted(BINDER_STAT_DEATH);
            } else {
                binder_enqueue_work_ilocked(
                        w, &proc->delivered_death);
                binder_inner_proc_unlock(proc);
            }
            if (put_user(cmd, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);
            if (put_user(cookie,
                     (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);
            binder_stat_br(proc, thread, cmd);
            if (cmd == BR_DEAD_BINDER)
                goto done; /* DEAD_BINDER notifications can cause transactions */
        } break;
        }

        if (!t)
            continue;

        BUG_ON(t->buffer == NULL);
        if (t->buffer->target_node) {
            struct binder_node *target_node = t->buffer->target_node;
            struct binder_priority node_prio;
            //构建 binder_transaction_data ,用于返回给应用层
            trd->target.ptr = target_node->ptr;
            trd->cookie =  target_node->cookie;
            node_prio.sched_policy = target_node->sched_policy;
            node_prio.prio = target_node->min_priority;
            binder_transaction_priority(current, t, node_prio,
                            target_node->inherit_rt);
            cmd = BR_TRANSACTION;
        } else {
            trd->target.ptr = 0;
            trd->cookie = 0;
            cmd = BR_REPLY;
        }
        trd->code = t->code; //code 表示要调用哪个函数
        trd->flags = t->flags;
        trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid);

        t_from = binder_get_txn_from(t);
        if (t_from) {
            struct task_struct *sender = t_from->proc->tsk;

            trd->sender_pid =
                task_tgid_nr_ns(sender,
                        task_active_pid_ns(current));
        } else {
            trd->sender_pid = 0;
        }

        trd->data_size = t->buffer->data_size;
        trd->offsets_size = t->buffer->offsets_size;
        trd->data.ptr.buffer = (uintptr_t)t->buffer->user_data;
        trd->data.ptr.offsets = trd->data.ptr.buffer +
                    ALIGN(t->buffer->data_size,
                        sizeof(void *));

        tr.secctx = t->security_ctx;
        if (t->security_ctx) {
            cmd = BR_TRANSACTION_SEC_CTX;
            trsize = sizeof(tr);
        }
        if (put_user(cmd, (uint32_t __user *)ptr)) {
            if (t_from)
                binder_thread_dec_tmpref(t_from);

            binder_cleanup_transaction(t, "put_user failed",
                           BR_FAILED_REPLY);

            return -EFAULT;
        }
        ptr += sizeof(uint32_t);
        //将数据拷贝到应用层
        if (copy_to_user(ptr, &tr, trsize)) {
            if (t_from)
                binder_thread_dec_tmpref(t_from);

            binder_cleanup_transaction(t, "copy_to_user failed",
                           BR_FAILED_REPLY);

            return -EFAULT;
        }
        ptr += trsize;

        trace_binder_transaction_received(t);
        binder_stat_br(proc, thread, cmd);
        binder_debug(BINDER_DEBUG_TRANSACTION,
                 "%d:%d %s %d %d:%d, cmd %d size %zd-%zd ptr %016llx-%016llx\n",
                 proc->pid, thread->pid,
                 (cmd == BR_TRANSACTION) ? "BR_TRANSACTION" :
                (cmd == BR_TRANSACTION_SEC_CTX) ?
                     "BR_TRANSACTION_SEC_CTX" : "BR_REPLY",
                 t->debug_id, t_from ? t_from->proc->pid : 0,
                 t_from ? t_from->pid : 0, cmd,
                 t->buffer->data_size, t->buffer->offsets_size,
                 (u64)trd->data.ptr.buffer,
                 (u64)trd->data.ptr.offsets);

        if (t_from)
            binder_thread_dec_tmpref(t_from);
        t->buffer->allow_user_free = 1;
        if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY)) {
            binder_inner_proc_lock(thread->proc);
            t->to_parent = thread->transaction_stack;
            t->to_thread = thread;
            thread->transaction_stack = t;
            binder_inner_proc_unlock(thread->proc);
        } else {
            binder_free_transaction(t);
        }
        break;
    }

done:

    *consumed = ptr - buffer;
    binder_inner_proc_lock(proc);
    //应用层创建新线程的条件
    if (proc->requested_threads == 0 &&
        list_empty(&thread->proc->waiting_threads) &&
        proc->requested_threads_started < proc->max_threads &&
        (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
         BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
         /*spawn a new thread if we leave this out */) {
        proc->requested_threads++;
        binder_inner_proc_unlock(proc);
        binder_debug(BINDER_DEBUG_THREADS,
                 "%d:%d BR_SPAWN_LOOPER\n",
                 proc->pid, thread->pid);
        //告知应用层创建新的线程
        if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
            return -EFAULT;
        binder_stat_br(proc, thread, BR_SPAWN_LOOPER);
    } else
        binder_inner_proc_unlock(proc);
    return 0;
}

数据发送方的线程处理

static int binder_thread_write(struct binder_proc *proc,
            struct binder_thread *thread,
            binder_uintptr_t binder_buffer, size_t size,
            binder_size_t *consumed)
{
    uint32_t cmd;
    struct binder_context *context = proc->context;
    void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;

    while (ptr < end && thread->return_error.cmd == BR_OK) {
        int ret;

        if (get_user(cmd, (uint32_t __user *)ptr))
            return -EFAULT;
        ptr += sizeof(uint32_t);
        trace_binder_command(cmd);
        if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {
            atomic_inc(&binder_stats.bc[_IOC_NR(cmd)]);
            atomic_inc(&proc->stats.bc[_IOC_NR(cmd)]);
            atomic_inc(&thread->stats.bc[_IOC_NR(cmd)]);
        }
        switch (cmd) {
        case BC_INCREFS:
        case BC_ACQUIRE:
        case BC_RELEASE:
        case BC_DECREFS: {
            uint32_t target;
            const char *debug_string;
            bool strong = cmd == BC_ACQUIRE || cmd == BC_RELEASE;
            bool increment = cmd == BC_INCREFS || cmd == BC_ACQUIRE;
            struct binder_ref_data rdata;

            if (get_user(target, (uint32_t __user *)ptr))
                return -EFAULT;

            ptr += sizeof(uint32_t);
            ret = -1;
            if (increment && !target) {
                struct binder_node *ctx_mgr_node;
                mutex_lock(&context->context_mgr_node_lock);
                ctx_mgr_node = context->binder_context_mgr_node;
                if (ctx_mgr_node)
                    ret = binder_inc_ref_for_node(
                            proc, ctx_mgr_node,
                            strong, NULL, &rdata);
                mutex_unlock(&context->context_mgr_node_lock);
            }
            if (ret)
                ret = binder_update_ref_for_handle(
                        proc, target, increment, strong,
                        &rdata);
            if (!ret && rdata.desc != target) {
                binder_user_error("%d:%d tried to acquire reference to desc %d, got %d instead\n",
                    proc->pid, thread->pid,
                    target, rdata.desc);
            }
            switch (cmd) {
            case BC_INCREFS:
                debug_string = "IncRefs";
                break;
            case BC_ACQUIRE:
                debug_string = "Acquire";
                break;
            case BC_RELEASE:
                debug_string = "Release";
                break;
            case BC_DECREFS:
            default:
                debug_string = "DecRefs";
                break;
            }
            if (ret) {
                binder_user_error("%d:%d %s %d refcount change on invalid ref %d ret %d\n",
                    proc->pid, thread->pid, debug_string,
                    strong, target, ret);
                break;
            }
            binder_debug(BINDER_DEBUG_USER_REFS,
                     "%d:%d %s ref %d desc %d s %d w %d\n",
                     proc->pid, thread->pid, debug_string,
                     rdata.debug_id, rdata.desc, rdata.strong,
                     rdata.weak);
            break;
        }
        case BC_INCREFS_DONE:
        case BC_ACQUIRE_DONE: {
            binder_uintptr_t node_ptr;
            binder_uintptr_t cookie;
            struct binder_node *node;
            bool free_node;

            if (get_user(node_ptr, (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);
            if (get_user(cookie, (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);
            node = binder_get_node(proc, node_ptr);
            if (node == NULL) {
                binder_user_error("%d:%d %s u%016llx no match\n",
                    proc->pid, thread->pid,
                    cmd == BC_INCREFS_DONE ?
                    "BC_INCREFS_DONE" :
                    "BC_ACQUIRE_DONE",
                    (u64)node_ptr);
                break;
            }
            if (cookie != node->cookie) {
                binder_user_error("%d:%d %s u%016llx node %d cookie mismatch %016llx != %016llx\n",
                    proc->pid, thread->pid,
                    cmd == BC_INCREFS_DONE ?
                    "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE",
                    (u64)node_ptr, node->debug_id,
                    (u64)cookie, (u64)node->cookie);
                binder_put_node(node);
                break;
            }
            binder_node_inner_lock(node);
            if (cmd == BC_ACQUIRE_DONE) {
                if (node->pending_strong_ref == 0) {
                    binder_user_error("%d:%d BC_ACQUIRE_DONE node %d has no pending acquire request\n",
                        proc->pid, thread->pid,
                        node->debug_id);
                    binder_node_inner_unlock(node);
                    binder_put_node(node);
                    break;
                }
                node->pending_strong_ref = 0;
            } else {
                if (node->pending_weak_ref == 0) {
                    binder_user_error("%d:%d BC_INCREFS_DONE node %d has no pending increfs request\n",
                        proc->pid, thread->pid,
                        node->debug_id);
                    binder_node_inner_unlock(node);
                    binder_put_node(node);
                    break;
                }
                node->pending_weak_ref = 0;
            }
            free_node = binder_dec_node_nilocked(node,
                    cmd == BC_ACQUIRE_DONE, 0);
            WARN_ON(free_node);
            binder_debug(BINDER_DEBUG_USER_REFS,
                     "%d:%d %s node %d ls %d lw %d tr %d\n",
                     proc->pid, thread->pid,
                     cmd == BC_INCREFS_DONE ? "BC_INCREFS_DONE" : "BC_ACQUIRE_DONE",
                     node->debug_id, node->local_strong_refs,
                     node->local_weak_refs, node->tmp_refs);
            binder_node_inner_unlock(node);
            binder_put_node(node);
            break;
        }
        case BC_ATTEMPT_ACQUIRE:
            pr_err("BC_ATTEMPT_ACQUIRE not supported\n");
            return -EINVAL;
        case BC_ACQUIRE_RESULT:
            pr_err("BC_ACQUIRE_RESULT not supported\n");
            return -EINVAL;

        case BC_FREE_BUFFER: {
            binder_uintptr_t data_ptr;
            struct binder_buffer *buffer;

            if (get_user(data_ptr, (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);

            buffer = binder_alloc_prepare_to_free(&proc->alloc,
                                  data_ptr);
            if (IS_ERR_OR_NULL(buffer)) {
                if (PTR_ERR(buffer) == -EPERM) {
                    binder_user_error(
                        "%d:%d BC_FREE_BUFFER u%016llx matched unreturned or currently freeing buffer\n",
                        proc->pid, thread->pid,
                        (u64)data_ptr);
                } else {
                    binder_user_error(
                        "%d:%d BC_FREE_BUFFER u%016llx no match\n",
                        proc->pid, thread->pid,
                        (u64)data_ptr);
                }
                break;
            }
            binder_debug(BINDER_DEBUG_FREE_BUFFER,
                     "%d:%d BC_FREE_BUFFER u%016llx found buffer %d for %s transaction\n",
                     proc->pid, thread->pid, (u64)data_ptr,
                     buffer->debug_id,
                     buffer->transaction ? "active" : "finished");

            if (buffer->transaction) {
                buffer->transaction->buffer = NULL;
                buffer->transaction = NULL;
            }
            if (buffer->async_transaction && buffer->target_node) {
                struct binder_node *buf_node;
                struct binder_work *w;

                buf_node = buffer->target_node;
                binder_node_inner_lock(buf_node);
                BUG_ON(!buf_node->has_async_transaction);
                BUG_ON(buf_node->proc != proc);
                w = binder_dequeue_work_head_ilocked(
                        &buf_node->async_todo);
                if (!w) {
                    buf_node->has_async_transaction = false;
                } else {
                    binder_enqueue_work_ilocked(
                            w, &proc->todo);
                    binder_wakeup_proc_ilocked(proc);
                }
                binder_node_inner_unlock(buf_node);
            }
            trace_binder_transaction_buffer_release(buffer);
            binder_transaction_buffer_release(proc, buffer, 0, false);
            binder_alloc_free_buf(&proc->alloc, buffer);
            break;
        }

        case BC_TRANSACTION_SG:
        case BC_REPLY_SG: {
            struct binder_transaction_data_sg tr;

            if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
            ptr += sizeof(tr);
            binder_transaction(proc, thread, &tr.transaction_data,
                       cmd == BC_REPLY_SG, tr.buffers_size);
            break;
        }
        case BC_TRANSACTION:
        case BC_REPLY: {
            struct binder_transaction_data tr;

            if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
            ptr += sizeof(tr);
            binder_transaction(proc, thread, &tr,
                       cmd == BC_REPLY, 0);
            break;
        }

        case BC_REGISTER_LOOPER:
            binder_debug(BINDER_DEBUG_THREADS,
                     "%d:%d BC_REGISTER_LOOPER\n",
                     proc->pid, thread->pid);
            binder_inner_proc_lock(proc);
            if (thread->looper & BINDER_LOOPER_STATE_ENTERED) {
                thread->looper |= BINDER_LOOPER_STATE_INVALID;
                binder_user_error("%d:%d ERROR: BC_REGISTER_LOOPER called after BC_ENTER_LOOPER\n",
                    proc->pid, thread->pid);
            } else if (proc->requested_threads == 0) {
                thread->looper |= BINDER_LOOPER_STATE_INVALID;
                binder_user_error("%d:%d ERROR: BC_REGISTER_LOOPER called without request\n",
                    proc->pid, thread->pid);
            } else {
                proc->requested_threads--;
                proc->requested_threads_started++;
            }
            thread->looper |= BINDER_LOOPER_STATE_REGISTERED;
            binder_inner_proc_unlock(proc);
            break;
        case BC_ENTER_LOOPER:
            binder_debug(BINDER_DEBUG_THREADS,
                     "%d:%d BC_ENTER_LOOPER\n",
                     proc->pid, thread->pid);
            if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {
                thread->looper |= BINDER_LOOPER_STATE_INVALID;
                binder_user_error("%d:%d ERROR: BC_ENTER_LOOPER called after BC_REGISTER_LOOPER\n",
                    proc->pid, thread->pid);
            }
            thread->looper |= BINDER_LOOPER_STATE_ENTERED;
            break;
        case BC_EXIT_LOOPER:
            binder_debug(BINDER_DEBUG_THREADS,
                     "%d:%d BC_EXIT_LOOPER\n",
                     proc->pid, thread->pid);
            thread->looper |= BINDER_LOOPER_STATE_EXITED;
            break;

        case BC_REQUEST_DEATH_NOTIFICATION:
        case BC_CLEAR_DEATH_NOTIFICATION: {
            uint32_t target;
            binder_uintptr_t cookie;
            struct binder_ref *ref;
            struct binder_ref_death *death = NULL;

            if (get_user(target, (uint32_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(uint32_t);
            if (get_user(cookie, (binder_uintptr_t __user *)ptr))
                return -EFAULT;
            ptr += sizeof(binder_uintptr_t);
            if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {
                /*
                 * Allocate memory for death notification
                 * before taking lock
                 */
                death = kzalloc(sizeof(*death), GFP_KERNEL);
                if (death == NULL) {
                    WARN_ON(thread->return_error.cmd !=
                        BR_OK);
                    thread->return_error.cmd = BR_ERROR;
                    binder_enqueue_thread_work(
                        thread,
                        &thread->return_error.work);
                    binder_debug(
                        BINDER_DEBUG_FAILED_TRANSACTION,
                        "%d:%d BC_REQUEST_DEATH_NOTIFICATION failed\n",
                        proc->pid, thread->pid);
                    break;
                }
            }
            binder_proc_lock(proc);
            ref = binder_get_ref_olocked(proc, target, false);
            if (ref == NULL) {
                binder_user_error("%d:%d %s invalid ref %d\n",
                    proc->pid, thread->pid,
                    cmd == BC_REQUEST_DEATH_NOTIFICATION ?
                    "BC_REQUEST_DEATH_NOTIFICATION" :
                    "BC_CLEAR_DEATH_NOTIFICATION",
                    target);
                binder_proc_unlock(proc);
                kfree(death);
                break;
            }

            binder_debug(BINDER_DEBUG_DEATH_NOTIFICATION,
                     "%d:%d %s %016llx ref %d desc %d s %d w %d for node %d\n",
                     proc->pid, thread->pid,
                     cmd == BC_REQUEST_DEATH_NOTIFICATION ?
                     "BC_REQUEST_DEATH_NOTIFICATION" :
                     "BC_CLEAR_DEATH_NOTIFICATION",
                     (u64)cookie, ref->data.debug_id,
                     ref->data.desc, ref->data.strong,
                     ref->data.weak, ref->node->debug_id);

            binder_node_lock(ref->node);
            if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {
                if (ref->death) {
                    binder_user_error("%d:%d BC_REQUEST_DEATH_NOTIFICATION death notification already set\n",
                        proc->pid, thread->pid);
                    binder_node_unlock(ref->node);
                    binder_proc_unlock(proc);
                    kfree(death);
                    break;
                }
                binder_stats_created(BINDER_STAT_DEATH);
                INIT_LIST_HEAD(&death->work.entry);
                death->cookie = cookie;
                ref->death = death;
                if (ref->node->proc == NULL) {
                    ref->death->work.type = BINDER_WORK_DEAD_BINDER;

                    binder_inner_proc_lock(proc);
                    binder_enqueue_work_ilocked(
                        &ref->death->work, &proc->todo);
                    binder_wakeup_proc_ilocked(proc);
                    binder_inner_proc_unlock(proc);
                }
            } else {
                if (ref->death == NULL) {
                    binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification not active\n",
                        proc->pid, thread->pid);
                    binder_node_unlock(ref->node);
                    binder_proc_unlock(proc);
                    break;
                }
                death = ref->death;
                if (death->cookie != cookie) {
                    binder_user_error("%d:%d BC_CLEAR_DEATH_NOTIFICATION death notification cookie mismatch %016llx != %016llx\n",
                        proc->pid, thread->pid,
                        (u64)death->cookie,
                        (u64)cookie);
                    binder_node_unlock(ref->node);
                    binder_proc_unlock(proc);
                    break;
                }
                ref->death = NULL;
                binder_inner_proc_lock(proc);
                if (list_empty(&death->work.entry)) {
                    death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
                    if (thread->looper &
                        (BINDER_LOOPER_STATE_REGISTERED |
                         BINDER_LOOPER_STATE_ENTERED))
                        binder_enqueue_thread_work_ilocked(
                                thread,
                                &death->work);
                    else {
                        binder_enqueue_work_ilocked(
                                &death->work,
                                &proc->todo);
                        binder_wakeup_proc_ilocked(
                                proc);
                    }
                } else {
                    BUG_ON(death->work.type != BINDER_WORK_DEAD_BINDER);
                    death->work.type = BINDER_WORK_DEAD_BINDER_AND_CLEAR;
                }
                binder_inner_proc_unlock(proc);
            }
            binder_node_unlock(ref->node);
            binder_proc_unlock(proc);
        } break;
        case BC_DEAD_BINDER_DONE: {
            struct binder_work *w;
            binder_uintptr_t cookie;
            struct binder_ref_death *death = NULL;

            if (get_user(cookie, (binder_uintptr_t __user *)ptr))
                return -EFAULT;

            ptr += sizeof(cookie);
            binder_inner_proc_lock(proc);
            list_for_each_entry(w, &proc->delivered_death,
                        entry) {
                struct binder_ref_death *tmp_death =
                    container_of(w,
                             struct binder_ref_death,
                             work);

                if (tmp_death->cookie == cookie) {
                    death = tmp_death;
                    break;
                }
            }
            binder_debug(BINDER_DEBUG_DEAD_BINDER,
                     "%d:%d BC_DEAD_BINDER_DONE %016llx found %pK\n",
                     proc->pid, thread->pid, (u64)cookie,
                     death);
            if (death == NULL) {
                binder_user_error("%d:%d BC_DEAD_BINDER_DONE %016llx not found\n",
                    proc->pid, thread->pid, (u64)cookie);
                binder_inner_proc_unlock(proc);
                break;
            }
            binder_dequeue_work_ilocked(&death->work);
            if (death->work.type == BINDER_WORK_DEAD_BINDER_AND_CLEAR) {
                death->work.type = BINDER_WORK_CLEAR_DEATH_NOTIFICATION;
                if (thread->looper &
                    (BINDER_LOOPER_STATE_REGISTERED |
                     BINDER_LOOPER_STATE_ENTERED))
                    binder_enqueue_thread_work_ilocked(
                        thread, &death->work);
                else {
                    binder_enqueue_work_ilocked(
                            &death->work,
                            &proc->todo);
                    binder_wakeup_proc_ilocked(proc);
                }
            }
            binder_inner_proc_unlock(proc);
        } break;

        default:
            pr_err("%d:%d unknown command %d\n",
                   proc->pid, thread->pid, cmd);
            return -EINVAL;
        }
        *consumed = ptr - buffer;
    }
    return 0;
}

线程不足时,会发消息给应用层,应用层创建新的线程:

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    status_t result = NO_ERROR;
    switch ((uint32_t)cmd) {
      ...
      case BR_SPAWN_LOOPER:
          //创建新的binder线程 【见小节2.3】
          mProcess->spawnPooledThread(false);
          break;
      ...
    }
    return result;
}

Java 进程 Binder 线程初始化

Java 层进程的创建都是通过 Process.start() 方法,向 Zygote 进程发出创建进程的 socket 消息,Zygote 收到消息后会调用 Zygote.forkAndSpecialize() 来 fork 出新进程,在新进程中会调用到 RuntimeInit.nativeZygoteInit 方法,该方法经过 jni 映射,最终会调用到 pp_main.cpp 中的 onZygoteInit

virtual void onZygoteInit() {
    //获取ProcessState对象
    sp<ProcessState> proc = ProcessState::self();
    //启动新binder线程 
    proc->startThreadPool();
}
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/20597,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?