`

Bluetooth

阅读更多

BlueTooth

根据官方DOC翻译

(本人英语4级没过,看不懂请自行脑补)

 

Bluetooth API操作流程:

  1. 搜索其他蓝牙设备
  2. 查询本地匹配器已经匹配的蓝牙设备
  3. 建立RFCOMM通道
  4. 通过Service发现并连接其他设备
  5. 与其他蓝牙设备进行数据交互
  6. 管理多个连接

Permission:

  • android.permission.BLUETOOTH:Allows applications to connect to paired bluetooth devices
  • android.permission.BLUETOOTH_ADMIN:Allows applications to discover and pair bluetooth devices

Setting Up Bluetooth:

       确保设备支持蓝牙模块,并已开启

  • 获取BluetoothAdapter。整个系统只有一个BluetoothAdapter。如果返回null表示设备不支持蓝牙。
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 设备是否支持蓝牙
if (mBluetoothAdapter == null) {
	Toast.makeText(this, R.string.not_support, Toast.LENGTH_SHORT).show();
}

 

  • 开启蓝牙:如果没用开启蓝牙,会弹出系统对话框,请求用户开启蓝牙功能。如果开启成功,则返回:Activity.RESULT_OK,否则返回:Activity.RESULT_CANCELED。
  • 监听状态:你可以通过监听BluetoothAdapter. ACTION_STATE_CHANGED广播获得蓝牙状态变化信息。广播包含以下额外字段: EXTRA_STATE和EXTRA_PREVIOUS_STATE。常见值包括:STATE_TURNING_ON,STATE_ON,STATE_TURNING_OFF和STATE_OFF。

Finding Devices:

       使用BluetoothAdapter,你可以找到远程蓝牙设备。通过查找设备,或查询配对(绑定)的设备列表。查找设备会搜索区域内的蓝牙设备,如果该设备设定为可被发现,则会返回一些信息,通过该信息则可连接设备。当首次建立连接,该设备信息会被保存并可通过API获得,使用MAC地址不需要重新搜索设备(假设设备在可连接范围内)。

  • 配对(Pair): 意味着两个设备都知道彼此的存在,有一个共同的链路密钥,可用于进行认证,并且能够建立与彼此的加密连接的。
  • 连接(Connect):该装置当前共享一个RFCOMM信道,并能够相互传送数据。Android Bluetooth API's要求设备配对前需要先建立RFCOMM连接(当你通过API建立加密连接配对会自动执行)。

 

Querying paired devices:

       通过getBondedDevices()可返回一组蓝牙配对清单(BluetoothDevice)。

 

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
	mDeviceInfoList = new ArrayList<HashMap<String, Object>>();
	for (BluetoothDevice device : pairedDevices) {
		mDeviceInfoList.add(device.getName() + "\n" + device.getAddress());
	}
}

 

 

Discovering devices:

       通过startDiscovery()可搜索设备,此方法会立即返回Boolean提示是否成功开始搜索。这是一个异步操作,搜索时间大约12秒然后会返回发现的蓝牙设备信息。对于每个搜索到的设备,系统会广播一个ACTION_FOUND  Intent,装载的信息包括:EXTRA_DEVICE和EXTRA_CLASS(包含BluetoothDevice和BluetoothClass)。所以你需要注册BroadcastReceiver接收ACTION_FOUND  Intent。

 

// 搜索到蓝牙设备时接收广播提示信息
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
// 搜索完成时接收广播提示信息
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);

private BroadcastReceiver mReceiver = new BroadcastReceiver() {

	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		String action = intent.getAction();
		if (BluetoothDevice.ACTION_FOUND.equals(action)) {
			// 如果查找到设备
			BluetoothDevice device = intent
					.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
		} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
				.equals(action)) {
			// 查找完成
		}
	}
};

protected void onDestroy() {
	// TODO Auto-generated method stub
	super.onDestroy();
	if (mBluetoothAdapter != null) {
		// 停止搜索设备
		mBluetoothAdapter.cancelDiscovery();
	}
	// 注销广播监听
	this.unregisterReceiver(mReceiver);
}

       需要注意的是,搜索蓝牙设备非常消耗资源,所以一旦找到对应设备,应该使用cancelDiscovery()停止搜索。如果你已有该设备的连接再执行搜索会节约带宽。

 

       如果你想使本地设备能被其他设备检测到,调用startActivityForResult(Intent,INT)和ACTION_REQUEST_DISCOVERABLE action Intent.。这将发出一个请求,以使通过系统设置发现模式(无需停止应用程序)。默认可见时间为120秒,也可以能过EXTRA_DISCOVERABLE_DURATION Intent extra来修改可见时间(最大值为3600秒,0表示设备始终可见)。系统会弹出对话框提示用户设置可见,并调用onActivityResult()返回结果,如果用户选择确定,则返回时间,如果用户选择否或者出现错误,则返回RESULT_CANCELED。如果未开启蓝牙,使设备可见会自动开启。如果你希望监听到设备可见状态的改变,可以注册BroadcastReceiver ACTION_SCAN_MODE_CHANGED Intent,包含:EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE。

 

// 设置设备对外可见,持续30秒
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivityForResult(intent, REQUEST_DISCOVERABLE);

 

 

Connectting Devices:

       服务器端创建Server Socket,客户端使用服务器端的MAC地址创建连接,当服务器端与与客户端连接时,双方的BluetoothSocket会在同一RFCOMM通道中。此时双方都各自拥有input和output stream,并能实现数据传输。服务器端和客户端分别以不同的方式获得BluetoothSocket。服务器端:当传入连接Accepted时,接收到BluetoothSocket;

 

客户端:当客户端打开RFCOMM通道时,接收到BluetoothSocket。

 

      有一种实现方法是每台设备均准备作为服务器,均打开Server Socket并监听连接。然后其它设备则以客户端的形式创建连接。

      注意:如果两个设备先前未配对,Android框架将发送配对请求通知对话框给用户,应用程序不需要关注的设备是否被配对。您的RFCOMM连接尝试将阻塞,直到用户成功配对,或者如果用户拒绝配对,或者如果配对失败或者超时就会失败。

 

Connecting as a server:

       当你尝试连接两台设备,其中一台需要充当服务器端打开并持有BluetoothServerSocket。Server Socket目的是监听来自客户端的连接请求,当客户端的连接请求被通过,服务器端将提供BluetoothSocket。一旦取得BluetoothSocket,BluetoothServerSocket应该被移除,除非你打算接受更多连接。

设置Server Socket和接收连接请求的基本流程:

  • 使用listenUsingRfcommWithServiceRecord(String, UUID)获得BluetoothServerSocket。String是服务器端的名称。UUID它是用来唯一标识应用程序的蓝牙服务。如果与蓝牙串口(Bluetooth serial board)连接可以尝试使用标准的SPP UUID:00001101-0000-1000-8000-00805F9B34FB。但是如果连接的是Android Peer,你需要生成自己的UUID。
  • 通过accept()开始监听连接请求。这是一个阻塞调用,当任意连接被同意或者发生异常时则会退出。只有当客户端发送连接请求,并且请求数据中的UUID与Server Socket的UUID才能被同意。如果连接成功,则返回BluetoothSocket。
  • 除非你打算接受其他的连接,否则连接成功后,调用close()。这将释放Server Socket及其所有资源,但不会关闭accept()返回的BluetoothSocket。RFCOMM只允许每个通道一个连接的客户端的时间,因此,在大多数情况下,在BluetoothServerSocket通过连接请求后立即调用close()。
  • Accept()不能运行于主线程,应该创建子线程执行些方法,另外BluetoothServerSocket和BluetoothSocket所有方法都是线程安全的。
private class AcceptThread extends Thread {
	private final BluetoothServerSocket mmServerSocket;
	private String mSocketType;

	public AcceptThread(boolean secure) {
		BluetoothServerSocket tmp = null;
		mSocketType = secure ? "Secure" : "Insecure";

		// 创建一个新的server socket监听,secure表示是否使用加密方式
		try {
			if (secure) {
				tmp = mAdapter.listenUsingRfcommWithServiceRecord(
							NAME_SECURE, MY_UUID_SECURE);
			} else {
				tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(
							NAME_INSECURE, MY_UUID_INSECURE);
			}
		} catch (IOException e) {
		}
		mmServerSocket = tmp;
	}

	public void run() {
		BluetoothSocket socket = null;
		// 保持监听,直到Socket返回,或者中止
		while (true) {
			try {
				// 这是一个阻塞方法,只会返回成功,否则抛出异常
				socket = mmServerSocket.accept();
			} catch (IOException e) {
				break;
			}
				// 如果连接成功
			if (socket != null) {
				// 新创建一个线程使用Socket
				manageConnectedSocket (socket);
mmServerSocket.close();
break;
			}
		}
}

	public void cancel() {
		try {
			mmServerSocket.close();
		} catch (IOException e) {
		}
	}
}

       请注意,当accept()返回的BluetoothSocket,socket已经连接,所以客户端不需要调用connect()。manageConnectedSocket()用于启动线程传输数据。你可能还需要在线程里提供public方法用于关闭BluetoothSocket。

 

Connecting as a client:

       客户端连接服务器端首先要从服务器端获得BluetoothDevice(参考Finding Devices),然后从BluetoothDevice中获得BluetoothSocket并创建连接。基本流程如下:

  • 通过createRfcommSocketToServiceRecord(UUID)从BluetoothDevice中获得BluetoothSocket。UUID必须与服务器端的UUID一致。
  • 通过connect()启动连接。connect()连接服务器时,服务器端会匹配UUID,如果匹配成功,服务器会同意连接请求并提供RFCOMM能道,此时connect()会返回。Connect()是阻塞方法,所以需要创建子线程执行,如果连接出现任何问题或者超时(12秒),将会抛出错误。需要注意的是,调用connect()时,确保已经停止搜索(cancelDiscovery()),否则性能会明显降低。也可以使用isDiscovering()检查是否正在搜索中。
private class ConnectThread extends Thread {
	private final BluetoothSocket mmSocket;
	private final BluetoothDevice mmDevice;
	private String mSocketType;

	public ConnectThread(BluetoothDevice device, boolean secure) {
		mmDevice = device;
		BluetoothSocket tmp = null;
		mSocketType = secure ? "Secure" : "Insecure";
		// 通过BluetoothDevel和UUID获得BluetoothSocket
	try {
			if (secure) {
				// 使用加密方式
				tmp = device
						.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
			} else {
				// 不使用加密方式,需要API Level 10支持
				tmp = device
				.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
			}
		} catch (IOException e) {
		}
		mmSocket = tmp;
	}

	public void run() {
// 停止搜索,搜索设备会占用大量资源
		mAdapter.cancelDiscovery();
		// 创建Bluetooth连接
		try {
// connect阻塞方法,只会返回成功, 否则抛出异常
			mmSocket.connect();
		} catch (IOException e) {
			try {
				mmSocket.close();
			} catch (IOException e2) {
		}
			return;
		}

		// 完成后重置线程
		synchronized (BluetoothChatService.this) {
			mConnectThread = null;
		}
		// 创建一个独立的线程使用Sokcet
manageConnectedSocket(mmSocket);
}

	public void cancel() {
		try {
			mmSocket.close();
		} catch (IOException e) {
		}
	}
}

 

       当使用完BluetoothSocket,调用close()关闭连接释放资源。

 

Managing a Connection:

       当两(多)台设备连接成功后,两台设备都持有BluetoothSocket,这时可以进行数据交互了。使用BluetoothSocket传输数据流程:

  • 通过Socket使用InputStream和OutputStream数据。
  • 通过read(byte[])和write(byte[])读写数据流。需要注意的是,你要为读写流创建线程,因为read(byte[])和write(byte[])是阻塞方法,read(byte[])会一直阻塞直到所有数据被读出;write(byte[])通常不会阻塞,但如果远和设备没有迅速调用read(byte[])又或者缓冲区已经满,则会造成阻塞。所以你的线程run主要用于读取InputStream。另外提供public方法,以启动写入输出流。
private class ConnectedThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final InputStream mmInStream;
		private final OutputStream mmOutStream;

		public ConnectedThread(BluetoothSocket socket, String socketType) {
			mmSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			// 获取输入和输出流,使用临时对象,因为成员流是final的
			try {
				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();
			} catch (IOException e) {
			}

			mmInStream = tmpIn;
			mmOutStream = tmpOut;
		}

		public void run() {
			Log.i(TAG, "BEGIN mConnectedThread");
			byte[] buffer = new byte[1024];
			int bytes;

			// 当连接成功后,一直监听InputStream
			while (true) {
				try {
					// 从InputStream读取数据
					bytes = mmInStream.read(buffer);
					// 发送获得的数据至UI Activity
					mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes,
							-1, buffer).sendToTarget();
				} catch (IOException e) {
					break;
				}
			}
		}
		// 写入数据并发送
		public void write(byte[] buffer) {
			try {
				mmOutStream.write(buffer);
			} catch (IOException e) {
			}
		}
		// 关闭连接
		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
			}
		}
	}

 

Working with Profiles:

       从Android 3.0开始,Bluetooth API包括了对Bluetooth Profiles的支持。Bluetooth Profile是一种基于设备间的蓝牙通信无线协议规范。其中一个例子是Hands-Free profile(免提模式),手机连接无线耳机,两个设备都必须要支持Hands-Free profile。你可以通过实现BluetoothProfile接口编写自己的类来支持特定的Bluetooth profiles。

  • Headset:Headset profile提供对手机使用蓝牙耳机的支持。Android提供BluetoothHeadset类,通过进程通讯(IPC)代理控制蓝牙耳机服务。这里包括蓝牙耳机和免提(v1.5)模式。BluetoothHeadset包括支持AT命令。
  • A2DP:The Advanced Audio Distribution Profile (A2DP高级音频传输模式)定义了多高质量的音频可以通过蓝牙传输。Android提供BluetoothA2dp类,通过进程通讯(IPC)代理控制蓝牙A2DP服务。
  • Health Device:Android 4.0 (API level 14) 支持Bluetooth Health Device Profile (HDP)。它使你能够创建应用,通过蓝牙与Health Device通讯。例如:heart-rate monitors(心脏速率监视器),blood meters(血压计),thermometers(体温计),scales(身体均衡器),等等。

基本步骤如下:

  • 获得Adapter(参考Setting Up Bluetooth)
  • 通过方法getProfileProxy()建立与模式关联的理对象的连接。在下面的例子中,配置文件代理对象是BluetoothHeadset的一个实例。
  • 设置BluetoothProfile.ServiceListener用于监听服务器端连接或断开连接的事件,并通知BluetoothProfile IPC。
  • 在方法onServiceConnected()中获得profile proxy的handle对象。
  • 一旦获得profile prox对象,你可以用它来监测连接状态,并进行其它相关的操作。

例如,以下代码显示了如何连接到一个BluetoothHeadset代理对象,这样就可以控制Headset profile:

 

BluetoothHeadset mBluetoothHeadset;
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
    public void onServiceConnected(int profile, BluetoothProfile proxy) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = (BluetoothHeadset) proxy;
        }
    }
    public void onServiceDisconnected(int profile) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = null;
        }
    }
};
// ... call functions on mBluetoothHeadset
// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);

 

Vendor-specific AT commands

        从Android 3.0开始应用程序可以通过注册来接收来自耳机的pre-defined vendor-specific AT commands系统广播(例如:Plantronics +XEVENT command),例如应用程序可以通过广播接收所连接设备的电量水平,并通知用户。创建广播接收器并通过ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent处理来自耳机的vendor-specific AT commands。

 

Health Device Profile:

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics