Android : 跟我學Binder --- (5) C++實現



目錄:

 

 

一、程序實現

參考文件:
frameworks\av\include\media\IMediaPlayerService.h     (IMediaPlayerService,BnMediaPlayerService)
frameworks\av\media\libmedia\IMediaPlayerService.cpp                      (BpMediaPlayerService)
frameworks\av\media\libmediaplayerservice\MediaPlayerService.h
frameworks\av\media\libmediaplayerservice\MediaPlayerService.cpp
frameworks\av\media\mediaserver\Main_mediaserver.cpp   (server, addService)

  之前的代碼結構是 test_server 向 service_manager 注冊服務,test_client 通過 service_manager 獲取服務並使用,服務的實現和數據解析都是在單一的.c文件中實現,接下來對程序框架進行改進,將服務具體函數抽離出來模塊化實現,統一由頭文件分別定義服務的接口: IHelloService.h 和 IGoodbyeService.h,然后cpp文件實現具體功能:server端為 BnHelloService.cpp 和 BnGoodbyeService.cpp,client端為 BpHelloService.cpp 和 BpGoodbyeService.cpp。

 (1)接口定義:I代表interface)

  ①IHelloService.h

/* 參考: frameworks\av\include\media\IMediaPlayerService.h */
#ifndef ANDROID_IHELLOERVICE_H #define ANDROID_IHELLOERVICE_H #include <utils/Errors.h> // for status_t #include <utils/KeyedVector.h> #include <utils/RefBase.h> #include <utils/String8.h> #include <binder/IInterface.h> #include <binder/Parcel.h> #define HELLO_SVR_CMD_SAYHELLO 1 #define HELLO_SVR_CMD_SAYHELLO_TO 2 #define HELLO_SVR_CMD_GET_FD 3 namespace android { class IHelloService: public IInterface { public: DECLARE_META_INTERFACE(HelloService); //宏自動聲明必須的接口 virtual void sayhello(void) = 0; virtual int sayhello_to(const char *name) = 0; virtual int get_fd(void) = 0; }; class BnHelloService: public BnInterface<IHelloService> { private: int fd; public: virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0); virtual void sayhello(void); virtual int sayhello_to(const char *name); virtual int get_fd(void); BnHelloService(); BnHelloService(int fd); }; } #endif

  ②IGoodbyeService.h

/* 參考: frameworks\av\include\media\IMediaPlayerService.h */

#ifndef ANDROID_IGOODBYEERVICE_H
#define ANDROID_IGOODBYEERVICE_H

#include <utils/Errors.h>  // for status_t
#include <utils/KeyedVector.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>

#define GOODBYE_SVR_CMD_SAYGOODBYE     1
#define GOODBYE_SVR_CMD_SAYGOODBYE_TO  2


namespace android {

class IGoodbyeService: public IInterface
{
public:
    DECLARE_META_INTERFACE(GoodbyeService);
    virtual void saygoodbye(void) = 0;
    virtual int saygoodbye_to(const char *name) = 0;
};

class BnGoodbyeService: public BnInterface<IGoodbyeService>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);

    virtual void saygoodbye(void);
    virtual int saygoodbye_to(const char *name);

};
}

#endif

 

 (2)功能實現:B代表binder,n代表native-本地實現,p代表proxy-代理)

  ①BnHelloService.cpp

/* 參考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */

#define LOG_TAG "HelloService"

#include "IHelloService.h"


namespace android {

BnHelloService::BnHelloService()
{
}

BnHelloService::BnHelloService(int fd)
{
    this->fd = fd;
}

status_t BnHelloService::onTransact( uint32_t code,
                                const Parcel& data,
                                Parcel* reply,
                                uint32_t flags)
{
    /* 解析數據,調用sayhello/sayhello_to */

    switch (code) {
        case HELLO_SVR_CMD_SAYHELLO: {
            sayhello();
            reply->writeInt32(0);  /* no exception */
            return NO_ERROR;
        } break;
        
        case HELLO_SVR_CMD_SAYHELLO_TO: {

            /* 從data中取出參數 */
            int32_t policy =  data.readInt32();
            String16 name16_tmp = data.readString16(); /* IHelloService */
            
            String16 name16 = data.readString16();
            String8 name8(name16);

            int cnt = sayhello_to(name8.string());

            /* 把返回值寫入reply傳回去 */
            reply->writeInt32(0);  /* no exception */
            reply->writeInt32(cnt);
            
            return NO_ERROR;
        } break;

        case HELLO_SVR_CMD_GET_FD: {
            int fd = this->get_fd();
            reply->writeInt32(0);  /* no exception */

            /* 參考:
             * frameworks\base\core\jni\android_view_InputChannel.cpp
             * android_view_InputChannel_nativeWriteToParcel
             */
            reply->writeDupFileDescriptor(fd);
            return NO_ERROR;
        } break;

        
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

void BnHelloService::sayhello(void)
{
    static int cnt = 0;
    ALOGI("say hello : %d\n", ++cnt);

}

int BnHelloService::sayhello_to(const char *name)
{
    static int cnt = 0;
    ALOGI("say hello to %s : %d\n", name, ++cnt);
    return cnt;
}

int BnHelloService::get_fd(void)
{
    return fd;
}


}

 

  ②BnGoodbyeService.cpp

/* 參考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */

#define LOG_TAG "GoodbyeService"

#include "IGoodbyeService.h"


namespace android {

status_t BnGoodbyeService::onTransact( uint32_t code,
                                const Parcel& data,
                                Parcel* reply,
                                uint32_t flags)
{
    /* 解析數據,調用saygoodbye/saygoodbye_to */

    switch (code) {
        case GOODBYE_SVR_CMD_SAYGOODBYE: {
        saygoodbye();
            reply->writeInt32(0);  /* no exception */
            return NO_ERROR;
        } break;
        
        case GOODBYE_SVR_CMD_SAYGOODBYE_TO: {

            /* 從data中取出參數 */
            int32_t policy =  data.readInt32();
            String16 name16_tmp = data.readString16(); /* IGoodbyeService */
            
            String16 name16 = data.readString16();
            String8 name8(name16);

            int cnt = saygoodbye_to(name8.string());

            /* 把返回值寫入reply傳回去 */
            reply->writeInt32(0);  /* no exception */
            reply->writeInt32(cnt);
            
            return NO_ERROR;
        } break;
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

void BnGoodbyeService::saygoodbye(void)
{
    static int cnt = 0;
    ALOGI("say goodbye : %d\n", ++cnt);

}

int BnGoodbyeService::saygoodbye_to(const char *name)
{
    static int cnt = 0;
    ALOGI("say goodbye to %s : %d\n", name, ++cnt);
    return cnt;
}

}

 

  ③BpHelloService.cpp

/* 參考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */

#include "IHelloService.h"

namespace android {

class BpHelloService: public BpInterface<IHelloService>
{
public:
    BpHelloService(const sp<IBinder>& impl)
        : BpInterface<IHelloService>(impl)
    {
    }

    void sayhello(void)
    {
        /* 構造/發送數據 */

        Parcel data, reply;
        data.writeInt32(0);
        data.writeString16(String16("IHelloService"));

        remote()->transact(HELLO_SVR_CMD_SAYHELLO, data, &reply);
    }
    
    int sayhello_to(const char *name)
    {
        /* 構造/發送數據 */
        Parcel data, reply;
        int exception;

        data.writeInt32(0);
        data.writeString16(String16("IHelloService"));

        data.writeString16(String16(name));

        remote()->transact(HELLO_SVR_CMD_SAYHELLO_TO, data, &reply);

        exception = reply.readInt32();
        if (exception)
            return -1;
        else
            return reply.readInt32();
    }

    int get_fd(void)
    {
        /* 構造/發送數據 */
        Parcel data, reply;
        int exception;

        data.writeInt32(0);
        data.writeString16(String16("IHelloService"));

        remote()->transact(HELLO_SVR_CMD_GET_FD, data, &reply);

        exception = reply.readInt32();
        if (exception)
            return -1;
        else
        {

            /* 參考:
             * frameworks\base\core\jni\android_view_InputChannel.cpp
             * android_view_InputChannel_nativeReadFromParcel
             */
            int rawFd = reply.readFileDescriptor();
            return dup(rawFd);
        }
    }


};

IMPLEMENT_META_INTERFACE(HelloService, "android.media.IHelloService");

}

 

  ④BpGoodbyeService.cpp

/* 參考: frameworks\av\media\libmedia\IMediaPlayerService.cpp */

#include "IGoodbyeService.h"

namespace android {

class BpGoodbyeService: public BpInterface<IGoodbyeService>
{
public:
    BpGoodbyeService(const sp<IBinder>& impl)
        : BpInterface<IGoodbyeService>(impl)
    {
    }

    void saygoodbye(void)
    {
        /* 構造/發送數據 */

        Parcel data, reply;
        data.writeInt32(0);
        data.writeString16(String16("IGoodbyeService"));

        remote()->transact(GOODBYE_SVR_CMD_SAYGOODBYE, data, &reply);
    }
    
    int saygoodbye_to(const char *name)
    {
        /* 構造/發送數據 */
        Parcel data, reply;
        int exception;

        data.writeInt32(0);
        data.writeString16(String16("IGoodbyeService"));
        
        data.writeString16(String16(name));

        remote()->transact(GOODBYE_SVR_CMD_SAYGOODBYE_TO, data, &reply);

        exception = reply.readInt32();
        if (exception)
            return -1;
        else
            return reply.readInt32();
        }

};

IMPLEMENT_META_INTERFACE(GoodbyeService, "android.media.IGoodbyeService");

}

 

(3)測試代碼:

  ①test_server.cpp

/* 參考: frameworks\av\media\mediaserver\Main_mediaserver.cpp */

//#define LOG_NDEBUG 0

#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>

#include "IHelloService.h"
#include "IGoodbyeService.h"

#define SOCKET_BUFFER_SIZE      (32768U)

using namespace android;

/* 參考:
 * http://blog.csdn.net/linan_nwu/article/details/8222349
 */
class MyThread: public Thread {  
private:
    int fd;
public:  
    MyThread() {}
    MyThread(int fd) { this->fd = fd; }
 
        
    //如果返回true,循環調用此函數,返回false下一次不會再調用此函數  
    bool threadLoop()
    {
        char buf[500];
        int len;
        int cnt = 0;
        
        while(1)
        {
            /* 讀數據: test_client發出的數據 */
            len = read(fd, buf, 500);
            buf[len] = '\0';
            ALOGI("%s\n", buf);
            
            /* 向 test_client 發出: Hello, test_client */
            len = sprintf(buf, "Hello, test_client, cnt = %d", cnt++);
            write(fd, buf, len);
        }
        
           return true;  
    }
  
};  


/* usage : test_server  */
int main(void)
{

    int sockets[2];

    socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets);

    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

    /* 創建一個線程, 用於跟test_client使用socketpiar通信 */
    sp<MyThread> th = new MyThread(sockets[0]);
    th->run();  


    /* addService */

    /* while(1){ read data, 解析數據, 調用服務函數 } */

    /* 打開驅動, mmap */
    sp<ProcessState> proc(ProcessState::self());

    /* 獲得BpServiceManager */
    sp<IServiceManager> sm = defaultServiceManager();

    sm->addService(String16("hello"), new BnHelloService(sockets[1]));
    sm->addService(String16("goodbye"), new BnGoodbyeService());

    /* 循環體 */
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();

    return 0;
}

 

  ②test_client.cpp

//#define LOG_NDEBUG 0

#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <unistd.h>

#include "IHelloService.h"
#include "IGoodbyeService.h"

using namespace android;

/* ./test_client <hello|goodbye>
 * ./test_client <readfile>
 * ./test_client <hello|goodbye> <name>
 */
int main(int argc, char **argv)
{
    int cnt;
    
    if (argc < 2){
        ALOGI("Usage:\n");
        ALOGI("%s <readfile>\n", argv[0]);
        ALOGI("%s <hello|goodbye>\n", argv[0]);
        ALOGI("%s <hello|goodbye> <name>\n", argv[0]);
        return -1;
    }

    /* getService */
    /* 打開驅動, mmap */
    sp<ProcessState> proc(ProcessState::self());

    /* 獲得BpServiceManager */
    sp<IServiceManager> sm = defaultServiceManager();

    if (strcmp(argv[1], "hello") == 0)
    {

        sp<IBinder> binder =
            sm->getService(String16("hello"));

        if (binder == 0)
        {
            ALOGI("can't get hello service\n");
            return -1;
        }

        /* service肯定是BpHelloServie指針 */
        sp<IHelloService> service =
            interface_cast<IHelloService>(binder);


        /* 調用Service的函數 */
        if (argc < 3) {
            service->sayhello();
            ALOGI("client call sayhello");
        }
        else {
            cnt = service->sayhello_to(argv[2]);
            ALOGI("client call sayhello_to, cnt = %d", cnt);
        }
    }
    else if (strcmp(argv[1], "readfile") == 0)
    {

        sp<IBinder> binder =
            sm->getService(String16("hello"));

        if (binder == 0)
        {
            ALOGI("can't get hello service\n");
            return -1;
        }

        /* service肯定是BpHelloServie指針 */
        sp<IHelloService> service =
            interface_cast<IHelloService>(binder);


        /* 調用Service的函數 */
        int fd = service->get_fd();

        ALOGI("client call get_fd = %d", fd);

        char buf[500];
        int len;
        int cnt = 0;
        
        while (1)
        {
            /* 向 test_server 進程發出: Hello, test_server    */
            len = sprintf(buf, "Hello, test_server, cnt = %d", cnt++);
            write(fd, buf, len);
        
            /* 讀取數據(test_server進程發回的數據) */
            len = read(fd, buf, 500);
            buf[len] = '\0';
            ALOGI("%s\n", buf);
        
            sleep(5);
        }
    }
    else
    {

        sp<IBinder> binder =
            sm->getService(String16("goodbye"));

        if (binder == 0)
        {
            ALOGI("can't get goodbye service\n");
            return -1;
        }

        /* service肯定是BpGoodbyeServie指針 */
        sp<IGoodbyeService> service =
            interface_cast<IGoodbyeService>(binder);


        /* 調用Service的函數 */
        if (argc < 3) {
            service->saygoodbye();
            ALOGI("client call saygoodbye");
        }
        else {
            cnt = service->saygoodbye_to(argv[2]);
            ALOGI("client call saygoodbye_to, cnt = %d", cnt);
        }
    }
    
    return 0;
}

 

(4)Android.mk:

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
    BnHelloService.cpp \
    BpHelloService.cpp \
    BnGoodbyeService.cpp \
    BpGoodbyeService.cpp \
    test_server.cpp

LOCAL_SHARED_LIBRARIES := \
    libcutils \
    libutils \
    liblog \
    libbinder 


LOCAL_MODULE:= test_server
LOCAL_32_BIT_ONLY := true

include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
    BpHelloService.cpp \
    BpGoodbyeService.cpp \
    test_client.cpp

LOCAL_SHARED_LIBRARIES := \
    libcutils \
    libutils \
    liblog \
    libbinder 


LOCAL_MODULE:= test_client
LOCAL_32_BIT_ONLY := true

include $(BUILD_EXECUTABLE)

 

二、內部機制

1.回顧binder框架關鍵點

  test_server向service_manager添加服務,test_client通過service_manager獲取服務,具體流程如下:

①add_service:
  a.test_server為每個服務構造 struct flat_binder_object 結構體,其中void *binder 或 void* cookie 對應不同服務;
  b.調用ioctl發送數據:
    b1. 數據:flat_binder_object + 服務名稱;
    b2.數據中含有"目的地":handle=0,則代表 service_manager;
  c.驅動程序對每一個flat_binder_object構造一個binder_node結構體,其中 void __user *ptr 和 void __user *cookie即來自flat_binder_object;
  d.驅動程序根據handle=0找到service_manager,把數據發送給service_manager並且創建一個 struct binder_ref結構體到鏈表,其中node指針指向binder_node結構體;
  e.service_manager中記錄服務名(“hello”、“goodbye”)稱和desc值(binder_ref結構體中的desc值);

②get_service: 
  a.test_client構造數據:名稱 + "目的"(handle=0);
  b.調ioctl發送數據;
  c.驅動程序根據handle=0找到service_manager把數據給service_manager;
  d.service_manager從service list中找到對應項,比如根據服務名"hello"找到第一項,handle=1;
  e.service_manager調用ioctl返回數據(flat_binder_object);
  f.驅動發現數據中含有flat_binder_object,且type為引用,從service_manager的binder_ref表中找到對應項(傳入的handle=binder_ref.desc)再找到binder_node,最后為test_client建立binder_ref,即對應service_manager里構造的binder_ref鏈表;

③test_client使用服務: hello->sayhello(code=1), hello->sayhello_to(code=2)
  a.構造數據,code定義要執行的函數、參數、目的(handle=1);
  b.使用ioctl發送數據;
  c.驅動從數據中取出handle=1,根據handle找到binder_ref,根據binder_ref找到binder_node,再根據binder_node找到目的進程(.proc->test_server),最后把數據傳給test_server,並且在數據中設置.ptr/.cookie 等於binder_node的.ptr/.cookie;
  d.test_server根據.ptr/.cookie獲知test_client想調用的服務,再根據code等參數調用具體的函數操作;

 

小結:

  server注冊服務時, 對每個服務都提供不同的ptr/cookie,在驅動程序里對每個服務都構造一個binder_node, 它也含有ptr/cookie,client使用服務前要先getService,在驅動程序里對該服務構造一個binder_ref,binder_ref含有desc, node成員, desc是整數, node指向對應服務的binder_node,使用服務時, client調用ioctl發送構造數據,數據里含有handle,驅動程序根據handle找到binder_ref(desc==handle), 再通過binder_ref找到binder_node, 再根據binder_node找到對應server,最后從binder_node取出ptr/cookie連同那些數據發給server,server根據數據中的ptr/cookie信息調用對應服務,再根據code調用對應函數。

Binder系統最核心的函數: ioclt;

client最核心的數據:handle;

server最核心的數據:.ptr/.cookie

 

2.代理類BpXXX分析

  test_server向service_manager添加服務時,首先獲得BpServiceManager(handle=0)成為一個client與之通信,test_client同樣獲得BpServiceManager和service_manager通信,然后test_client再獲得BpHelloService(handle=BpServiceManager->getService("Hello")==1)和test_server通信。

 UML展示BpServiceManager的繼承關系

                                (1)

 

                                (2)

 

                                (3)

 

 

   2.1 獲得BpServiceManager對象的過程:
    defaultServiceManager構造了一個BpServiceManager對象(派生自BpRefBase,含有IBinder *mRemote, mRemote指向BpBinder對象,它含有mHandle),
 其中它的mRemote = new BpBinder(0); // mRemote->mHandle=0

 defaultServiceManager // IServiceManager.cpp
                               // 把BpBinder(mHandle=0)對象轉換為IServiceManager接口(BpServiceManager)                           
    gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
 分析:
 ProcessState::self()->getContextObject(NULL)
     getStrongProxyForHandle(0);
         b = new BpBinder(handle);   // mHandle=handle=0

 interface_cast<IServiceManager>(new BpBinder(0))  // IInterface.h
     IServiceManager::asInterface(obj);
            return new BpServiceManager(obj); // mRemote=obj=new BpBinder(0);


   2.2 獲得BpHelloService對象的過程:
    調用BpServiceManager的getService函數獲得一個flat_binder_object,
    從中取出handle, 創建一個BpBinder(handle),
    然后使用interface_cast使用這個BpBinder創建一個BpHelloService對象

 // binder是BpBinder對象, 里面含有HelloService的handle
 sp<IBinder> binder =
        sm->getService(String16("hello")); // IServiceManager.cpp
                    // 構造數據: 數據中肯定含有"hello"
                    // 發送數據: 給handle 0, 即 service_manager進程
                    // 從收到的回復中取出HelloService的handle
                             return reply.readStrongBinder();
                                                     unflatten_binder(ProcessState::self(), *this, &val);
                                                             *out = proc->getStrongProxyForHandle(flat->handle);
                                                                                 new BpBinder(handle);  //handle來自service_manager進程的回復
        
                            // 把binder轉換為IHelloService接口(BpHelloService對象)
                            // binder是BpBinder對象, 里面含有HelloService的handle
 sp<IHelloService> service = interface_cast<IHelloService>(binder);


   2.3 代理類如何發送數據: ioctl, 數據里含有handle, 含有其他構造的參數
        構造好數據之后,調用:
        remote()->transact(...)  //addService、checkService、getService 以及sayhello/sayhello_to等函數最終都會調用這里,remote()返回一個BpBinder對象,其中實現了transact;
             IPCThreadState::self()->transact(mHandle, code, data, reply, flags); //該線程中實現了waitForResponse()、talkWithDriver(其中通過mDriverFD句柄進行ioctl操作) 等函數。

 

3.數據傳輸:ProcessState 和 IPCThreadState 類 (單例模式)

 ProcessState::self()->startThreadPool(); //創建子線程,最終執行:IPCThreadState ::self()->joinThreadPool();

 IPCThreadState ::self()->joinThreadPool(); //循環,讀取數據、解析、處理、回復。


   3.1 addService
    前面介紹過,對於不同服務構造的flat_binder_object結構體,里面的.binder/.cookie對於不同的服務它的值不一樣
                                                        
 sm->addService(String16("hello"), new BnHelloService());
            data.writeStrongBinder(service);  // service = new BnHelloService();
                            flatten_binder(ProcessState::self(), val, this); // val = service = new BnHelloService();
                                    flat_binder_object obj;  // 參數 binder = val = service = new BnHelloService();
                                    IBinder *local = binder->localBinder(); // =this = new BnHelloService();
                        obj.type = BINDER_TYPE_BINDER;
                        obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
                        obj.cookie = reinterpret_cast<uintptr_t>(local);  // new BnHelloService();
                                   
   3.2 server如何分辨client想使用哪一個服務?
    server收到數據里含有flat_binder_object結構體,
    它可以根據.binder/.cookie分析client想使用哪一個服務
    
    把.cookie轉換為BnXXXX對象,然后調用它的函數:
            // 根據cookie構造了一個BBinder指針, 實際上是指向某個BnXXX對象
            sp<BBinder> b((BBinder*)tr.cookie);
            // 然后調用它的transact函數
            error = b->transact(tr.code, buffer, &reply, tr.flags);
                                        err = onTransact(code, data, reply, flags);  // 就會調用到BnXXX里實現的onTransact
                                // 它就會根據code值來調用不同的函數 

 

-end-


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM