zoukankan      html  css  js  c++  java
  • Android bluetooth介绍(三): 蓝牙扫描(scan)设备分析

    关键词:蓝牙blueZ  A2DP、SINK、sink_connect、sink_disconnect、sink_suspend、sink_resume、sink_is_connected、sink_get_properties、AUDIO、DBUS
    版本号:基于android4.2之前版本号 bluez
    内核:linux/linux3.08
    系统:android/android4.1.3.4
    作者:xubin341719(欢迎转载。请注明作者。请尊重版权谢谢)
    欢迎指正错误。共同学习、共同进步!!


    參考站点:
    http://blog.csdn.net/u011960402/article/details/17216563
    http://www.cnblogs.com/fityme/archive/2013/04/13/3019471.html socket相关
    http://hi.baidu.com/wwwkljoel/item/a35e5745d14e02e6bcf45170 setsockopt

    Android bluetooth介绍(一):基本概念及硬件接口
    Android bluetooth介绍(二): android 蓝牙代码架构及其uart 到rfcomm流程
    Android bluetooth介绍(三): 蓝牙扫描(scan)设备分析
    Android bluetooth介绍(四): a2dp connect流程分析


    一、蓝牙扫描经常使用的方法:
             蓝牙扫描的能够通过两种途径实现:命令行hciitool扫描;Android界面触发,通过JNI、DUBS下发命令。


    1、  命令行hciitool扫描(这部分通过Linux命令操作,跟android没有关系)
    通过bluez的tool发送扫描命令,如:hcitoool scan
    adb shell 下#hcitool  scan扫描结果

    Hcitool扫描逻辑例如以下所看到的:

    2、Android界面触发,通过JNI、DUBS下发命令:通过android界面点击搜索设备

    应用扫描触发逻辑流程:自上而下三种颜色,分别代表应用部分、JNI部分、linux blueZ部分。


    二、Hcitool触发逻辑分析
    1、hcitool这部分代码比較简单,实现函数
    idh.codeexternalluetoothluez oolshcitool.c代码大致流程例如以下:

    通过所带的參数,找到cmd_scan,进入hci_inquriy。这个函数中创建一个BTPROTO_HCI的socket,通过ioctlHCINQUIRY向内核读取数据,保存返回信息。

    2、内核层逻辑:
    当然IOCTL仅仅是当中一项。
    idh.codekernel etluetooth hci_sock.c

    static const struct proto_ops hci_sock_ops = {
    …………
    	.ioctl		= hci_sock_ioctl,
    	.poll		= datagram_poll,
    	.listen		= sock_no_listen,
    …………
    };
    


    它的流程就是构造查询命令,放入命令队列,调度队列来发送命令,当中hci_send_frame后面会解说,这里关键是命令的发送和数据的收集是分开的。所以它在里面会放弃2s的调度。以此来等待数据的收集。收集的数据放在hdev->inq_cache里面。我们来看看这个数据是怎样取得的。例如以下图所看到的:

    入口点hci_rx_work前面已经具体分析过了,这里就不说了。它里面会依据不同的事件类型做不同的处理。通常情况下,扫描都是带信号强度的扫描。所以走的hci_inquiry_result_with_rssi_evt路线,还有其他几种扫描方式,比方:HCI_EV_INQUIRY_RESULT,HCI_EV_EXTENDED_INQUIRY_RESULT等。处理逻辑都几乎相同的,里面会hci_inquiry_cache_update来把结果放到hdev->discovery链表里面去。供后面的查询;比方前面调用的inquiry_cache_dump函数就能够从这个链表里面把数据取出来。然后copy到用户层;
    三、Android界面触发,通过JNI、DUBS下发命令
    总体流程例如以下所看到的:

    (一)、应用部分:
    1、 idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

      @Override
        public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
            Preference preference) {
         	…………
            mLocalAdapter.startScanning(true);
            return true;
          }
    

    2、 idh.codepackagesappsSettingssrccomandroidsettingsluetoothLocalBluetoothAdapter.java

     private final BluetoothAdapter mAdapter;
     void startScanning(boolean force) {
            // Only start if we're not already scanning
            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();
                }
            }
    } 
    

    3、idh.codeframeworksasecorejavaandroidluetoothBluetoothAdapter.java

        public boolean startDiscovery() {
            if (getState() != STATE_ON) return false;
            try {
                return mService.startDiscovery();
            } catch (RemoteException e) {Log.e(TAG, "", e);}
            return false;
        }

    4、JNI函数的调用idh.codeframeworksasecorejavaandroidserverBluetoothService.java

    private native boolean startDiscoveryNative();//Native函数声明
    public class BluetoothService extends IBluetooth.Stub {
        private static final String TAG = "BluetoothService";
        private static final boolean DBG = true;
    …………
    public synchronized boolean startDiscovery() {
    mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
            "Need BLUETOOTH_ADMIN permission");
            if (!isEnabledInternal()) return false;
            return startDiscoveryNative();
    }
    ………………
    }

    (二)、JNI部分:
    1、android_server_BluetoothService.cpp中JNI函数的对比表
    idh.codeframeworksasecorejniandroid_server_BluetoothService.cpp

    static JNINativeMethod sMethods[] = {
         /* name, signature, funcPtr */
       ………………
        {"startDiscoveryNative", "()Z", (void*)startDiscoveryNative},
    {"stopDiscoveryNative", "()Z", (void *)stopDiscoveryNative}, 
    …………
    }

    2、相应Native函数的实现
    这里面有个知识点DBUS,这个后面我们单独去解说
    idh.codeframeworksasecorejniandroid_server_BluetoothService.cpp

    #define BLUEZ_DBUS_BASE_IFC       "org.bluez"
    #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter"//事实上DBUS_ADAPTER_IFACE 也就是 org.bluez.Adapter
    static jboolean startDiscoveryNative(JNIEnv *env, jobject object) {
    
    ………………
        /* Compose the command */
        msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
                                           get_adapter_path(env, object),
                                           DBUS_ADAPTER_IFACE, "StartDiscovery");
    …………
    }
    Native函数startDiscoveryNative和字符串StartDiscovery相应。

    (三)、DBUS部分

    1、DBUS相应方法的实现,这里跟JNI部分比較相似,也是用了函数结构体相应关系。
    idh.codeexternalluetoothluezsrcadapter.c

    #define ADAPTER_INTERFACE	"org.bluez.Adapter"
    static GDBusMethodTable adapter_methods[] = {
    ………………
    	{ "ReleaseSession",	"",	"",	release_session		},
    	{ "StartDiscovery",	"",	"",	adapter_start_discovery },
    	{ "StopDiscovery",	"",	"",	adapter_stop_discovery,
    						G_DBUS_METHOD_FLAG_ASYNC},
    ………………
    }
    

    字符StartDiscovery又相应C中的实现函数adapter_start_discovery。
    2、adapter_start_discovery的实现
    idh.codeexternalluetoothluezsrcadapter.c

    static DBusMessage *adapter_start_discovery(DBusConnection *conn,
    						DBusMessage *msg, void *data)
    {
    …………
    	err = start_discovery(adapter);
    	if (err < 0 && err != -EINPROGRESS)
    		return btd_error_failed(msg, strerror(-err));
    
    done:
    	req = create_session(adapter, conn, msg, 0,
    				session_owner_exit);
    
    	adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);
    
    	return dbus_message_new_method_return(msg);
    }
    

    3、 start_discovery调用
    idh.codeexternalluetoothluezsrcadapter.c

    const struct btd_adapter_ops *adapter_ops = NULL;
    static int start_discovery(struct btd_adapter *adapter)
    {
    …………
    	pending_remote_name_cancel(adapter);
    	return adapter_ops->start_discovery(adapter->dev_id);
    }
    

    adapter_ops相应结构体btd_adapter_ops中相应函数,例如以下:上面部分就相应到btd_adapter_ops中的hci_ops结构体。


    4、btd_adapter_ops中的hci_ops结构体
    idh.codeexternalluetoothluezpluginshciops.c

    static struct btd_adapter_ops hci_ops = {
    …………
    	.set_powered = hciops_set_powered,
    	.set_discoverable = hciops_set_discoverable,
    	.set_pairable = hciops_set_pairable,
    	.set_limited_discoverable = hciops_set_limited_discoverable,
    	.start_discovery = hciops_start_discovery,
    	.stop_discovery = hciops_stop_discovery,
    	………………
    	.create_bonding = hciops_create_bonding,
    	.cancel_bonding = hciops_cancel_bonding,
    	.read_local_oob_data = hciops_read_local_oob_data,
    	.add_remote_oob_data = hciops_add_remote_oob_data,
    	.remove_remote_oob_data = hciops_remove_remote_oob_data,
    	.set_link_timeout = hciops_set_link_timeout,
    	.retry_authentication = hciops_retry_authentication,
    };

    5、hciops_start_discovery函数的实现
    idh.codeexternalluetoothluezpluginshciops.c

    static int hciops_start_discovery(int index)
    {
    	int adapter_type = get_adapter_type(index);
    
    	switch (adapter_type) {
    	case BR_EDR_LE:
    		return hciops_start_inquiry(index, LENGTH_BR_LE_INQ);
    	case BR_EDR: //蓝牙芯片为2.1+EDR的
    		return hciops_start_inquiry(index, LENGTH_BR_INQ);
    	case LE_ONLY:
    		return hciops_start_scanning(index, TIMEOUT_LE_SCAN);
    	default:
    		return -EINVAL;
    	}
    }

    6、hciops_start_inquiry
    idh.codeexternalluetoothluezpluginshciops.c

    static int hciops_start_inquiry(int index, uint8_t length)
    {
    	struct dev_info *dev = &devs[index];
    	uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
    	inquiry_cp inq_cp;
    
    	DBG("hci%d length %u", index, length);
    
    	memset(&inq_cp, 0, sizeof(inq_cp));
    	memcpy(&inq_cp.lap, lap, 3);
    	inq_cp.length = length;
    	inq_cp.num_rsp = 0x00;
    
    	if (hci_send_cmd(dev->sk, OGF_LINK_CTL,
    			OCF_INQUIRY, INQUIRY_CP_SIZE, &inq_cp) < 0)
    		return -errno;
    
    	return 0;
    }
    

    7、idh.codeexternalluetoothluezlibhci.c

    /* HCI functions that require open device
     * dd - Device descriptor returned by hci_open_dev. */
    dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
    int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)
    {
    ………………
    	if (plen) {
    		iv[2].iov_base = param;
    		iv[2].iov_len  = plen;
    		ivn = 3;
    	}
    
    	while (writev(dd, iv, ivn) < 0) {//writev这里把数据写到socket里面。
    		if (errno == EAGAIN || errno == EINTR)
    			continue;
    		return -1;
    	}
    	return 0;
    }

    (四)、内核部分:
    1、HCI FILTER的设置
    HCIsocket的类型为BTPROTO_HCI。上层调用setsockopt的时候。触发了内核的hci_sock_setsockopt函数的运行。在这里面设置了socket的filter特性,包含包类型,包含事件类型

    当上层调用setsockopt(sock, SOL_HCI, HCI_FILTER,&flt, sizeof(flt))时。触发相应的内核路径。
    idh.codekernel etluetoothhci_sock.c

    static const struct proto_ops hci_sock_ops = {
    	.family		= PF_BLUETOOTH,
    	.owner		= THIS_MODULE,
    …………
    	.shutdown	= sock_no_shutdown,
    	.setsockopt	= hci_sock_setsockopt,
    	.getsockopt	= hci_sock_getsockopt,
    	.connect	= sock_no_connect,
    …………
    };

    idh.codekernel etluetoothhci_sock.c

    static int hci_sock_setsockopt(struct socket *sock, int level, int optname, char __user *optval, unsigned int len)
    {
    ………………
    	case HCI_FILTER:
    		{
    			struct hci_filter *f = &hci_pi(sk)->filter;
    
    			uf.type_mask = f->type_mask;
    			uf.opcode    = f->opcode;
    			uf.event_mask[0] = *((u32 *) f->event_mask + 0);
    			uf.event_mask[1] = *((u32 *) f->event_mask + 1);
    		}
    	………………
    }
    

    内核这部分就比較统一的数据,通过hci_send_cmd把命令发出去,HCI_FILTER这个地方的处理还没理解。后面补充


    Writev函数通过socket把数据写下去,经过VFS层,调用到内核空间的sendmsg函数。

    (五)、EVENT返回状态

    Controller收到查询命令后。返回一个命令状态
    1、cmd_status
    idh.codeexternalluetoothluezpluginshciops.c

    	switch (eh->evt) {
    	case EVT_CMD_STATUS:
    		cmd_status(index, ptr);
    		break;
    static inline void cmd_status(int index, void *ptr)
    {
    	evt_cmd_status *evt = ptr;
    	uint16_t opcode = btohs(evt->opcode);
    
    	if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY))//假设是inquriy做特殊处理;
    		cs_inquiry_evt(index, evt->status);
    }

    2、cs_inquiry_evt的实现 idh.codeexternalluetoothluezpluginshciops.c

    static inline void cs_inquiry_evt(int index, uint8_t status)
    {
    	if (status) {//错误信息
    		error("Inquiry Failed with status 0x%02x", status);
    		return;
    	}
    
    	set_state(index, DISCOV_INQ);//设置状态为INQ,向上层回复discoverying的property change
    }
    

    3、设置不同的DISCOV 状态 idh.codeexternalluetoothluezpluginshciops.c

    static void set_state(int index, int state)
    {
    	………………
    	switch (dev->discov_state) {
    	case DISCOV_HALTED://停止发现;
    		if (adapter_get_state(adapter) == STATE_SUSPENDED)
    			return;
    
    		if (is_resolvname_enabled() &&
    					adapter_has_discov_sessions(adapter))
    			adapter_set_state(adapter, STATE_RESOLVNAME);
    		else
    			adapter_set_state(adapter, STATE_IDLE);
    		break;
    	case DISCOV_INQ:
    	case DISCOV_SCAN://扫描发现;
    		adapter_set_state(adapter, STATE_DISCOV);
    		break;
    	}
    }
    

    4、设置adapter的状态 idh.codeexternalluetoothluezsrcadapter.c

    idh.codeexternalluetoothluezsrcadapter.c
    #define ADAPTER_INTERFACE	"org.bluez.Adapter"
    void adapter_set_state(struct btd_adapter *adapter, int state)
    {
    …………
    	case STATE_DISCOV:
    		discov_active = TRUE;
    //向上层回复discovering的property change
    		emit_property_changed(connection, path,
    					ADAPTER_INTERFACE, "Discovering",
    					DBUS_TYPE_BOOLEAN, &discov_active);
    		break;
    …………
    }
    

    emit_property_changed发送PropertyChanged的消息,消息内容为Discovering。通知上层BluetoothEventLoop进行Discovering。


    5、emit_property_changed发送Discovering消息的实现 idh.codeexternalluetoothluezsrcdbus-common.c
    这部分涉及到DBUS内容

    dbus_bool_t emit_property_changed(DBusConnection *conn,
    					const char *path,
    					const char *interface,
    					const char *name,
    					int type, void *value)
    {
    	DBusMessage *signal;
    	DBusMessageIter iter;
    	signal = dbus_message_new_signal(path, interface, "PropertyChanged"); // 创建消息对象并标识路径 
    	if (!signal) {
    		error("Unable to allocate new %s.PropertyChanged signal",
    				interface);
    		return FALSE;
    	}
    	dbus_message_iter_init_append(signal, &iter);//把信号相相应的參数压进去
    	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name);//申请一个首地址。把前面压入的參数传入这个首地址	
    append_variant(&iter, type, value);//
    	return g_dbus_send_message(conn, signal);//启动发送调用,并释放发送相关消息信息
    }
    

    6、DBUS消息接收的实现 idh.codeframeworksasecorejniandroid_server_BluetoothEventLoop.cpp

    // Called by dbus during WaitForAndDispatchEventNative()
    static DBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg,
                                          void *data) {
    …………
    else if (dbus_message_is_signal(msg, "org.bluez.Adapter", "PropertyChanged")) {
            jobjectArray str_array = parse_adapter_property_change(env, msg);//(1)、对收到消息的解析
            if (str_array != NULL) {
                /* Check if bluetoothd has (re)started, if so update the path. */
                jstring property =(jstring) env->GetObjectArrayElement(str_array, 0);
                const char *c_property = env->GetStringUTFChars(property, NULL);
                if (!strncmp(c_property, "Powered", strlen("Powered"))) {
                    jstring value =
                        (jstring) env->GetObjectArrayElement(str_array, 1);
                    const char *c_value = env->GetStringUTFChars(value, NULL);
                    if (!strncmp(c_value, "true", strlen("true")))
                        nat->adapter = get_adapter_path(nat->conn);
                    env->ReleaseStringUTFChars(value, c_value);
                }
                env->ReleaseStringUTFChars(property, c_property);
    
                env->CallVoidMethod(nat->me,
                                  method_onPropertyChanged,//(2)、
    method_onPropertyChanged NATVIE函数的实现
                                  str_array);
            } else LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
            goto success;
    }
    

    (1)、对收到消息的解析 idh.codeframeworksasecorejniandroid_bluetooth_common.cpp 

    jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg) {
        return parse_property_change(env, msg, (Properties *) &adapter_properties,
                        sizeof(adapter_properties) / sizeof(Properties));
    }
    

    针对org.bluez.Adapter不同的消息类型
    idh.codeframeworksasecorejniandroid_bluetooth_common.cpp

    static Properties adapter_properties[] = {
        {"Address", DBUS_TYPE_STRING},
        {"Name", DBUS_TYPE_STRING},
        {"Class", DBUS_TYPE_UINT32},
        {"Powered", DBUS_TYPE_BOOLEAN},
        {"Discoverable", DBUS_TYPE_BOOLEAN},
        {"DiscoverableTimeout", DBUS_TYPE_UINT32},
        {"Pairable", DBUS_TYPE_BOOLEAN},
        {"PairableTimeout", DBUS_TYPE_UINT32},
        {"Discovering", DBUS_TYPE_BOOLEAN},
        {"Devices", DBUS_TYPE_ARRAY},
        {"UUIDs", DBUS_TYPE_ARRAY},
    };
    

    (2)、method_onPropertyChanged NATVIE函数的实现 idh.codeframeworksasecorejniandroid_server_BluetoothEventLoop.cpp

    static void classInitNative(JNIEnv* env, jclass clazz) {
        ALOGV("%s", __FUNCTION__);
    #ifdef HAVE_BLUETOOTH
        method_onPropertyChanged = env->GetMethodID(clazz, "onPropertyChanged",
                                                    "([Ljava/lang/String;)V");
    method_onDevicePropertyChanged = env->GetMethodID(clazz,
     "onDevicePropertyChanged","(Ljava/lang/String;[Ljava/lang/String;)V");
    …………
    }
    

    7、JNI调用onPropertyChanged相应JAVA的实现,在BluetoothEventLoop.java
    idh.codeframeworksasecorejavaandroidserverBluetoothEventLoop.java中

       private static native void classInitNative();
    /*package*/ void onPropertyChanged(String[] propValues) {
     ………………
            log("Property Changed: " + propValues[0] + " : " + propValues[1]);
            String name = propValues[0];
            if (name.equals("Name")) {//获取蓝牙名字;
       			…………
            } else if (name.equals("Pairable") || name.equals("Discoverable")) {//配对。
               ………………
            } else if (name.equals("Discovering")) {//扫描查询;
                Intent intent;
                adapterProperties.setProperty(name, propValues[1]);
                if (propValues[1].equals("true")) {
                    intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
                } else {
                    // Stop the discovery.
                    mBluetoothService.cancelDiscovery();
                    intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
                }
                mContext.sendBroadcast(intent, BLUETOOTH_PERM);
            } else if (name.equals("Devices") || name.equals("UUIDs")) {//Devices、UUID的获取;
         	………………
            } else if (name.equals("Powered")) {//蓝牙打开、关闭;
                mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,
                    propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));
            } else if (name.equals("DiscoverableTimeout")) {
                adapterProperties.setProperty(name, propValues[1]);
            }
        } 

    (1)、看到这份log我们或许会更明确其他功能的由来:
    D BluetoothEventLoop: Property Changed: Powered : true
    D BluetoothEventLoop: Property Changed: Pairable : true
    D BluetoothEventLoop: Property Changed: Class : 5898764
    D BluetoothEventLoop: Property Changed: Pairable : true
    D BluetoothEventLoop: Property Changed: Discoverable : false
    D BluetoothEventLoop: Property Changed: Discovering : true
    D BluetoothEventLoop: Property Changed: Discovering : false
    D BluetoothEventLoop: Property Changed: Devices : 1
    D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: Connected value: true
    D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: Paired value: true
    D BluetoothEventLoop: Device property changed: 94:20:53:01:15:90 property: UUIDs value: 4
    

    (2)、以下我们重点分析Discovering这部分
    idh.codeframeworksasecorejavaandroidserverBluetoothEventLoop.java

    else if (name.equals("Discovering")) {
                Intent intent;
                adapterProperties.setProperty(name, propValues[1]);
                if (propValues[1].equals("true")) {//開始扫描
                    intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//
                } else {
                    // Stop the discovery. //停止扫描
                    mBluetoothService.cancelDiscovery();
                    intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
                }
                mContext.sendBroadcast(intent, BLUETOOTH_PERM);
            }
    这样就能够通过broadcast发送ACTION_DISCOVERY_STARTED广播,注冊的receiver来响应了。
    

    8、ACTION_DISCOVERY_STARTEDACTION_DISCOVERY_FINISHED的receiver分析
    从代码中我们能够看到这个action一共同拥有两个receiver,一个是静态注冊的BluetoothDiscoveryReceiver,一个是动态注冊是ScanningStateChangedHandler。
    (1)、BluetoothDiscoveryReceiver:
    这个receiver是在settings中的Androidmanifest中静态注冊的。用途:主要用于获取扫描開始和终止的时间。
    idh.codepackagesappsSettingsAndroidManifest.xml

    <receiver
          android:name=".bluetooth.BluetoothDiscoveryReceiver">
          <intent-filter>
            <action android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
            <action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
            <category android:name="android.intent.category.DEFAULT" />
           </intent-filter>
    </receiver>
    

    1)、ACTION_DISCOVERY_STARTEDACTION_DISCOVERY_FINISHEDAndroidManifest.xml文件的联系
    idh.codeframeworksasecorejavaandroidluetoothBluetoothAdapter.java

    public final class BluetoothAdapter {
        private static final String TAG = "BluetoothAdapter";
    private static final boolean DBG = false;
    …………
        public static final String ACTION_DISCOVERY_STARTED =
                "android.bluetooth.adapter.action.DISCOVERY_STARTED";
        public static final String ACTION_DISCOVERY_FINISHED =
                "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
    …………
    }
    

    2)、BluetoothAdapter,蓝牙适配器。直到我们建立bluetoothSocket连接之前。都要不断操作它。


    BluetoothAdapter中的动作常量

    ACTION_DISCOVERY_FINISHED

    已完毕蓝牙搜索

    ACTION_DISCOVERY_STARTED

    已经開始搜索蓝牙设备

    ACTION_LOCAL_NAME_CHANGED

    更改蓝牙的名字

    ACTION_REQUEST_DISCOVERABLE

    请求能够被搜索

    ACTION_REQUEST_ENABLE

    请求启动蓝牙

    ACTION_SCAN_MODE_CHANGED

    扫描模式已经改变

    ACTION_STATE_CHANGED

    状态已改变

    ACTION_CONNECTION_STATE_CHANGED

    3)、收到广播后函数实现,開始扫描
    Main log中显示的log为DISCOVERY_STARTED
    D BluetoothDiscoveryReceiver: Received:android.bluetooth.adapter.action.DISCOVERY_STARTED
    HCI log 中:

    idh.codepackagesappsSettingssrccomandroidsettingsluetoothBluetoothDiscoveryReceiver.java这个文件里就一个函数。还是比简单

    public final class BluetoothDiscoveryReceiver extends BroadcastReceiver {
        private static final String TAG = "BluetoothDiscoveryReceiver";
        private static final boolean DEBUG = Debug.isDebug();
    
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (DEBUG) Log.d(TAG, "Received: " + action);
    
            if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED) ||
                    action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {    
    //共享时间戳。扫描開始和结束的时间。 
       LocalBluetoothPreferences.persistDiscoveringTimestamp(context);
            }
        }
    }
    

    ScanningStateChangedHandler的注冊及用途,要用于開始扫描。和扫描显示界面的控制。


    这个receiver是在idh.codepackagesappsSettingssrccomandroidsettingsluetoothBluetoothEventManager.java动态注冊的,例如以下:

    BluetoothEventManager(LocalBluetoothAdapter adapter,
                CachedBluetoothDeviceManager deviceManager, Context context) {
    mLocalAdapter = adapter;
    …………
    // Bluetooth on/off broadcasts
     addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
    
    // Discovery broadcastsaddHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
            addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
    …………
    }
    

    (1)、ScanningStateChangedHandler函数实现例如以下:idh.codepackagesappsSettingssrccomandroidsettingsluetoothBluetoothEventManager.java

     private class ScanningStateChangedHandler implements Handler {
            private final boolean mStarted;
    
            ScanningStateChangedHandler(boolean started) {
                mStarted = started;
            }
            public void onReceive(Context context, Intent intent,
                    BluetoothDevice device) {
                synchronized (mCallbacks) {//1)、调用注冊的callback
    中的onScanningStateChanged函数。

    for (BluetoothCallback callback : mCallbacks) { callback.onScanningStateChanged(mStarted); } } //2)、这个函数就是把上次扫描到设备、和之前的设备做相应处理; mDeviceManager.onScanningStateChanged(mStarted); LocalBluetoothPreferences.persistDiscoveringTimestamp(context); } }

    1)、调用注冊的callback中的callback.onScanningStateChanged(mStarted)函数。
    idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

        public void onScanningStateChanged(boolean started) {
            if (started == false) {//《1》、假设扫描结束;
                removeOutOfRangeDevices();
            }
            updateProgressUi(started);// 《2》、UI显示小圆圈扫描;
    }
    

    《1》、假设扫描结束;removeOutOfRangeDevices();
    idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

    private void removeOutOfRangeDevices() {
        Collection<CachedBluetoothDevice> cachedDevices =
                mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
        for (CachedBluetoothDevice cachedDevice : cachedDevices) {
             if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&
                 cachedDevice.isVisible() == false) {
                 BluetoothDevicePreference preference = mDevicePreferenceMap.get(cachedDevice);
                 if (preference != null) {
                     mDeviceListGroup.removePreference(preference);
                 }
                 mDevicePreferenceMap.remove(cachedDevice);
              }
         }
    }
    

    《2》、UI显示小圆圈扫描,updateProgressUi(started);例如以下图所看到的:

    idh.codepackagesappsSettingssrccomandroidsettingsluetoothDeviceListPreferenceFragment.java

    private void updateProgressUi(boolean start) {
        if (mDeviceListGroup instanceof ProgressCategory) {
            ((ProgressCategory) mDeviceListGroup).setProgress(start);
        }
    }
    

    2)、这部分的作用,開始扫描,不显示列表中内容,或把之前列表中没扫描到的设备清除
    mDeviceManager.onScanningStateChanged(mStarted);
    idh.codepackagesappsSettingssrccomandroidsettingsluetoothCachedBluetoothDevice.java

    private void updateProgressUi(boolean start) {
        if (mDeviceListGroup instanceof ProgressCategory) {
            ((ProgressCategory) mDeviceListGroup).setProgress(start);
        }
    }
    2)、这部分的作用,開始扫描。不显示列表中内容,或把之前列表中没扫描到的设备清除
    mDeviceManager.onScanningStateChanged(mStarted);
    idh.codepackagesappsSettingssrccomandroidsettingsluetooth CachedBluetoothDevice.java
        public synchronized void onScanningStateChanged(boolean started) {
            // If starting a new scan, clear old visibility
            // Iterate in reverse order since devices may be removed.
            //假设開始新的扫描,清除旧的能见设备。迭代反序由于有的设备可能被删除
            for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
                CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
                if (started) {//假设扫描開始就不显示;
                    cachedDevice.setVisible(false);
                } else {//对扫描的结果作出推断。假设之前扫描过,这次没有扫描到,就移除列表。
                    if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE &&
                            cachedDevice.isVisible() == false) {
                        mCachedDevices.remove(cachedDevice);
                    }
                }
            }
        }































  • 相关阅读:
    linux 定时备份数据库
    Web前端优化>javascript优化
    IT项目开发的75条管理守则
    Web前端优化>css 优化
    Web前端优化>图象篇
    SNS关键点
    项目管理(对事不对人)
    Yahoo!网站性能最佳体验的34条黄金守则——内容
    互联网主题知名博客
    Web前端优化>Cookie 优化
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/7348841.html
Copyright © 2011-2022 走看看