的startScanning(),我们分析的起点也从这里开始,起步代码如下



1. private void updateContent(int
2. if
3.          preferenceScreen.removePreference(mPairedDevicesCategory);  
4. if (scanState == true) {  
5. false;  
6.              startScanning();  
7. else<SPAN style="FONT-FAMILY: Arial, Helvetica, sans-serif">    ........</SPAN>  
8. }         
9. private void
10. if
11.             getPreferenceScreen().addPreference(mAvailableDevicesCategory);  
12.         }  
13. true);  
14.     }


private void updateContent(int bluetoothState, boolean scanState) {
    if (numberOfPairedDevices == 0) {
         preferenceScreen.removePreference(mPairedDevicesCategory);
         if (scanState == true) {
             mActivityStarted = false;
             startScanning();
        } else    ........
}       
private void startScanning() {
        if (!mAvailableDevicesCategoryIsPresent) {
            getPreferenceScreen().addPreference(mAvailableDevicesCategory);
        }
        mLocalAdapter.startScanning(true);
    }



        其实在这里蓝牙搜索和打开流程是结构上是一致的,利用LocalBluetoothAdapter.java过渡到BluetoothAdapter.java再跳转至AdapterService.java要稍微留意下的是在这个过渡中startScaning()方法变成了startDiscovery()方法,看下代码:packages/apps/Settings/src/com/android/settings/bluetooth/LocalBluetoothAdapter.java

1. void startScanning(boolean
2. if
3. if
4. // Don't scan more than frequently than SCAN_EXPIRATION_MS, 
5. // unless forced 
6. if
7. return;  
8.          }  
9. // If we are playing music, don't scan unless forced. 
10.          A2dpProfile a2dp = mProfileManager.getA2dpProfile();  
11. if (a2dp != null
12. return;  
13.          }  
14.      }  
15. //这里才是我们最关注的,前面限制条件关注一下就行了 
16. if
17.          mLastScan = System.currentTimeMillis();  
18.      }  
19. }  
 
void startScanning(boolean force) {
if (!mAdapter.isDiscovering()) {
     if (!force) {
         // Don't scan more than frequently than SCAN_EXPIRATION_MS,
         // unless forced
         if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
             return;
         }
         // If we are playing music, don't scan unless forced.
         A2dpProfile a2dp = mProfileManager.getA2dpProfile();
         if (a2dp != null && a2dp.isA2dpPlaying()) {
             return;
         }
     }
//这里才是我们最关注的,前面限制条件关注一下就行了
     if (mAdapter.startDiscovery()) {
         mLastScan = System.currentTimeMillis();
     }
}
void startScanning(boolean force) {
if (!mAdapter.isDiscovering()) {
     if (!force) {
         // Don't scan more than frequently than SCAN_EXPIRATION_MS,
         // unless forced
         if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
             return;
         }
         // If we are playing music, don't scan unless forced.
         A2dpProfile a2dp = mProfileManager.getA2dpProfile();
         if (a2dp != null && a2dp.isA2dpPlaying()) {
             return;
         }
     }
//这里才是我们最关注的,前面限制条件关注一下就行了
     if (mAdapter.startDiscovery()) {
         mLastScan = System.currentTimeMillis();
     }
}

       BluetoothAdapter.java的那一段,路径 /frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java


1. public boolean
2.     .............................  
3.     AdapterService service = getService();  
4. if (service == null) return false;  
5. return
6. }


public boolean startDiscovery() {
      .............................
      AdapterService service = getService();
      if (service == null) return false;
      return service.startDiscovery();
  }

      这个service代码写得很明白AdapterService,转了一圈从framework又回到packages了,

      下面的代码路径自然是 :packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java,


1. boolean
2.     enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,  
3. "Need BLUETOOTH ADMIN permission");  
4.   
5. return
6. }


boolean startDiscovery() {
      enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
              "Need BLUETOOTH ADMIN permission");

      return startDiscoveryNative();
  }

      和打开蓝牙根本就是一个套路,上面的流程略过一小步,很简单的不写了,下面要怎么走,估计大家也都猜到了,JNI应该出场了,

       路径:/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp



1. static
2. "%s:",__FUNCTION__);                                  
3.                                                                 
4.     jboolean result = JNI_FALSE;                                
5. if (!sBluetoothInterface) return
6.                                                                 
7. int
8.     result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;  
9. return
10. }


static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {                                                   
      ALOGV("%s:",__FUNCTION__);                                
                                                                
      jboolean result = JNI_FALSE;                              
      if (!sBluetoothInterface) return result;                  
                                                                
      int ret = sBluetoothInterface->start_discovery();         
      result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
      return result;                                            
  }

      在下面要去哪?稍微要动下脑筋,不过我们在上一篇android -- 蓝牙 bluetooth (二) 打开蓝牙已经说过怎么找了,注意android.mk文件,先找头文件,再找对应的实现C文件代码。就是现在回顾下,蓝牙打开和搜索的代码流程我们都看了,跳转都是一个套路,settings界面发起,LocalBluetoothAdapter.java过渡,去framework的转转(BluetoothAdapter.java)后回到packages的AdapterService.java,再走JNI来的external。流程就是这样的,相信类似的功能跳转(比如蓝牙配对,关闭蓝牙,停止扫描这些)大家都应该熟悉了,后面再有类似的功能就写函数名一笔带过了,还有这里要注意的就是这个start_discovery()实现代码的寻找,留意mk文件就是了,不复杂。小结结束,继续看代码    路径:/external/bluetooth/bluedroid/btif/src/bluetooth.c



1. static int start_discovery(void)  
2. {  
3. /* sanity check */
4. if
5. return
6.   
7. return
8. }


static int start_discovery(void)
{
    /* sanity check */
    if (interface_ready() == FALSE)
        return BT_STATUS_NOT_READY;

    return btif_dm_start_discovery();
}

        下面代码直接跳转就可以找到,路径external/bluetooth/bluedroid/btif/src/btif_dm.c  

        这个代码有点多,不过里面的信息也很多,所以连注释也一起保留的贴出来了,蓝牙的搜索实现并没有像蓝牙打开那样交由vendor厂商实现,在这里已经写出来了,仔细看下那些#if和#else,都是一些查询条件的调置,#if (BLE_INCLUDED == TRUE)   这个应该就google为蓝牙4.0 LE作的准备了,也算是今年google I/O大会上宣布即将支持蓝牙4.0低能耗版一个佐证吧,对于代码里面那些字符串的含义看这里好了external/bluetooth/bluedroid/bta/include/bta_api.h,一个头文件,大部分字符串和结构体的定义都在这了,多少还有些注释。


1. bt_status_t btif_dm_start_discovery(void)                                    
2. {                                                                            
3.     tBTA_DM_INQ inq_params;                                                  
4.     tBTA_SERVICE_MASK services = 0;                                          
5.                                                                              
6. "%s", __FUNCTION__);                                   
7. /* TODO: Do we need to handle multiple inquiries at the same time? */
8.                                                                              
9. /* Set inquiry params and call API */
10. #if (BLE_INCLUDED == TRUE)                                                  
11.     inq_params.mode = BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;        
12. #else                                                                       
13.     inq_params.mode = BTA_DM_GENERAL_INQUIRY;                                
14. #endif                                                                      
15.     inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;                  
16.                                                                              
17.     inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;                  
18.     inq_params.report_dup = TRUE;                                            
19.                                                                              
20.     inq_params.filter_type = BTA_DM_INQ_CLR;                                 
21. /* TODO: Filter device by BDA needs to be implemented here */
22.                                                                              
23. /* Will be enabled to TRUE once inquiry busy level has been received */
24.     btif_dm_inquiry_in_progress = FALSE;                                     
25. /* find nearby devices */
26.     BTA_DmSearch(&inq_params, services, bte_search_devices_evt);             
27.                                                                              
28. return
29. }


bt_status_t btif_dm_start_discovery(void)                                  
{                                                                          
    tBTA_DM_INQ inq_params;                                                
    tBTA_SERVICE_MASK services = 0;                                        
                                                                           
    BTIF_TRACE_EVENT1("%s", __FUNCTION__);                                 
    /* TODO: Do we need to handle multiple inquiries at the same time? */  
                                                                           
    /* Set inquiry params and call API */                                  
#if (BLE_INCLUDED == TRUE)                                                 
    inq_params.mode = BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;      
#else                                                                      
    inq_params.mode = BTA_DM_GENERAL_INQUIRY;                              
#endif                                                                     
    inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;                
                                                                           
    inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;                
    inq_params.report_dup = TRUE;                                          
                                                                           
    inq_params.filter_type = BTA_DM_INQ_CLR;                               
    /* TODO: Filter device by BDA needs to be implemented here */          
                                                                           
    /* Will be enabled to TRUE once inquiry busy level has been received */
    btif_dm_inquiry_in_progress = FALSE;                                   
    /* find nearby devices */                                              
    BTA_DmSearch(&inq_params, services, bte_search_devices_evt);           
                                                                           
    return BT_STATUS_SUCCESS;                                              
}



      BTA_DmSearch()方法是看起来是要搜索了,不过里面这个家伙bte_search_devices_evt才是真正干活的主力,所以我们先看它,在这个函数里


1. static void
2.        UINT16 param_len = 0;                                                                                           
3.                                                                                                                         
4. if
5. sizeof(tBTA_DM_SEARCH);                                                                         
6. /* Allocate buffer to hold the pointers (deep copy). The pointers will point to the end of the tBTA_DM_SEARCH */
7. switch
8.        {                                                                                                                
9. case
10.            {                                                                                                            
11. if
12.                    param_len += HCI_EXT_INQ_RESPONSE_LEN;                                                               
13.            }                                                                                                            
14. break;                                                                                                              
15.           ..............................                                                                                                
16.        }                                                                                                                
17. "%s event=%s param_len=%d", __FUNCTION__, dump_dm_search_event(event), param_len);             
18.                                                                                                                         
19. /* if remote name is available in EIR, set teh flag so that stack doesnt trigger RNR */
20. if
21.            p_data->inq_res.remt_name_not_required = check_eir_remote_name(p_data, NULL, NULL);                          
22.                                                                                                                         
23. void
24. sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);                                       
25.    }


static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)                                                                      {                                                                                                                  
         UINT16 param_len = 0;                                                                                         
                                                                                                                        
         if (p_data)                                                                                                    
             param_len += sizeof(tBTA_DM_SEARCH);                                                                       
         /* Allocate buffer to hold the pointers (deep copy). The pointers will point to the end of the tBTA_DM_SEARCH */
         switch (event)                                                                                                 
         {                                                                                                              
             case BTA_DM_INQ_RES_EVT:                                                                                   
             {                                                                                                          
                 if (p_data->inq_res.p_eir)                                                                             
                     param_len += HCI_EXT_INQ_RESPONSE_LEN;                                                             
             }                                                                                                          
             break;                                                                                                            
            ..............................                                                                                              
         }                                                                                                              
         BTIF_TRACE_DEBUG3("%s event=%s param_len=%d", __FUNCTION__, dump_dm_search_event(event), param_len);           
                                                                                                                        
         /* if remote name is available in EIR, set teh flag so that stack doesnt trigger RNR */                        
         if (event == BTA_DM_INQ_RES_EVT)                                                                               
             p_data->inq_res.remt_name_not_required = check_eir_remote_name(p_data, NULL, NULL);                        
                                                                                                                        
         btif_transfer_context (btif_dm_search_devices_evt , (UINT16) event, (void *)p_data, param_len,                 
             (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);                                     
     }




    1. static void btif_dm_search_devices_evt (UINT16 event, char
    2.   
    3.    tBTA_DM_SEARCH *p_search_data;  
    4. "%s event=%s", __FUNCTION__, dump_dm_search_event(event));  
    5.   
    6. switch
    7.    {  
    8. case
    9.        {  
    10.            p_search_data = (tBTA_DM_SEARCH *)p_param;  
    11. /* Remote name update */
    12. if (strlen((const char
    13.            {  
    14.                bt_property_t properties[1];  
    15.                bt_bdaddr_t bdaddr;  
    16.                bt_status_t status;  
    17.   
    18.                properties[0].type = BT_PROPERTY_BDNAME;  
    19.                properties[0].val = p_search_data->disc_res.bd_name;  
    20. char
    21.                bdcpy(bdaddr.address, p_search_data->disc_res.bd_addr);  
    22.   
    23.                status = btif_storage_set_remote_device_property(&bdaddr, &properties[0]);  
    24. "failed to save remote device property", status);  
    25.                HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,  
    26.                                 status, &bdaddr, 1, properties);  
    27.            }  
    28. /* TODO: Services? */
    29.        }  
    30. break;



    static void btif_dm_search_devices_evt (UINT16 event, char *p_param)
    {
        tBTA_DM_SEARCH *p_search_data;
        BTIF_TRACE_EVENT2("%s event=%s", __FUNCTION__, dump_dm_search_event(event));
    
        switch (event)
        {
            case BTA_DM_DISC_RES_EVT:
            {
                p_search_data = (tBTA_DM_SEARCH *)p_param;
                /* Remote name update */
                if (strlen((const char *) p_search_data->disc_res.bd_name))
                {
                    bt_property_t properties[1];
                    bt_bdaddr_t bdaddr;
                    bt_status_t status;
    
                    properties[0].type = BT_PROPERTY_BDNAME;
                    properties[0].val = p_search_data->disc_res.bd_name;
                    properties[0].len = strlen((char *)p_search_data->disc_res.bd_name);
                    bdcpy(bdaddr.address, p_search_data->disc_res.bd_addr);
    
                    status = btif_storage_set_remote_device_property(&bdaddr, &properties[0]);
                    ASSERTC(status == BT_STATUS_SUCCESS, "failed to save remote device property", status);
                    HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,
                                     status, &bdaddr, 1, properties);
                }
                /* TODO: Services? */
            }
            break;


    05-30 13:52:14.890  1578  2612 D bt-btif : bte_search_devices_evt event=BTA_DM_INQ_RES_EVT param_len=524
    05-30 13:52:14.890  1578  2612 D bt-btif : search_devices_copy_cb: event=BTA_DM_INQ_RES_EVT
    05-30 13:52:14.890  1578  2584 I bt-btif : btif_dm_search_devices_evt event=BTA_DM_INQ_RES_EVT
    05-30 13:52:14.890  1578  2584 D bt-btif : btif_dm_search_devices_evt() ec:89:f5:ba:fb:03 device_type = 0x1



            当然回过头我们还要看下那个BTA_DmSearch(),看它的实现,更应该是起消息发送的作用,代码在/external/bluetooth/bluedroid/bta/dm/bta_dm_api.c,这个函数具体流程并没有看多少,当工具方法看了,有时间看看还是没坏处的。



    1. void
    2. {  tBTA_DM_API_SEARCH    *p_msg;  
    3. if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) != NULL)  
    4.     {  
    5. sizeof(tBTA_DM_API_SEARCH));  
    6.   
    7.         p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;  
    8. sizeof(tBTA_DM_INQ));  
    9.         p_msg->services = services;  
    10.         p_msg->p_cback = p_cback;  
    11.         p_msg->rs_res  = BTA_DM_RS_NONE;  
    12.         bta_sys_sendmsg(p_msg);  
    13.     }  
    14. }


    void BTA_DmSearch(tBTA_DM_INQ *p_dm_inq, tBTA_SERVICE_MASK services, tBTA_DM_SEARCH_CBACK *p_cback)
    {  tBTA_DM_API_SEARCH    *p_msg;
        if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) != NULL)
        {
            memset(p_msg, 0, sizeof(tBTA_DM_API_SEARCH));
    
            p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;
            memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));
            p_msg->services = services;
            p_msg->p_cback = p_cback;
            p_msg->rs_res  = BTA_DM_RS_NONE;
            bta_sys_sendmsg(p_msg);
        }
    }



    1. method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");


    method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");

    deviceFoundCallback方法最后会来java层的/packages/apps/Bluetooth/src/com/android/bluetooth/btservice/RemoteDevices.java

    1. void deviceFoundCallback(byte[] address) {  
    2. // The device properties are already registered - we can send the intent 
    3. // now 
    4.        BluetoothDevice device = getDevice(address);  
    5. "deviceFoundCallback: Remote Address is:"
    6.        DeviceProperties deviceProp = getDeviceProperties(device);  
    7. if (deviceProp == null) {  
    8. "Device Properties is null for Device:"
    9. return;  
    10.        }  
    11.   
    12. new
    13.        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);  
    14.        intent.putExtra(BluetoothDevice.EXTRA_CLASS,  
    15. new
    16.        intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);  
    17.        intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);  
    18.   
    19.        mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);  
    20.    }  
     
     
    void deviceFoundCallback(byte[] address) {
            // The device properties are already registered - we can send the intent
            // now
            BluetoothDevice device = getDevice(address);
            debugLog("deviceFoundCallback: Remote Address is:" + device);
            DeviceProperties deviceProp = getDeviceProperties(device);
            if (deviceProp == null) {
                errorLog("Device Properties is null for Device:" + device);
                return;
            }
    
            Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
            intent.putExtra(BluetoothDevice.EXTRA_CLASS,
                    new BluetoothClass(Integer.valueOf(deviceProp.mBluetoothClass)));
            intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
            intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
    
            mAdapterService.sendBroadcast(intent, mAdapterService.BLUETOOTH_PERM);
        }


    1. addHandler(BluetoothDevice.ACTION_FOUND, new
    2. private final BroadcastReceiver mBroadcastReceiver = new
    3. @Override
    4. public void
    5.         String action = intent.getAction();  
    6.         BluetoothDevice device = intent  
    7.                 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);  
    8.   
    9.         Handler handler = mHandlerMap.get(action);  
    10. if (handler != null) {  
    11.             handler.onReceive(context, intent, device);  
    12.         }  
    13.     }  
    14. };


    addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
            private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                BluetoothDevice device = intent
                        .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    
                Handler handler = mHandlerMap.get(action);
                if (handler != null) {
                    handler.onReceive(context, intent, device);
                }
            }
        };


    1. private class DeviceFoundHandler implements
    2. public void
    3.             BluetoothDevice device) {  
    4.        ........................  
    5. // TODO Pick up UUID. They should be available for 2.1 devices. 
    6. // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. 
    7.         CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);  
    8. if (cachedDevice == null) {  
    9.             cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);  
    10. "DeviceFoundHandler created new CachedBluetoothDevice: "
    11.                     + cachedDevice);  
    12. // callback to UI to create Preference for new device 
    13.             dispatchDeviceAdded(cachedDevice);  
    14.         }  
    15.       ......................  
    16.     }  
    17. }


    private class DeviceFoundHandler implements Handler {
         public void onReceive(Context context, Intent intent,
                 BluetoothDevice device) {
            ........................
             // TODO Pick up UUID. They should be available for 2.1 devices.
             // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
             CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
             if (cachedDevice == null) {
                 cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
                 Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
                         + cachedDevice);
                 // callback to UI to create Preference for new device
                 dispatchDeviceAdded(cachedDevice);
             }
           ......................
         }
     }



      1. public void
      2. if (mDevicePreferenceMap.get(cachedDevice) != null) {                          
      3. return;                                                                    
      4.    }                                                                              
      5.                                                                                   
      6. // Prevent updates while the list shows one of the state messages             
      7. if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;    
      8.                                                                                   
      9. if
      10.        createDevicePreference(cachedDevice);                                      
      11.    }                                                                              
      12. }



      public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {                 
          if (mDevicePreferenceMap.get(cachedDevice) != null) {                        
              return;                                                                  
          }                                                                            
                                                                                       
          // Prevent updates while the list shows one of the state messages            
          if (mLocalAdapter.getBluetoothState() != BluetoothAdapter.STATE_ON) return;  
                                                                                       
          if (mFilter.matches(cachedDevice.getDevice())) {                             
              createDevicePreference(cachedDevice);                                    
          }                                                                            
       }


      1. void
      2. new
      3.             getActivity(), cachedDevice);  
      4.   
      5.     initDevicePreference(preference);  
      6.     mDeviceListGroup.addPreference(preference);  
      7.     mDevicePreferenceMap.put(cachedDevice, preference);  
      8. }

      void createDevicePreference(CachedBluetoothDevice cachedDevice) {
              BluetoothDevicePreference preference = new BluetoothDevicePreference(
                      getActivity(), cachedDevice);
      
              initDevicePreference(preference);
              mDeviceListGroup.addPreference(preference);
              mDevicePreferenceMap.put(cachedDevice, preference);
          }


              到目前为止,包括前面的打开流程分析,还仅是针对代码流程做的分析,对于蓝牙协议方面东西还没有涉及,比如蓝牙是如何发现其它蓝牙设备,这个流程究竟是怎么工作还不是很清楚,后续会尽量关注这些问题,估计看起来就没那么容易,欢迎有经验的朋友指点一二,当然对于本文不足,欢迎拍砖讨论。分享是快乐的,谢谢!