移动互联网开发之蓝牙通信
- 项目要求
- 项目源码地址
- Bluetooth聊天
项目要求
在原有微信界面设计基础上(),在类微信程序的第一子项中完成“蓝牙聊天功能”
项目源码地址
Bluetooth聊天
- Bluetooth介绍
Android对 Bluetooth profile API的实现主要采用了中介模式、代理模式及状态模式等。应用通过一个统一的类BluetoothAdapter(蓝牙本地适配器类)与这些蓝牙设备协议对应的BLUETOOTH API进行交互。BluetoothAdapter是所有蓝牙对象交互和执行蓝牙操作的入口:包括调用BLUETOOTH profile API,发现其它蓝牙设备、查询配对成功的设备、使用已知的MAC地址实例化蓝牙设备、创建一个BluetoothServerSocket对象来监听其它蓝牙设备以及根据地址实例化蓝牙设备等操作。下面介绍代码实例操作。 - 清单文件注册权限,并在文件res/values/strings.xml里,添加程序运行过程中的状态描述文本及配色代码。
<!--下面2个是普通权限,只需要在清单文件里注册,不需要在程序里动态申请-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<resources>
<string name="app_name">蓝牙Demo</string>
<string name="send">发送</string>
<string name="not_connected">你没有链接一个设备</string>
<string name="bt_not_enabled_leaving">蓝牙不可用,离开聊天室</string>
<string name="title_connecting">链接中...</string>
<string name="title_connected_to">连接到:</string>
<string name="title_not_connected">无链接</string>
<string name="scanning">蓝牙设备搜索中...</string>
<string name="select_device">选择一个好友链接</string>
<string name="none_paired">没有配对好友</string>
<string name="none_found">附近没有发现好友</string>
<string name="title_paired_devices">已配对好友</string>
<string name="title_other_devices">其它可连接好友</string>
<string name="button_scan">搜索好友</string>
<string name="connect">我的好友</string>
<string name="discoverable">设置在线</string>
<string name="back">退出</string>
<string name="startVideo">开始聊天</string>
<string name="stopVideo">结束聊天</string>
</resources>
- 布局比较简单:
a.使用垂直线性布局并嵌套水平线性布局
b.在默认的主布局文件activity_main.xml里,添加1个Toolbar控件,其内包含2个水平的TextView控件;
c.在Toolbar控件的下方添加1个ListView控件,用于显示聊天内容;
d.最后在ListView控件的下方添加水平放置的1个EditText控件和一个Button控件。 - 编写用于蓝牙会话的服务组件ChatService,主要定义了三个内部类:AcceptThread(接受新连接)、ConnectThread(发出连接)和ConnectedThread (已连接)。
// 创建监听线程,准备接受新连接。使用阻塞方式,调用 BluetoothServerSocket.accept()
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null;
try {
//使用射频端口(RF comm)监听
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) {
}
mmServerSocket = tmp;
}
@Override
public void run() {
setName("AcceptThread");
BluetoothSocket socket = null;
while (mState != STATE_CONNECTED) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
if (socket != null) {
synchronized (ChatService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
connected(socket, socket.getRemoteDevice());
break;
case STATE_NONE:
case STATE_CONNECTED:
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}
}
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
连接线程,专门用来对外发出连接对方蓝牙的请求和处理流程。
构造函数里通过 BluetoothDevice.createRfcommSocketToServiceRecord() ,
从待连接的 device 产生 BluetoothSocket. 然后在 run 方法中 connect ,
成功后调用 BluetoothChatSevice 的 connected() 方法。定义 cancel() 在关闭线程时能够关闭相关socket 。
*/
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
mmDevice = device;
BluetoothSocket tmp = null;
try {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
e.printStackTrace();
}
mmSocket = tmp;
}
@Override
public void run() {
setName("ConnectThread");
mAdapter.cancelDiscovery();
try {
mmSocket.connect();
} catch (IOException e) {
connectionFailed();
try {
mmSocket.close();
} catch (IOException e2) {
e.printStackTrace();
}
ChatService.this.start();
return;
}
synchronized (ChatService.this) {
mConnectThread = null;
}
connected(mmSocket, mmDevice);
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
双方蓝牙连接后一直运行的线程;构造函数中设置输入输出流。
run()方法中使用阻塞模式的 InputStream.read()循环读取输入流,然后发送到 UI 线程中更新聊天消息。
本线程也提供了 write() 将聊天消息写入输出流传输至对方,传输成功后回写入 UI 线程。最后使用cancel()关闭连接的 socket
*/
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
@Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
while (true) {
try {
bytes = mmInStream.read(buffer);
mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
} catch (IOException e) {
connectionLost();
break;
}
}
}
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
} catch (IOException e) {
e.printStackTrace();
}
}
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 分别建立供主Activity使用的菜单文件res/menu/optionmenu.xml、选择好友(即已经配对过的蓝牙设备)的界面布局文件devicelist.xml。(具体代码见源码)
- 新建Activity组件DeviceList,实现选取与之会话的蓝牙设备。
(1)显示已配对的好友列表;
(2)搜索可配对的好友进行配对
(3)新选择并配对的蓝牙设备将刷新好友列表
注意:发现新的蓝牙设备并请求配对时,需要对应接受
关键技术:动态注册一个广播接收者,处理蓝牙设备扫描的结果
private void init() {
Button scanButton = findViewById(R.id.button_scan);
scanButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Toast.makeText(DeviceList.this, R.string.scanning, Toast.LENGTH_LONG).show();
doDiscovery(); //搜索蓝牙设备
}
});
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
//已配对蓝牙设备列表
ListView pairedListView =findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mPaireDeviceClickListener);
//未配对蓝牙设备列表
ListView newDevicesListView = findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mNewDeviceClickListener);
//动态注册广播接收者
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
for (BluetoothDevice device : pairedDevices) {
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
} else {
String noDevices = getResources().getText(R.string.none_paired).toString();
mPairedDevicesArrayAdapter.add(noDevices);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
this.unregisterReceiver(mReceiver);
}
private void doDiscovery() {
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
mBtAdapter.startDiscovery(); //开始搜索蓝牙设备并产生广播
//startDiscovery是一个异步方法
//找到一个设备时就发送一个BluetoothDevice.ACTION_FOUND的广播
}
- 使用菜单File→Refactor→Rename,重命名模块的MainActivity为BluetoothChat(它是蓝牙会话的主Activity组件程序)。
//使用Handler对象在UI主线程与子线程之间传递消息
private final Handler mHandler = new Handler() { //消息处理
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_STATE_CHANGE:
switch (msg.arg1) {
case ChatService.STATE_CONNECTED:
mTitle.setText(R.string.title_connected_to);
mTitle.append(mConnectedDeviceName);
mConversationArrayAdapter.clear();
break;
case ChatService.STATE_CONNECTING:
mTitle.setText(R.string.title_connecting);
break;
case ChatService.STATE_LISTEN:
case ChatService.STATE_NONE:
mTitle.setText(R.string.title_not_connected);
break;
}
break;
case MESSAGE_WRITE:
byte[] writeBuf = (byte[]) msg.obj;
String writeMessage = new String(writeBuf);
mConversationArrayAdapter.add("我: " + writeMessage);
break;
case MESSAGE_READ:
byte[] readBuf = (byte[]) msg.obj;
String readMessage = new String(readBuf, 0, msg.arg1);
mConversationArrayAdapter.add(mConnectedDeviceName + ": "
+ readMessage);
break;
case MESSAGE_DEVICE_NAME:
mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
Toast.makeText(getApplicationContext(),"链接到 " + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
break;
case MESSAGE_TOAST:
Toast.makeText(getApplicationContext(),
msg.getData().getString(TOAST), Toast.LENGTH_SHORT).show();
break;
}
}
};
//返回进入好友列表操作后的数回调方法
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONNECT_DEVICE:
if (resultCode == Activity.RESULT_OK) {
String address = data.getExtras().getString(DeviceList.EXTRA_DEVICE_ADDRESS);
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
mChatService.connect(device);
}else if(resultCode==Activity.RESULT_CANCELED){
Toast.makeText(this, "未选择任何好友!", Toast.LENGTH_SHORT).show();
}
break;
case REQUEST_ENABLE_BT:
if (resultCode == Activity.RESULT_OK) {
setupChat();
} else {
Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show();
finish();
}
}
}
- 操作步骤:
(1)在两个手机分别安装、运行本应用。如果未打开手机蓝牙,则进入打开蓝牙设置界面;
(2)在OptionMenu(选项菜单)里,选择“我的好友”,即可直接连接,再进行会话;
(3)新建好友前,需要先使用选项菜单“设置在线”,即要求对方可见(能被扫描到)。
然后,单击“搜索好友”按钮。