Key words: Android, bluetoothmodule, SPP, 蓝牙
目录:
一. android 官方提供的案例分析
1 主Activity
1.1 onCreate()
1.2 onStart()
1.3 onResume()
1.4 onDestroy()
1.5 menuItem
2 BluetoothClient
2.1 BluetoothSocket Programming
2.1.1 Server
2.1.2 Client
2.1.3 ConnectedThread
3 Mobile Phone Bluetooth With Other Bluetooth Module (Bluetooth Hardware)
3.1 UUID配对问题
3.2 Javax.Comm 或者 RXTXComm API & AT command
————————————————————————————————————————————————————————————————————————————一. android 官方提供的案例分析
我首先用简单的话来概括一下,该蓝牙的案例是由两个部分组成: 第一是主activity,第二个是BluetoothClient,包含connect进程 & connected进程。
接受到的数据通过handler为手段,msg类作为容器来进行传递,传递给activity以后,获得msg的内容在进行相应的GUI改变或者其他操作(数据库等等根据所需)。
1 主Activity
在这个activity里,首先根据activity的流程(注意我的解释基本按照activity的流程来说,编程的时候无论用不用那个节点的方法,我总是写上,并且带上相应的debug语句方便以后监视调试)。
1.1 onCreate()
protected void onCreate(Bundle savedInstanceState)
这个总是初始化GUI 组件,获得组件名,然后特别的蓝牙部分在于:
//Dealing with the bluetooth connection.
// Get local Bluetooth adapter
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// If the adapter is null, then Bluetooth is not supported
if (mBluetoothAdapter == null) {
Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
finish();
return;
}
要用蓝牙总归要先判断一下你手机有没有蓝牙模块,这一段就是起这个作用。
1.2 onStart()
public void onStart()
有模块是不是开启了,是不是又权限,这个需要用户决定,所以这里系统将检查并询问用户是否开启使用蓝牙。
// If BT is not on, request that it be enabled.
if (!mBluetoothAdapter.isEnabled()) {
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
} else {
if(mBluetoothClient==null) setupBlueToothClient();
}
这里运用了android的信息传递机制就是Intent对象。
void startActivityForResult(intent, “是否启用返回值处理”)
@Override //返回值处理方法
public void onActivityResult(int requestCode, int resultCode, Intent data){switch case}
这两个是系统现有的方法,如果你需要处理系统返回的消息,则成对出现。 “是否启用返回值处理”
在android里是由系统定义的RequestCode, 当requestCode > 0的时候,你就有必要调用 "返回值处理方法", 因为的确有返回值需要处理。
这两个方法需要被再次调用用来询问是否进行连接。
1.3 onResume()
@Override
public synchronized void onResume() {
super.onResume();
if(D) Log.e(TAG, "+ ON RESUME +");
if(mBluetoothClient!=null){
mBluetoothClient.setmHandler(this._Handler); //cannot be deleted, keep mhanlder is always represents the current Activities Handler
if(mBluetoothClient.getState()==BlueToothClient.STATE_NONE)
mBluetoothClient.start();//first time initialize connectionstatus.
}
BluetoothClient 是管理蓝牙的客户端(顾名思义), 管理连接,断开,信息传递。 觉得真是一个很对象的概念。
如果有连接而且从其他activity回到当前activity那当然要把handler抓回来,因为下面的操作都将在当前activity发生。
那么什么时候client会被初始化呢? 相信非常好理解,当你的手机有蓝牙功能,Client就要出现了!来处理连接等问题。
1.4 onDestroy()
这个就是我之前所说debug专用风格。
@Override //进行数据的保存,这是程序不会被android由于内存不够强行关闭时的保存, 如果android申请额外内存是必须关闭当前程序是,则有必要使用
onSaveInstanceState(){} 这个方法给出机会给程序保存必须保存的东西。
public synchronized void onPause() {
super.onPause();
if(D) Log.e(TAG, "- ON PAUSE -");
}
@Override
public void onStop() {
super.onStop();
if(D) Log.e(TAG, "-- ON STOP --");
}
@Override
public void onDestroy() {
super.onDestroy();
// Stop the Bluetooth chat services
if (mBluetoothClient != null) mBluetoothClient.stop();
mBluetoothClient=null;
if(D) Log.e(TAG, "--- ON DESTROY ---");
}
onDestroy(),又要顾名思义了,处理一切需要处理的结尾工作,个人觉得譬如关闭线程,关闭动画,不提倡保存什么数据。
1.5 menuItem
前面提到过按键然后进行连接。按的键其实就是menuItem对象。对于蓝牙连接,启动模块以后(操作已经完成), 我们需要:扫描,能被找——2个按键。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.option_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu){
MenuInflater inflater = getMenuInflater();
menu.clear();
inflater.inflate(R.menu.option_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.scan:
// Launch the DeviceListActivity to see devices and do scan
Intent serverIntent = new Intent(this, DeviceListActivity.class);
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
return true;
case R.id.discoverable:
// Ensure this device is discoverable by others
ensureDiscoverable();
return true;
}
return false;
}
这三个方法是定义OptionMenu的三段论。很好理解。 同样这里出现了startActivityForResult()方法来处理连接其他机器这个动作,为什么呢?同样很好理解,会有很多机器有蓝牙开着被找,这个返回的DeviceList肯定是要被处理的,出一个列表,然后选择一个(EventonClick 事件监听方法),交给BluetoothClient去连接。 第一部分就全部结束了,个人觉得比较关键的其实是这个方法:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(D) Log.d(TAG, "onActivityResult " + resultCode);
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
// When DeviceListActivity returns with a device to connect
if (resultCode == Activity.RESULT_OK) {
// Get the device MAC address
String address = data.getExtras()
.getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
// Get the BLuetoothDevice object
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
// Attempt to connect to the device
mBluetoothClient.connect(device);
}
break;
case REQUEST_ENABLE_BT:
// When the request to enable Bluetooth returns
if (resultCode == Activity.RESULT_OK) {
// Bluetooth is now enabled, so set up a chat session
setupBlueToothClient();
} else {
// User did not enable Bluetooth or an error occured
if(D) Log.d(TAG, "BT not enabled");
Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
finish();
}
}
}
他将是开启Java Bluetooth Socket的一扇门。 下回就将阐述管理连接和接收到数据后的处理方法。
2 BluetoothClient
首先来理顺一下连接创建的思路:
当当前手机是作为client进行主动连接角色的时候, 所需要的工作是“连接”以及“连接任务完成后的数据流导向”。
当当前手机是作为server被动连接角色的时候,所需要的工作是“监听”以及“连接任务完成后的数据流导向”。
2.1 BluetoothSocket Programming
和平时处理网络socket编程的不同处在于,首先不牵涉到IP地址等。对于Bluetooth而言,使用的配对对象是已经被很好打包的“BluetoothDevice”。 同时应用程序需要有一个ID来进行区别,在此使用的RFComm信道就是用UUID进行区别,UUID产生的是伪随机码,而且基本没有重复的可能性, 可以很好的进行软件端口的区分。 UUID 将会在下面一部分进行额外的讨论。这一部分由Server, Client & ConnectedThread组成。
Socket Programming Diagram:
一句话server和client都各由两个线程进行管理。
server监听(AcceptThread),有连接后(ConncectedThread)把client打包成一个对象——Socket对象,然后就可以通过这个Socket对象获得Client的数据流IO对象(ConnectedThread功能)。
client连接后(ConnectThread), 有连接后(ConnectedThread)把server打包成一个对象——Socket对象,然后就可以通过这个Socket对象获得Server的数据流IO对象(ConnectedThread功能)。
在server端,看到的是socket,
在client端,看到的也是socket。
2.1.1 Server
Server端的主要是由 “监听线程AcceptThread ” 以及 “ConnectedThread” 进行控制。
以下是AcceptThread主体,觉得这个也是一般线程定义的模板,构造方法,run(), 收尾工作(关闭socket)。
AcceptThread需要在BluetoothClient被构造以后立刻初始化,因为如果你的系统有蓝牙,而且能被找,就是一个server,所以AcceptThread的初始化将会在BluetoothClient的构造函数被定义。
流程: 获得ServerSocket-> 用ServerSocket监听->有接入则返回->(只允许一个接入,如果已有通信进行则关闭该socket连接)->进入ConnectedThread进行流数据处理。(如下关键程序所示:)
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
socket = mmServerSocket.accept();
startConnectedThread(socket,socket.getDevice())
mmServerSocket.close();
private class AcceptThread extends Thread {
// The local server socket
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null;
// Create a new listening server socket
try {
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) {
Log.e(TAG, "listen() failed", e);
}
mmServerSocket = tmp;
}
public void run() {
if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
setName("AcceptThread");
BluetoothSocket socket = null;
// Listen to the server socket if we're not connected
while (mState != STATE_CONNECTED) {
try {
// This is a blocking call and will only return on a
// successful connection or an exception
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "accept() failed", e);
break;
}
// If a connection was accepted
if (socket != null) {
synchronized (BluetoothChatService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
// Situation normal. Start the connected thread.
connected(socket, socket.getRemoteDevice());
break;
case STATE_NONE:
case STATE_CONNECTED:
// Either not ready or already connected. Terminate new socket.
try {
socket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close unwanted socket", e);
}
break;
}
}
}
}
if (D) Log.i(TAG, "END mAcceptThread");
}
public void cancel() {
if (D) Log.d(TAG, "cancel " + this);
try {
mmServerSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of server failed", e);
}
}
}
private class AcceptThread extends Thread {
// The local server socket
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null;
// Create a new listening server socket
try {
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) {
Log.e(TAG, "listen() failed", e);
}
mmServerSocket = tmp;
}
public void run() {
if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
setName("AcceptThread");
BluetoothSocket socket = null;
// Listen to the server socket if we're not connected
while (mState != STATE_CONNECTED) {
try {
// This is a blocking call and will only return on a
// successful connection or an exception
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "accept() failed", e);
break;
}
// If a connection was accepted
if (socket != null) {
synchronized (BluetoothChatService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
// Situation normal. Start the connected thread.
connected(socket, socket.getRemoteDevice());
break;
case STATE_NONE:
case STATE_CONNECTED:
// Either not ready or already connected. Terminate new socket.
try {
socket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close unwanted socket", e);
}
break;
}
}
}
}
if (D) Log.i(TAG, "END mAcceptThread");
}
public void cancel() {
if (D) Log.d(TAG, "cancel " + this);
try {
mmServerSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of server failed", e);
}
}
}
2.1.2 Client
Client端的主要是由 “连接线程ConnectThread ” 以及 “ConnectedThread” 进行控制。以下是ConnectThread主体,again->构造方法,run(), 收尾工作(关闭socket)。
AcceptThread需要在BluetoothClient被构造以后立刻初始化,因为如果你的系统有蓝牙,而且能被找,就是一个server,所以AcceptThread的初始化将会在BluetoothClient的构造函数被定义。
流程: 获得ServerSocket-> 用ServerSocket监听->有接入则返回->(只允许一个接入,如果已有通信进行则关闭该socket连接)->进入ConnectedThread进行流数据处理。(如下关键程序所示:)
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
connect(从找到的机器里获得的Device名);
startConnectedThread(socket,socket.getDevice())
mmSocket.close();
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
mmDevice = device;
BluetoothSocket tmp = null;
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
try {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, "create() failed", e);
}
mmSocket = tmp;
}
public void run() {
Log.i(TAG, "BEGIN mConnectThread");
setName("ConnectThread");
// Always cancel discovery because it will slow down a connection
mAdapter.cancelDiscovery();
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmSocket.connect();
} catch (IOException e) {
connectionFailed();
// Close the socket
try {
mmSocket.close();
} catch (IOException e2) {
Log.e(TAG, "unable to close() socket during connection failure", e2);
}
// Start the service over to restart listening mode
BluetoothChatService.this.start();
return;
}
// Reset the ConnectThread because we're done
synchronized (BluetoothChatService.this) {
mConnectThread = null;
}
// Start the connected thread
connected(mmSocket, mmDevice);
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
2.1.3 ConnectedThread
这个进程是最关键的一个,处理数据流的节点。 通过得到的socket分别抓取对应的输入输出流, inputstream 以及outputstream。
public class ConnectedThread{
BluetoothSocket mSocket;
InputStream mInputStream;
OutputStream mOutputStream;
public ConnectedThread(BluetoothSocket socket){
mSocket=socket;
InputStream tmpIn=null;
OutputStream tmpOut=null;
try{
tmpIn=mSocket.getInputStream(); //获得输入流
tmpOut=mSocket.geOutputStream(); //获得输出流
}catch(IOException e){
e.printStackTrace();
}
}
public void run(){
byte[] buffer=new byte[2048];
while(true){
try{
bytes=mInputStream.read(buffer);
mHandler.ObtainMessage(Bluetooth.MessageRead, bytes,-1, buffer). SendToTarget();
}catch(IOException e){
connectionLost();
break;
}
}
}
public void write(byte[] buffer){
try{
mOutputStream.write(buffer);
}catch(IOException e){
connectionLost();
break;
}
}
public void cancel(){
try{
mSocket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
我给大家提供一个相当稳定的数据流处理方法, 非常适合于传输的信息是有通信协议的,无论是通过XML 或者webservice类似或者是自己定义的rules用来区分不同的数据段,都很适合。
我用xml做个例子,已知结构如下:
<cyk>
<label1/>
<label2/>
</cyk>
那么要处理这个xml文件就应该等他全部接收到以后才能处理。那如何才能判断并且做到呢? 如下(替代掉bytes=mInputStream.read(buffer);)。
byte[] buffer=new byte(2048);
StringBuilder curMsg= new StringBuilder();
while (true) {
try {
//Ensure the whole xml file is received completedly
bytes = mmInStream.read(buffer);
curMsg.append(new String(buffer, 0, bytes));
int endIdx = curMsg.indexOf(end);
if(D) Log.e(TAG,"endIdx: "+endIdx);
if (endIdx != -1) {
String fullMessage = curMsg.substring(0, endIdx + end.length());
if(D) Log.e(TAG,fullMessage);
curMsg.delete(0, endIdx + end.length());
}
XMLProcessing(fullMessage);
}catch (IOException e) {
if(D) Log.e(TAG, "disconnected", e);
connectionLost();
break;
}
我使用了一段时间,觉得非常的elegant,可以尝试使用。
3 Mobile Phone Bluetooth With Other Bluetooth Module (Bluetooth Hardware)
不知道有没有朋友开发手机和其他蓝牙硬件进行配对,不是手机和手机简单的软件方法就可以解决。解决的途径:
- 蓝牙硬件——javax.Comm 或者 RXTXComm API & AT command
- 手机端 ——BluetoothSocket Programming
- UUID配对问题。
3.1 UUID配对问题
手机连接蓝牙模块比较特殊:
首先查一下蓝牙模块的说明书,查找哪一个UUID是进行串口通行的Serial Connection。我使用的是“1101”。
然后就挺无奈的,因为我们看到的Android的UUID 都是“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”的模式,哪有“1101” 4位的。
最后从网上得到了SPP协议的Android专用串口连接UUID:"0000xxxx-0000-1000-8000-00805F9B34F" 的。
这里的“xxxx”就是蓝牙模块的串口号。这个首要问题解决了后面就方便了。
UUID=0000xxxx-0000-1000-8000-00805F9B34F;
device.createRfcommSocketToServiceRecord(UUID); client(手机)连接Server(蓝牙模块)。3.2 Javax.Comm 或者 RXTXComm API & AT command
-------------------------------------------------------------------------------------------------------------------------
English Version
Content:
1. The example from android
1.1 The problems lie in the example
2. How can we modify the code and make the connection to be robust
2.1 AT Command.
2.2 What the hell with the UUID in android and Bluetoot Module?
3. Incoming data is XML how to lead the date to XMLParser.
-------------------------------------------------------------------------------------------------------------------------
2.2 What the hell with the UUID in android and Bluetoot Module?
First in my application, I need to use the android phone to connect an Bluetooth module which is integrated on a PCB board. The Bluetooth module I used is provided by Stollmann.
The following is an email I wrote to technical support of stollmann.
The email described how to manage a connection from android phone to the bluetooth module via AT Command.
For spp connection (Bluetooth module launchs the scan, theconnection and so on):
The uuid of App in android should be like: 0000xxxx-0000-1000-8000-00805F9B34FB, where"xxxx" is the App uuid displayed via AT command : at**binqserv d0x.
But I believe "CAFE" has been reserved only forapple products, so when I try to use "CAFE" the AT Command : atd d0x uCAFE. The connectionalways failed.
So I suggest that "CAFE" can also be used byother products not only for apples :)
For spp connection(Android phone launched the scan, theconnection).
The java code in android for handling a bluetoothconnection should be like this:
device.createRfcommSocketToServiceRecord(UUID);
Still it's the problem of UUID.
The service in BlueEva+P25/G2 that I should attach is theservice with UUID"1101"; So the UUID should be like:00001101-0000-1000-8000-00805F9B34FB.