zoukankan      html  css  js  c++  java
  • android4.3 Bluetooth(le)分析之startLeScan分析

    BluetoothAdapter.java中有low enery(le)的一些方法,android提供了这些方法,但源码中并未找到这些方法的调用之处。本文档主要分析这类方法的执行流程,来了解下le到底做了些什么。

     

    本文主要就是分析下startLeScan方法(两个重载方法)。

        public boolean startLeScan(LeScanCallback callback) {
            return startLeScan(null, callback);
        }
    
        public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) { 
            if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);                                                                                                           
    
    
            synchronized(mLeScanClients) {     
                if (mLeScanClients.containsKey(callback)) { 
                    if (DBG) Log.e(TAG, "LE Scan has already started");                                                                                                      
                    return false;
                }
    
                try {
                    //获取BluetoothGattBinder类的实例,该类的定义在GattService.java中
                    IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();                                                                                               
                    if (iGatt == null) {               
                        // BLE is not supported        
                        return false;              
                    }
    
                    UUID uuid = UUID.randomUUID(); 
                    GattCallbackWrapper wrapper = new GattCallbackWrapper(this, callback, serviceUuids);                                                     
                    //重点分析该方法。作用是为本地设备进行注册,以及启动扫描
                    //wrapper是GattCallbackWrapper类的对象。该类注册了一些Gatt协议的回调方法
                    iGatt.registerClient(new ParcelUuid(uuid), wrapper); 
                    if (wrapper.scanStarted()) {       
                        mLeScanClients.put(callback, wrapper);
                        return true;               
                    }
                } catch (RemoteException e) {      
                    Log.e(TAG,"",e);           
                }
            }
            return false;
        }

    下面来分析下iGatt.registerClient(new ParcelUuid(uuid), wrapper)方法,路径如下:(packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java::BluetoothGattBinder)

            public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) {
                GattService service = getService();
                if (service == null) return;   
                service.registerClient(uuid.getUuid(), callback);
            }

    接着会调用GattService服务的同名方法

        void registerClient(UUID uuid, IBluetoothGattCallback callback) {
            enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    
            if (DBG) Log.d(TAG, "registerClient() - UUID=" + uuid);
            mClientMap.add(uuid, callback);
            gattClientRegisterAppNative(uuid.getLeastSignificantBits(),
                                        uuid.getMostSignificantBits());
        }

    接下来会调用jni层com_android_bluetooth_gatt.cpp文件中的gattClientRegisterAppNative方法。

    static void gattClientRegisterAppNative(JNIEnv* env, jobject object,
                                            jlong app_uuid_lsb, jlong app_uuid_msb )
    {
        bt_uuid_t uuid;
    
        if (!sGattIf) return;
        set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
        sGattIf->client->register_client(&uuid);
    }

    分析sGattIf->client->register_client(&uuid);语句

    (1)sGattIf是一个静态变量,定义是static const btgatt_interface_t *sGattIf = NULL;

     

    又是这种类型的变量。第一反应就是去找btgatt_interface_t结构体定义的头文件(一般在hardware目录),然后再搜索调用的c文件(一般在external/bluetooth/bluedroid,有时找到的c文件与头文件同名)。

    btgatt_interface_t结构体的定义:hardware/libhardware/include/hardware/bt_gatt.h

    /** Represents the standard Bluetooth GATT interface. */
    typedef struct {
        /** Set to sizeof(btgatt_interface_t) */
        size_t          size;
    
        /**
         * Initializes the interface and provides callback routines
         */
        bt_status_t (*init)( const btgatt_callbacks_t* callbacks );
    
        /** Closes the interface */    
        void (*cleanup)( void );
    
        /** Pointer to the GATT client interface methods.*/
        const btgatt_client_interface_t* client;
    
        /** Pointer to the GATT server interface methods.*/
        const btgatt_server_interface_t* server;
    } btgatt_interface_t;

    btgatt_interface_t结构体的对象:external/bluetooth/bluedroi/btif/src/btif_gatt.c

    static const btgatt_interface_t btgattInterface = {
        sizeof(btgattInterface),
    
        btif_gatt_init,
        btif_gatt_cleanup,
    
        &btgattClientInterface,
        &btgattServerInterface,   
    };

    回到sGattIf->client->register_client(&uuid);语句,它调用了sGattIf结构体对象中的client对象的register_client函数,那么就是btgattClientInterface对象的register_client函数。

     

    由结构体的定义可知client对象的类型是btgatt_client_interface_t结构体。同理分析可得以下结果,

    btgatt_client_interface_t结构体的定义:hardware/libhardware/include/hardware/ bt_gatt_client.h

    typedef struct {
        /** Registers a GATT client application with the stack */
        bt_status_t (*register_client)( bt_uuid_t *uuid );
    
        /** Unregister a client application from the stack */
      bt_status_t (*unregister_client)(int client_if );
      ......
    }

    btgatt_client_interface_t结构体的对象:external/bluetooth/bluedroi/btif/src/btif_gatt_client.c

    const btgatt_client_interface_t btgattClientInterface = {
        btif_gattc_register_app,
        btif_gattc_unregister_app,
        btif_gattc_scan,
      ......
    };

    因此client->register_client就是调用了btif_gattc_register_app方法[-->btif_gatt_client.c]。

    static bt_status_t btif_gattc_register_app(bt_uuid_t *uuid)
    {
        CHECK_BTGATT_INIT();
        btif_gattc_cb_t btif_cb;
        memcpy(&btif_cb.uuid, uuid, sizeof(bt_uuid_t));
        return btif_transfer_context(btgattc_handle_event, BTIF_GATTC_REGISTER_APP,
                                     (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
    }

    分析btgattc_handle_event函数

    static void btgattc_handle_event(uint16_t event, char* p_param)
    {
        ......
        btif_gattc_cb_t* p_cb = (btif_gattc_cb_t*)p_param;
        if (!p_cb) return;
    
        switch (event)
        {
            case BTIF_GATTC_REGISTER_APP:
                btif_to_bta_uuid(&uuid, &p_cb->uuid);
                //为uuid注册回调函数
                BTA_GATTC_AppRegister(&uuid, bte_gattc_cback);
                break;
            .......
        }
    }

    分析BTA_GATTC_AppRegister函数

    -------------------------------------------------------------------------------------------------------

    void BTA_GATTC_AppRegister(tBT_UUID *p_app_uuid, tBTA_GATTC_CBACK *p_client_cb)                                                                                          
    {
        tBTA_GATTC_API_REG  *p_buf;                                                                                                                                          
    
        /* register with BTA system manager */
      GKI_sched_lock();
      //注册Gatt客户端主事件处理函数bta_gattc_hdl_event,在bta_gatt_reg结构体中定义。
        bta_sys_register(BTA_ID_GATTC, &bta_gatt_reg);
        GKI_sched_unlock();
    
        if ((p_buf = (tBTA_GATTC_API_REG *) GKI_getbuf(sizeof(tBTA_GATTC_API_REG))) != NULL)                                                                                 
        {
            p_buf->hdr.event    = BTA_GATTC_API_REG_EVT;
            if (p_app_uuid != NULL)            
                memcpy(&p_buf->app_uuid, p_app_uuid, sizeof(tBT_UUID));
            p_buf->p_cback      = p_client_cb;                                                                                                                               
    
            bta_sys_sendmsg(p_buf);    
        }
        return;
    }

    (a)通过bta_sys_register函数注册了bta_gatt_reg结构体中定义的客户端主事件处理函数bta_gattc_hdl_event;然后设置event为BTA_GATTC_API_REG_EV,触发bta_gattc_hdl_event函数。

    BOOLEAN bta_gattc_hdl_event(BT_HDR *p_msg)
    {
        tBTA_GATTC_CB *p_cb = &bta_gattc_cb;
        tBTA_GATTC_CLCB *p_clcb = NULL;
    
    #if BTA_GATT_DEBUG == TRUE
        APPL_TRACE_DEBUG1("bta_gattc_hdl_event: Event [%s]", gattc_evt_code(p_msg->event));                                                                                  
    #endif
        switch (p_msg->event)
        {
            case BTA_GATTC_API_REG_EVT:        
                bta_gattc_register(p_cb, (tBTA_GATTC_DATA *) p_msg);
                break;
            ......
      }
    }

    (b)调用bta_gattc_register函数。该函数用来注册一个客户端Gatt应用程序。

    void bta_gattc_register(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_data)
    {
        ......
        /* callback with register event */
        if (p_data->api_reg.p_cback)
        {
            (*p_data->api_reg.p_cback)(BTA_GATTC_REG_EVT,  (tBTA_GATTC *)&cb_data);
        }
    }

    调用相关event(BTA_GATTC_REG_EVT)的回调函数。

    到此,BTA_GATTC_AppRegister函数分析完毕,接下来分析BTA_GATTC_AppRegister(&uuid, bte_gattc_cback);中的参数部分。

    ps:上述的回调函数就是这里的参数:bte_gattc_cback函数。那么BTA_GATTC_REG_EVT事件就调用该函数处理了。

    -------------------------------------------------------------------------------------------------------

     

    分析回调函数bte_gattc_cback 

    static void bte_gattc_cback(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
    {
        bt_status_t status = btif_transfer_context(btif_gattc_upstreams_evt,
                        (uint16_t) event, (void*)p_data, sizeof(tBTA_GATTC), NULL);
        ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status);
    }

    分析btif_gattc_upstreams_evt函数,在该函数中会处理BTA_GATTC_REG_EVT事件。

    static void btif_gattc_upstreams_evt(uint16_t event, char* p_param)
    {
        tBTA_GATTC *p_data = (tBTA_GATTC*)p_param;
        switch (event)
        {  
            case BTA_GATTC_REG_EVT:        
            {
                bt_uuid_t app_uuid;            
                bta_to_btif_uuid(&app_uuid, &p_data->reg_oper.app_uuid);
                HAL_CBACK(bt_gatt_callbacks, client->register_client_cb
                    , p_data->reg_oper.status      
                    , p_data->reg_oper.client_if   
                    , &app_uuid
                );
                break;
            }
        ......
      }
    }

    bt_gatt_callbacks对象的类型是btgatt_callbacks_t,其定义在hardware/libhardware/include/hardware/bt_gatt.h文件中。现在对bt_gatt_callbacks对象从头开始分析其来源。

     

    在GattService.java::start()方法中,调用了initializeNative方法。继而调用JNI层initializeNative方法。贴出该方法。

    static const btgatt_interface_t *sGattIf = NULL;
    static const bt_interface_t* btIf;
    ......
    static void initializeNative(JNIEnv *env, jobject object) {
        /* getBluetoothInterface 函数返回sBluetoothInterface对象,在android4.3 bt 扫描分析.docx中已说明该对象的来源*/
        if ( (btIf = getBluetoothInterface()) == NULL) {
            error("Bluetooth module is not loaded");
            return;
        }  
      ......
      //(a)
      // BT_PROFILE_GATT_ID的值是”gatt”
        if ( (sGattIf = (btgatt_interface_t *)
              btIf->get_profile_interface(BT_PROFILE_GATT_ID)) == NULL) {
            error("Failed to get Bluetooth GATT Interface");
            return;
        } 
    
      bt_status_t status;
      //(b)
      /* sGattCallbacks的定义
      static const btgatt_callbacks_t sGattCallbacks = {
          sizeof(btgatt_callbacks_t),
          &sGattClientCallbacks,
          &sGattServerCallbacks
      };*/
        if ( (status = sGattIf->init(&sGattCallbacks)) != BT_STATUS_SUCCESS) {
            error("Failed to initialize Bluetooth GATT, status: %d", status);
            sGattIf = NULL;
            return;
        }  
    
        mCallbacksObj = env->NewGlobalRef(object);
    }

    (a) 分析

    static const void* get_profile_interface (const char *profile_id)
    {
        ......
    #if BTA_GATT_INCLUDED == TRUE 
        if (is_profile(profile_id, BT_PROFILE_GATT_ID))
            return btif_gatt_get_interface();
    #endif
        return NULL;
    }

    分析btif_gatt_get_interface函数

    const btgatt_interface_t *btif_gatt_get_interface()
    {
        return &btgattInterface;
    }

    btgattInterface对象的类型是btgatt_interface_t结构体。再贴一遍该结构体的定义,如下:

    typedef struct {
        /** Set to sizeof(btgatt_interface_t) */
        size_t          size;
    
        /**
         * Initializes the interface and provides callback routines
         */
        bt_status_t (*init)( const btgatt_callbacks_t* callbacks );
    
        /** Closes the interface */    
        void (*cleanup)( void );
    
        /** Pointer to the GATT client interface methods.*/
        const btgatt_client_interface_t* client;
    
        /** Pointer to the GATT server interface methods.*/
        const btgatt_server_interface_t* server;
    } btgatt_interface_t;

    另,btgattInterface对象定义如下:

    static const btgatt_interface_t btgattInterface = {
        sizeof(btgattInterface),
    
        btif_gatt_init,
        btif_gatt_cleanup,
    
        &btgattClientInterface,
        &btgattServerInterface,
    };

    所以sGattIf 就是btgattInterface对象。

     

    (b) 接下来调用sGattIf->init函数。由上可知,即为btif_gatt_init函数。

    static bt_status_t btif_gatt_init( const btgatt_callbacks_t* callbacks )
    {
      /*bt_gatt_callbacks由参数赋值,该参数是sGattCallbacks。
      sGattCallbacks的定义
      static const btgatt_callbacks_t sGattCallbacks = {
          sizeof(btgatt_callbacks_t),
          &sGattClientCallbacks,
          &sGattServerCallbacks
      };*/
        bt_gatt_callbacks = callbacks;
    
        BTA_GATTC_Init();
        BTA_GATTS_Init();
    
        return BT_STATUS_SUCCESS;
    }

    到此为止,调用语句中bt_gatt_callbacks对象我们已经清楚了,就是sGattCallbacks对象。现在分析client->register_client_cb

    HAL_CBACK(bt_gatt_callbacks, client->register_client_cb
                    , p_data->reg_oper.status      
                    , p_data->reg_oper.client_if   
                    , &app_uuid
                );

    client对象是在btgatt_callbacks_t结构体中定义的一个变量,其初始化是在bt_gatt_callbacks对象(即sGattCallbacks对象)中。

    btgatt_callbacks_t结构体如下:

    typedef struct {
        /** Set to sizeof(btgatt_callbacks_t) */
        size_t size;
    
        /** GATT Client callbacks */   
        const btgatt_client_callbacks_t* client;
    
        /** GATT Server callbacks */   
        const btgatt_server_callbacks_t* server;
    } btgatt_callbacks_t;

    因此client对应的就是sGattCallbacks对象中的sGattClientCallbacks对象。sGattClientCallbacks对象定义如下(在JNI层的com_android_bluetooth_gatt.cpp文件中定义):

    static const btgatt_client_callbacks_t sGattClientCallbacks = {
        btgattc_register_app_cb,
        btgattc_scan_result_cb,
        ......
    };

    而sGattClientCallbacks对象的类型是btgatt_client_callbacks_t结构体,如下

    typedef struct {
        register_client_callback            register_client_cb;            
        scan_result_callback                scan_result_cb;                
        connect_callback                    open_cb;
        disconnect_callback                 close_cb;
        ......        
    } btgatt_client_callbacks_t;

    因此,client->register_client_cb就是调用了sGattClientCallbacks 对象中的btgattc_register_app_cb函数。

    void btgattc_register_app_cb(int status, int clientIf, bt_uuid_t *app_uuid)
    {
        CHECK_CALLBACK_ENV
        sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
            clientIf, UUID_PARAMS(app_uuid));
        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
    }

    JNI层的method_onClientRegistered 函数对应java层的onClientRegistered方法[-->GattService.java]。

        void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb)
                throws RemoteException {       
            UUID uuid = new UUID(uuidMsb, uuidLsb);
            if (DBG) Log.d(TAG, "onClientRegistered() - UUID=" + uuid + ", clientIf=" + clientIf);
            ClientMap.App app = mClientMap.getByUuid(uuid);
            if (app != null) {
                app.id = clientIf;
                app.linkToDeath(new ClientDeathRecipient(clientIf));
                app.callback.onClientRegistered(status, clientIf);
            }
        }

    此callback其实是GattCallbackWrapper类的对象。

    分析mClientMap对象,在registerClient方法中调用了ClientMap的父类ContextMap::add方法,将GattCallbackWrapper类对象wrapper作为callback参数添加到mClientMap对象中。

     

    接下来重新分析:

    onClientRegistered方法[--->BluetoothAdapter::GattCallbackWrapper类]

            public void onClientRegistered(int status, int clientIf) {
                if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status +
                               " clientIf=" + clientIf);
                synchronized(this) {
                    if (mLeHandle == -1) {
                        if (DBG) Log.d(TAG, "onClientRegistered LE scan canceled");
                    }
    
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        mLeHandle = clientIf;
                        IBluetoothGatt iGatt = null;
                        try {
                            BluetoothAdapter adapter = mBluetoothAdapter.get();
                            if (adapter != null) {
                                iGatt = adapter.getBluetoothManager().getBluetoothGatt();
                                //调用startLeScan方法时,传递过来的参数为null,执行此处
                                if (mScanFilter == null) {
                                    iGatt.startScan(mLeHandle, false);
                                } else {
                                    ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
                                    for(int i = 0; i != uuids.length; ++i) {
                                        uuids[i] = new ParcelUuid(mScanFilter[i]);
                                    }
                                    iGatt.startScanWithUuids(mLeHandle, false, uuids);
                                }
                            } else {
                                Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
                                mLeHandle = -1;
                            }
                        } catch (RemoteException e) {
                            Log.e(TAG, "fail to start le scan: " + e);
                            mLeHandle = -1;
                        }
                ......
            }

    接下来分析startScan方法,在GattService.java中。

        void startScan(int appIf, boolean isServer) {
            ......
            if (getScanClient(appIf, isServer) == null) {
                if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf);
                mScanQueue.add(new ScanClient(appIf, isServer));
            }
    
            gattClientScanNative(appIf, true);
        }

    JNI层gattClientScanNative函数

    static void gattClientScanNative(JNIEnv* env, jobject object, jint clientIf, jboolean start)
    {
        if (!sGattIf) return;
        sGattIf->client->scan(clientIf, start);
    }

    同之前分析register_client的步骤,分析的scan函数对应btif_gattc_scan函数。

    static bt_status_t btif_gattc_scan( int client_if, bool start )
    {
        CHECK_BTGATT_INIT();
        btif_gattc_cb_t btif_cb;
        btif_cb.client_if = (uint8_t) client_if;
        return btif_transfer_context(btgattc_handle_event, start ? BTIF_GATTC_SCAN_START : BTIF_GATTC_SCAN_STOP,
                                     (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
    }

    在btgattc_handle_event函数中处理BTIF_GATTC_SCAN_START事件

            case BTIF_GATTC_SCAN_START:
                btif_gattc_init_dev_cb();
                // BTA_DmBleObserve发出消息,包含BTA_DM_API_BLE_OBSERVE_EVT事件
                BTA_DmBleObserve(TRUE, 0, bte_scan_results_cb);
                break;

    调用bte_scan_results_cb函数,

    static void bte_scan_results_cb (tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)
    {
        btif_gattc_cb_t btif_cb;
        uint8_t len;
    
        switch (event)
        {
            case BTA_DM_INQ_RES_EVT:
                ......
    
            case BTA_DM_INQ_CMPL_EVT:
                ......
        btif_transfer_context(btif_gattc_upstreams_evt, BTIF_GATT_OBSERVE_EVT,
                                     (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
    }

    在btif_gattc_upstreams_evt函数中处理BTIF_GATT_OBSERVE_EVT事件。

            case BTIF_GATT_OBSERVE_EVT:
            {
                btif_gattc_cb_t *p_btif_cb = (btif_gattc_cb_t*)p_param;
                if (!btif_gattc_find_bdaddr(p_btif_cb->bd_addr.address))
                {
                    btif_gattc_add_remote_bdaddr(p_btif_cb->bd_addr.address, p_btif_cb->addr_type);
                    btif_gattc_update_properties(p_btif_cb);
                }
                HAL_CBACK(bt_gatt_callbacks, client->scan_result_cb,
                          &p_btif_cb->bd_addr, p_btif_cb->rssi, p_btif_cb->value);
                break;
            }

    同分析register_client_cb函数,在JNI层com_android_bluetooth_gatt.cpp文件中定义,分析得scan_result_cb对应函数btgattc_scan_result_cb。

    void btgattc_scan_result_cb(bt_bdaddr_t* bda, int rssi, uint8_t* adv_data)
    {
        ......
        sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanResult
            , address, rssi, jb);
        ......
    }

    对应java层文件GattService类onScanResult方法。

        void onScanResult(String address, int rssi, byte[] adv_data) {
            for (ScanClient client : mScanQueue) {
                ......
                if (!client.isServer) {
                    ClientMap.App app = mClientMap.getById(client.appIf);
                    if (app != null) {
                        try {
                            app.callback.onScanResult(address, rssi, adv_data);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Exception: " + e);
                            mClientMap.remove(client.appIf);
                            mScanQueue.remove(client);
                        }
                    }
                }
            ......
            }
      }

    callback为GattCallbackWrapper类的对象,因此调用GattCallbackWrapper类中的onScanResult方法。

            public void onScanResult(String address, int rssi, byte[] advData) {
                ......
                try {
                    BluetoothAdapter adapter = mBluetoothAdapter.get();
                    if (adapter == null) {
                        Log.d(TAG, "onScanResult, BluetoothAdapter null");
                        return;
                    }
                    mLeScanCb.onLeScan(adapter.getRemoteDevice(address), rssi, advData);
                } catch (Exception ex) {
                    Log.w(TAG, "Unhandled exception: " + ex);
                }
            }

    mLeScanCb对象为LeScanCallback接口的对象,不过源码中并没有类来实现该接口,故只能分析到这里了。扫描到此结束,over~~ 

    -------------------------------------------------------------------------------------------------------------

    贴出流程图,see see,5个步骤:

    1.startLeScan(JAVA-->JNI)

    -------------------------------------------------------------------------------------------------------------

    2.startLeScan(蓝牙栈)

    -------------------------------------------------------------------------------------------------------------

    3.startLeScan(JNI-->JAVA)

    -------------------------------------------------------------------------------------------------------------

    4.startLeScan(蓝牙栈)

    -------------------------------------------------------------------------------------------------------------

    5.startLeScan(JNI-->JAVA)

    -------------------------------------------------------------------------------------------------------------

  • 相关阅读:
    Treap 树堆 容易实现的平衡树
    (转)Maven实战(二)构建简单Maven项目
    (转)Maven实战(一)安装与配置
    根据请求头跳转判断Android&iOS
    (转)苹果消息推送服务器 php 证书生成
    (转)How to renew your Apple Push Notification Push SSL Certificate
    (转)How to build an Apple Push Notification provider server (tutorial)
    (转)pem, cer, p12 and the pains of iOS Push Notifications encryption
    (转)Apple Push Notification Services in iOS 6 Tutorial: Part 2/2
    (转)Apple Push Notification Services in iOS 6 Tutorial: Part 1/2
  • 原文地址:https://www.cnblogs.com/chenbin7/p/3334082.html
Copyright © 2011-2022 走看看