Android 通过samples\android-x\BluetoothChat学习蓝牙操作
2012-05-07 19:20
375 查看
最近帮别人写了一个东西需要用到蓝牙共享数据,发现Android SDK里的例子里的BluetoothChat——蓝牙聊天软件代码写得不错,就学习分析了一下。
项目java文件3个:BluetoothChat:主界面,显示聊天信息BluetoothChatService:里面有3个主要线程类,AcceptThread:蓝牙服务端socket监听线程.。ConnectThread:蓝牙socket连接线程。 ConnectedThread:连接后的通信线程DeviceListActivity:蓝牙扫描选择界面,负责传回选择连接的设备BluetoothChat:定义了很多常量,用于处理消息和请求:
控件不用理会,无非是一个显示连接到某个设备的TextView,一个用于显示对话的ListView,一个用于输入聊天内容的EditText,一个发送按钮Button
这里我学到了EditText注册了一个监听器,然后实现了软键盘按回车return键发送消息(一般我们在EditText里按return键是换行)
然后是ListView从底下开始显示:需要XML里一句:
然后我们就可以看到在onCreate方法里有获得BluetoothAdapter实例的语句:BluetoothAdapter代表的是一个本地蓝牙设备
在onStart方法里,有判断本地蓝牙是否可用的语句,不在的话想系统发送一个是否开启蓝牙的请求:
在onResume方法里,对BluetoothChatService实例进行判断有无以及状态,如果是‘无’状态,即刚启动,活stop了,就调用其start方法。
start方法作用就是开启BluetoothChatService里的AcceptThread线程,进行蓝牙服务端socket监听,开启之前它会确保连接线程和通信线程是关闭的,不然就代码强制关闭,因为蓝牙通信基本是点对点的。并且设置状态为监听STATE_LISTEN。
此外,还有一个ensureDiscoverable方法,目的在于使本机蓝牙可见,对应于菜单键menu的第二个选项
菜单menu的第一个选项是扫描周围可用的蓝牙设备进行连接:这里使用了请求结果的开启Activity方法(startActivityForResult)开启了DeviceListActivity。
这个Activity显示出来是一个对话框(AlterDialog)的样式,实现方法是在Mannifest.xml文件里声明这个Activity时定义它的主题为
随便一提的是menu里也用了sdk里的图片文件:
路径都是android-sdk\platforms\android-x\data\res\drawable-x里面的图片
DeviceListActivity:
这Activity里主要是ListView的显示,以及关于蓝牙搜索的一下方法。
在OnCreate方法里,先适配器的实例化,然后是ListView的实例化,并设置适配器已经注册Item点击监听器。监听器事件里处理了点击搜索到的设备的名字,并将其传回到BluetoothChat那个Activity里做后续处理:
在搜索设备中,还需要显示之前匹配过的设备,获取之前匹配过的语句:
关键是搜索的过程:
这里用到了接受者,处理搜索过程中的两个广播:BluetoothDevice.ACTION_FOUND 和 BluetoothDevice.ACTION_DISCOVERY_FINISHED
第一个广播是在搜索到一个设备后就发送一个广播,第二个是搜索完成后发送的广播。随便提一下BluetoothDevice类代表的是远程设备。
接受者代码:
在选择设备,通过关闭Activity返回数据后,BluetoothCaht里通过
然后调用BluetoothChatService实例的connect方法进行连接操作。这个方法实际是开启的ConnectThread线程,之前同样要对连接线程和通信线程进行停止。理由同AcceptThread。之后再改变状态为连接中STATE_CONNECTING。
BluetoothChatService:
这个类的话关键就是3个线程类:
AcceptThread:蓝牙服务端socket监听线程.:
ConnectThread:蓝牙socket连接线程:
ConnectedThread:连接后的通信线程:
另外就是在读别人发过来的数据的时候,由于别人发过来的是一个byte数组, 然后数组里面不是每个元素都是有效数据,所以要自己对数据进行String再构造处理
第一个参数是字节数组,第二个为偏移量,(内容是从第一个位置写入的),第三个参数是长度。
项目java文件3个:BluetoothChat:主界面,显示聊天信息BluetoothChatService:里面有3个主要线程类,AcceptThread:蓝牙服务端socket监听线程.。ConnectThread:蓝牙socket连接线程。 ConnectedThread:连接后的通信线程DeviceListActivity:蓝牙扫描选择界面,负责传回选择连接的设备BluetoothChat:定义了很多常量,用于处理消息和请求:
// 调试用的日志标志TAG与是否打印日志的标志D private static final String TAG = "BluetoothChat"; private static final boolean D = true; // 从BluetoothChatService传回来交给Handler处理的消息类型 public static final int MESSAGE_STATE_CHANGE = 1; // 蓝牙socket状态改变,居然有4个状态监听、正在连接、已连接、无。具体可以看Handler的handMessage方法 public static final int MESSAGE_READ = 2; // 这个消息类型被发送回来是因为蓝牙服务socket在读别的设备发来的内容 public static final int MESSAGE_WRITE = 3; // 这个消息类型被发送回来是因为蓝牙socket在写要发送的内容 public static final int MESSAGE_DEVICE_NAME = 4; // 这个消息发送回来是因为连接到了一个设备,并且获得了对方的名字,好像是手机的型号 public static final int MESSAGE_TOAST = 5; // 这个消息发送回来是有要用Toast控件广播的内容 // 从BluetoothChatService的发送回来消息内容里的键值 public static final String DEVICE_NAME = "device_name"; // 在发回设备名(MESSAGE_DEVICE_NAME)消息时,获取设备名时的键值 public static final String TOAST = "toast"; // 同样是键值,指向的是要Toast控件广播的内容 // Intent的请求值,在startActivityForResult时使用 private static final int REQUEST_CONNECT_DEVICE = 1; // 在要请求去搜索设备的时候使用到 private static final int REQUEST_ENABLE_BT = 2; // 在请求要使蓝牙可用的时候用到
控件不用理会,无非是一个显示连接到某个设备的TextView,一个用于显示对话的ListView,一个用于输入聊天内容的EditText,一个发送按钮Button
这里我学到了EditText注册了一个监听器,然后实现了软键盘按回车return键发送消息(一般我们在EditText里按return键是换行)
// 用于监听EditText的一个return键事件 private TextView.OnEditorActionListener mWriteListener = new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { // If the action is a key-up event on the return key, send the message if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) { String message = view.getText().toString(); sendMessage(message); } if(D) Log.i(TAG, "END onEditorAction"); return true; } };
然后是ListView从底下开始显示:需要XML里一句:
android:stackFromBottom="true"
然后我们就可以看到在onCreate方法里有获得BluetoothAdapter实例的语句:BluetoothAdapter代表的是一个本地蓝牙设备
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
在onStart方法里,有判断本地蓝牙是否可用的语句,不在的话想系统发送一个是否开启蓝牙的请求:
if (!mBluetoothAdapter.isEnabled()) { // 判断蓝牙是否可用 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); // 用于发送启动请求的Intent startActivityForResult(enableIntent, REQUEST_ENABLE_BT); // 这里会启动系统的一个Activity,然后也会根据REQUEST_ENABLE_BT在OnActivityResult方法里处理 } else { // 可用的情况下初始化界面和一些控件,比如ListView的适配器,BluetoothChatService实例 if (mChatService == null) setupChat(); }
在onResume方法里,对BluetoothChatService实例进行判断有无以及状态,如果是‘无’状态,即刚启动,活stop了,就调用其start方法。
start方法作用就是开启BluetoothChatService里的AcceptThread线程,进行蓝牙服务端socket监听,开启之前它会确保连接线程和通信线程是关闭的,不然就代码强制关闭,因为蓝牙通信基本是点对点的。并且设置状态为监听STATE_LISTEN。
此外,还有一个ensureDiscoverable方法,目的在于使本机蓝牙可见,对应于菜单键menu的第二个选项
private void ensureDiscoverable() { if(D) Log.d(TAG, "ensure discoverable"); if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { // 如果现在蓝牙是不可见的模式 Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); // 请求可见时间为300秒 startActivity(discoverableIntent); // 同样开始一个系统的Activity } }
菜单menu的第一个选项是扫描周围可用的蓝牙设备进行连接:这里使用了请求结果的开启Activity方法(startActivityForResult)开启了DeviceListActivity。
这个Activity显示出来是一个对话框(AlterDialog)的样式,实现方法是在Mannifest.xml文件里声明这个Activity时定义它的主题为
android:theme="@android:style/Theme.Dialog"这个是用了android-sdk\platforms\android-x\data\res\values\styles.xml 的样式
随便一提的是menu里也用了sdk里的图片文件:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/scan" android:icon="@android:drawable/ic_menu_search" // 这里 android:title="@string/connect" /> <item android:id="@+id/discoverable" android:icon="@android:drawable/ic_menu_mylocation" // 这里 android:title="@string/discoverable" /> </menu>
路径都是android-sdk\platforms\android-x\data\res\drawable-x里面的图片
DeviceListActivity:
这Activity里主要是ListView的显示,以及关于蓝牙搜索的一下方法。
在OnCreate方法里,先适配器的实例化,然后是ListView的实例化,并设置适配器已经注册Item点击监听器。监听器事件里处理了点击搜索到的设备的名字,并将其传回到BluetoothChat那个Activity里做后续处理:
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() { public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) { // Cancel discovery because it's costly and we're about to connect mBtAdapter.cancelDiscovery(); // 本地设备取消扫描 // 获取扫描到的设备的MAC地址,是随后的17个字符 String info = ((TextView) v).getText().toString(); String address = info.substring(info.length() - 17); // 建立包含MAC地址的Intent,用于传回BluetoothChat那个Activity Intent intent = new Intent(); intent.putExtra(EXTRA_DEVICE_ADDRESS, address); // 设置结果并关闭当前Activity setResult(Activity.RESULT_OK, intent); finish(); } };
在搜索设备中,还需要显示之前匹配过的设备,获取之前匹配过的语句:
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
关键是搜索的过程:
这里用到了接受者,处理搜索过程中的两个广播:BluetoothDevice.ACTION_FOUND 和 BluetoothDevice.ACTION_DISCOVERY_FINISHED
第一个广播是在搜索到一个设备后就发送一个广播,第二个是搜索完成后发送的广播。随便提一下BluetoothDevice类代表的是远程设备。
接受者代码:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // 当找到一个设备 if (BluetoothDevice.ACTION_FOUND.equals(action)) { // 从Intent里面获取一个BluetoothDevice对象 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // 如果它是已经匹配过的,那就不再新设备的那个列表显示了,因为在已经匹配过的列表已经有显示了 if (device.getBondState() != BluetoothDevice.BOND_BONDED) { mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } // 当扫描完成以后,改变Title,这个Activity是一个带圆形进度条的Activity } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { setProgressBarIndeterminateVisibility(false); // 关闭进度条 setTitle(R.string.select_device); if (mNewDevicesArrayAdapter.getCount() == 0) { // 如果扫描不到设备 String noDevices = getResources().getText(R.string.none_found).toString(); mNewDevicesArrayAdapter.add(noDevices); //显示无设备的字符串 } } } };
在选择设备,通过关闭Activity返回数据后,BluetoothCaht里通过
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);获得远程设备对象
然后调用BluetoothChatService实例的connect方法进行连接操作。这个方法实际是开启的ConnectThread线程,之前同样要对连接线程和通信线程进行停止。理由同AcceptThread。之后再改变状态为连接中STATE_CONNECTING。
BluetoothChatService:
这个类的话关键就是3个线程类:
AcceptThread:蓝牙服务端socket监听线程.:
private class AcceptThread extends Thread { // 本地的服务端socket private final BluetoothServerSocket mmServerSocket; public AcceptThread() { BluetoothServerSocket tmp = null; // 创建一个用于监听的服务端socket,通过下面这个方法,NAME参数没关系,MY_UUID是确定唯一通道的标示符,用于连接的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; // 不断的监听,直到状态被改变成已连接状态 while (mState != STATE_CONNECTED) { try { // 这是一个会产生阻塞的方法accept,要不就是成功地建立一个连接,要不就是返回一个异常 socket = mmServerSocket.accept(); } catch (IOException e) { Log.e(TAG, "accept() failed", e); break; } // 连接已经建立成功 if (socket != null) { synchronized (BluetoothChatService.this) { // 同步块,同一时间,只有一个线程可以访问该区域 switch (mState) { case STATE_LISTEN: case STATE_CONNECTING: // 状态正常,开始进行线程通信,实际就是开启通信线程ConnectedThread connected(socket, socket.getRemoteDevice()); break; case STATE_NONE: case STATE_CONNECTED: // 未准备或已连接状态. 关闭新建的这个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() { //关闭服务端的socket if (D) Log.d(TAG, "cancel " + this); try { mmServerSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of server failed", e); } } }
ConnectThread:蓝牙socket连接线程:
private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { mmDevice = device; BluetoothSocket tmp = null; // 通过远程设备以及唯一的UUID创建一个用于连接的socket 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"); // 一定要停止扫描,不然会减慢连接速度 mAdapter.cancelDiscovery(); // 连接到服务端的socket try { // connect方法也会造成阻塞,直到成功连接,或返回一个异常 mmSocket.connect(); } catch (IOException e) { connectionFailed(); //连接失败发送要Toast的消息 // 关闭socket try { mmSocket.close(); } catch (IOException e2) { Log.e(TAG, "unable to close() socket during connection failure", e2); } // 连接失败了,把软件变成监听模式,可以让别的设备来连接 BluetoothChatService.this.start(); return; } // 重置连接线程,因为我们已经完成了 synchronized (BluetoothChatService.this) { mConnectThread = null; } // 开始进行线程通信,实际就是开启通信线程ConnectedThread connected(mmSocket, mmDevice); } public void cancel() { // 关闭连接用的socket try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } }
ConnectedThread:连接后的通信线程:
private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { Log.d(TAG, "create ConnectedThread"); mmSocket = socket; // 这个是之前的用于连接的socket InputStream tmpIn = null; OutputStream tmpOut = null; // 从连接的socket里获取InputStream和OutputStream try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e(TAG, "temp sockets not created", e); } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { Log.i(TAG, "BEGIN mConnectedThread"); byte[] buffer = new byte[1024]; int bytes; // 已经连接上以后持续从通道中监听输入流的情况 while (true) { try { // 从通道的输入流InputStream中读取数据到buffer数组中 bytes = mmInStream.read(buffer); // 将获取到数据的消息发送到UI界面,同时也把内容buffer发过去显示 mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer) .sendToTarget(); } catch (IOException e) { Log.e(TAG, "disconnected", e); connectionLost(); // 连接异常断开的时候发送一个需要Toast的消息,让软件进行Toast break; } } } /** * Write to the connected OutStream. * @param buffer The bytes to write */ public void write(byte[] buffer) { // 这个方法用于把发送内容写到通道的OutputStream中,会在发信息是被调用 try { mmOutStream.write(buffer); //将buffer内容写进通道 // 用于将自己发送给对方的内容也在UI界面显示 mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer) .sendToTarget(); } catch (IOException e) { Log.e(TAG, "Exception during write", e); } } public void cancel() { //关闭socket,即关闭通道 try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect socket failed", e); } } }
另外就是在读别人发过来的数据的时候,由于别人发过来的是一个byte数组, 然后数组里面不是每个元素都是有效数据,所以要自己对数据进行String再构造处理
String readMessage = new String(readBuf, 0, msg.arg1);
第一个参数是字节数组,第二个为偏移量,(内容是从第一个位置写入的),第三个参数是长度。
相关文章推荐
- Android 通过samples\android-x\BluetoothChat学习蓝牙操作
- 通过BluetoothChat学习蓝牙操作
- Android 蓝牙编程学习一基本操作
- Android通过JNI实现与C语言的串口通讯操作蓝牙硬件模块
- Android学习之蓝牙操作
- Android 蓝牙编程学习一基本操作
- Android学习---通过内容提供者(ContentProvider)操作另外一个应用私有数据库的内容
- Android4.2蓝牙学习环境搭建
- Android(java)学习笔记108:通过反射获取私有构造方法并且使用
- 在 android 系统上通过蓝牙获取通讯录
- Android 通过蓝牙采集音频
- Android – 学习操作NFC – 2
- Android 蓝牙模块基础操作
- XE5 ANDROID通过webservice访问操作MSSQL数据库
- Android BLE学习(二): Android与51822蓝牙模块通信流程的实现与分析
- android学习之通过handler更新UI的例子
- Android基于XMPP Smack Openfire下学习开发IM(二)对分组、好友和头像等一些操作
- Android学习之——操作SIM卡联系人
- Android驱动(一)硬件访问服务学习之(一)Android通过JNI访问硬件
- Android 蓝牙开发基础操作