Android-蓝牙聊天代码解析

来源:互联网 发布:光纤网络延迟高怎么办 编辑:程序博客网 时间:2024/05/19 22:25

Android SDK 里包含了蓝牙聊天程序的源代码,位于\BluetoothChat。可以使用 adt-bundle 里的eclipse.exe将其导入。需要准备两台 Android 手机,一个作为客户端,一个作为服务器。

1.1 权限设置

对蓝牙的操作需要相应的权限,请参考AndroidManifest.xml文件里的如下两行语句:

<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />

<uses-permissionandroid:name="android.permission.BLUETOOTH" />

1.2 启用蓝牙设备

在两台手机上分别运行BluetoothChat。程序首先启动BluetoothChat这个Activity。

1.2.1 获取蓝牙设备

代码执行到 BluetoothChat.onCreate 函数,与蓝牙相关的代码如下:

mBluetoothAdapter= BluetoothAdapter.getDefaultAdapter();

mBluetoothAdapter是一个BluetoothAdapter(蓝牙适配器),上面的代码就是获取Android手机的蓝牙设备,通过它来操作蓝牙设备。

1.2.2 打开蓝牙设备

手机上的蓝牙设备可能是处于关闭状态的,聊天前需要打开它。

代码执行到 BluetoothChat.onStart 函数:

if(!mBluetoothAdapter.isEnabled()){

IntentenableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(enableIntent,REQUEST_ENABLE_BT);

}

mBluetoothAdapter.isEnabled()返回true 表示蓝牙设备处于打开状态,否则表示关闭状态。

startActivityForResult 将启动一个界面,询问用户是否打开蓝牙设备。当用户单击确定按钮的时候,该界面会被销毁,然后系统会调用BluetoothChat.onActivityResult函数。

public void onActivityResult(int requestCode, int resultCode,Intent data){

switch(requestCode) {

case REQUEST_ENABLE_BT:

if (resultCode == Activity.RESULT_OK) {

setupChat();

}

}

}

此时的requestCode将是startActivityForResult的第二个参数,即REQUEST_ENABLE_BT。用户单击了确定按钮,因此resultCode 等于Activity.RESULT_OK。结果,setupChat将被调用。

setupChat 最重要的工作就是创建了一个BluetoothChatService对象,其代码如下:

mChatService = new BluetoothChatService(this, mHandler);

BluetoothChatService 可用于服务器,创建监听线程,监听来自客户端的连接请求;也可以用于客户端,连接服务器。BluetoothChatService构造函数的第一个参数没有任何作用。第二个参数比较重要:它用于接收来自BluetoothChatService的反馈消息,如:BluetoothChatService完成读、写操作将发送MESSAGE_READMESSAGE_WRITE消息。

1.3 创建监听线程

代码执行到 BluetoothChat.onResume,其中最关键的代码是

mChatService.start();

它调用的是BluetoothChatService.start函数。在这个函数里,创建了两个监听线程:

mSecureAcceptThread = new AcceptThread(true);

mInsecureAcceptThread = new AcceptThread(false);

mSecureAcceptThread 用于监听加过密的连接请求,mInsecureAcceptThread用于监听未加密的连接请求。它们最大的区别在于:创建服务器套接字时,使用的函数不同,代码在AcceptThread的构造函数里:

if (secure) {

tmp = mAdapter.listenUsingRfcommWithServiceRecord(

NAME_SECURE,MY_UUID_SECURE);

} else {

tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(

NAME_INSECURE, MY_UUID_INSECURE);

}

可见:listenUsingRfcommWithServiceRecord用于监听加密连接,listenUsingInsecureRfcommWithServiceRecord用于监听未加密连接。这两个函数的第一个参数用处不大,第二个参数很重要,即服务UUID。客户端连接服务器时,需要知道服务器提供的服务UUID是什么,否则无法连接。有些服务是固定的,如:串行口服务,其UUID就是00001101-0000-1000-8000-00805F9B34FB。在本例中,UUID是随机生成的。为了保证这个UUID不重复,可以使用WindowsAPI函数 CoCreateGuid

在多线程AcceptThread.run里,线程在socket = mmServerSocket.accept();这个地方被阻塞。当有客户端连接的时候,accept函数才会返回,执行下面的代码。accept函数返回的套接字socket是通讯套接字和服务端的监听套接字mmServerSocket是不一样的,切莫混淆。

AcceptThread.run里,connected(socket, socket.getRemoteDevice(),mSocketType);将创建多线程ConnectedThread,它的主要作用就是不断轮询客户端是否有数据发送过来,有的话就读取并通知mHandler

也就是说,两台手机只要运行了BluetoothChat这个程序,就都是服务器了。接下来的操作需要确定两台手机的身份:一台作为服务器,另一台作为客户端。

1.4 使设备可见

作为服务器的手机,其蓝牙设备必须能被客户端搜索到。需要对此进行设置。用户单击服务器手机上的菜单项discoverable,将执行到BluetoothChat. onOptionsItemSelected函数里的ensureDiscoverable函数。

public boolean onOptionsItemSelected(MenuItem item) {

Intent serverIntent = null;

switch (item.getItemId()) {

... ... ... ...

case R.id.discoverable:

ensureDiscoverable();

return true;

}

return false;

}

ensureDiscoverable函数的功能就是设置蓝牙设备能被客户端搜索到,其代码如下:

if (mBluetoothAdapter.getScanMode() !=

BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {

Intent discoverableIntent = newIntent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300); //设置蓝牙300 秒内可以被搜索到

startActivity(discoverableIntent);

}

startActivity将弹出一个对话框,询问是否使设备可见。请单击确定按钮,完成蓝牙设置。

1.5 搜索蓝牙设备

作为客户端的手机,必须知道服务器的地址和服务UUID,才能连接服务器。通过搜索蓝牙设备,可以获取服务器地址。

用户单击客户端手机上的菜单项secure_connect(加密连接)或insecure_connect(未加密连接),将执行到BluetoothChat.onOptionsItemSelected函数:

public boolean onOptionsItemSelected(MenuItem item) {

Intent serverIntent = null;

switch (item.getItemId()) {

case R.id.secure_connect_scan:

serverIntent = new Intent(this, DeviceListActivity.class);

startActivityForResult(serverIntent,REQUEST_CONNECT_DEVICE_SECURE);

return true;

case R.id.insecure_connect_scan:

serverIntent = new Intent(this, DeviceListActivity.class);

startActivityForResult(serverIntent,REQUEST_CONNECT_DEVICE_INSECURE);

return true;

... ... ...

}

return false;

}

不论加密连接还是未加密连接,启动的都是DeviceListActivity这个Activity。它主要做了两项工作:列出已经配对的蓝牙设备;搜索周围的蓝牙设备。

1.5.1 获得已经配对的蓝牙设备

代码在DeviceListActivity.onCreate函数里,只有两行:

mBtAdapter = BluetoothAdapter.getDefaultAdapter();

Set<BluetoothDevice> pairedDevices =mBtAdapter.getBondedDevices();

 

1.5.2 搜索蓝牙设备

启动搜索的代码在DeviceListActivity.doDiscovery里,只有一行:

mBtAdapter.startDiscovery();

搜索到的蓝牙设备如何通知程序?答案是放在广播接收器mReceiver里。它的创建代码如下:

private final BroadcastReceiver mReceiver = newBroadcastReceiver() {

public void onReceive(Context context, Intent intent) {

... ... ...

}

};

mReceiver覆盖了onReceive函数,一旦搜索到蓝牙设备,就将其增加到mNewDevicesArrayAdapter这个数组里。最重要的数据就是蓝牙设备的地址,即device.getAddress()

创建了mReceiver之后,需要注册才能正常工作,其注册代码在DeviceListActivity.onCreate

IntentFilter filter = newIntentFilter(BluetoothDevice.ACTION_FOUND);

this.registerReceiver(mReceiver, filter);

filter = newIntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);

this.registerReceiver(mReceiver, filter);

最后,不再使用mReceiver的时候,不要忘了注销,其代码在DeviceListActivity.onDestroy

this.unregisterReceiver(mReceiver);

1.6 连接服务器

在客户端手机的DeviceListActivity界面上,完成搜索后将列出周围的蓝牙设备。用户单击某个设备,将连接该设备。

用户单击DeviceListActivity界面上的蓝牙设备列表,将调用DeviceListAcivity.mDeviceClickListeneronItemClick函数。关键代码如下:

String info = ((TextView) v).getText().toString();

String address = info.substring(info.length() - 17);

Intent intent = new Intent();

intent.putExtra(EXTRA_DEVICE_ADDRESS, address);

setResult(Activity.RESULT_OK, intent);

finish();

上述代码中intent.putExtra保存了服务器地址。finish将销毁本界面。系统将依次调用函数:BluetoothChat.onActivityResult==>BluetoothChat.connectDevice==>mChatService.connect(device,secure)==>BluetoothChatService.connectconnect函数将创建连接线程——ConnectThread线程。该线程的重点在于:

1、由createRfcommSocketToServiceRecordcreateInsecureRfcommSocketToServiceRecord创建套接字。需要指定蓝牙服务器的服务UUID

2mmSocket.connect();完成连接;

3connected(mmSocket, mmDevice,...);创建读取数据的线程——ConnectedThread

至此,服务器与客户端的蓝牙数据连接完成了,可以聊天了。

1.7 收发数据

用户程序主界面的文本框内输入文本,单击Send按钮,将依次调用BluetoothChat.sendMessage==>mChatService.write==>ConnectedThread.writeConnectedThread.write的代码如下:

mmOutStream.write(buffer);

mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1,buffer).sendToTarget();

第一行是发送数据给对方,第二行是给mHandler发送消息。这个mHandlerBluetoothChatService.mHandler。在BluetoothChatService构造时mHandler被指定为第二个参数,其实就是BluetoothChat.mHandlerBluetoothChat.mHandler在处理MESSAGE_WRITE消息时,将更新界面显示。

服务器或客户端都有一个ConnectedThread线程,一旦有数据进来,就会调用mHandler.obtainMessage(BluetoothChat.MESSAGE_READ,bytes, -1, buffer).sendToTarget();即给mHandler发送MESSAGE_READ消息。BluetoothChat.mHandler在处理此消息时,将更新界面显示。

1 0