bluetoothchat的学习

来源:互联网 发布:js创建tr 编辑:程序博客网 时间:2024/05/29 19:35

项目java文件3个:BluetoothChat:主界面,显示聊天信息BluetoothChatService:里面有3个主要线程类,AcceptThread:蓝牙服务端socket监听线程.。ConnectThread:蓝牙socket连接线程。 ConnectedThread:连接后的通信线程DeviceListActivity:蓝牙扫描选择界面,负责传回选择连接的设备BluetoothChat:定义了很多常量,用于处理消息和请求:

[java] view plaincopy
  1. // 调试用的日志标志TAG与是否打印日志的标志D  
  2. private static final String TAG = "BluetoothChat";  
  3. private static final boolean D = true;  
  4.   
  5. // 从BluetoothChatService传回来交给Handler处理的消息类型  
  6. public static final int MESSAGE_STATE_CHANGE = 1// 蓝牙socket状态改变,居然有4个状态监听、正在连接、已连接、无。具体可以看Handler的handMessage方法   
  7. public static final int MESSAGE_READ = 2// 这个消息类型被发送回来是因为蓝牙服务socket在读别的设备发来的内容  
  8. public static final int MESSAGE_WRITE = 3// 这个消息类型被发送回来是因为蓝牙socket在写要发送的内容  
  9. public static final int MESSAGE_DEVICE_NAME = 4// 这个消息发送回来是因为连接到了一个设备,并且获得了对方的名字,好像是手机的型号  
  10. public static final int MESSAGE_TOAST = 5// 这个消息发送回来是有要用Toast控件广播的内容  
  11.   
  12. // 从BluetoothChatService的发送回来消息内容里的键值  
  13. public static final String DEVICE_NAME = "device_name"// 在发回设备名(MESSAGE_DEVICE_NAME)消息时,获取设备名时的键值  
  14. public static final String TOAST = "toast"// 同样是键值,指向的是要Toast控件广播的内容   
  15.   
  16. // Intent的请求值,在startActivityForResult时使用  
  17. private static final int REQUEST_CONNECT_DEVICE = 1// 在要请求去搜索设备的时候使用到  
  18. private static final int REQUEST_ENABLE_BT = 2// 在请求要使蓝牙可用的时候用到  

控件不用理会,无非是一个显示连接到某个设备的TextView,一个用于显示对话的ListView,一个用于输入聊天内容的EditText,一个发送按钮Button

这里我学到了EditText注册了一个监听器,然后实现了软键盘按回车return键发送消息(一般我们在EditText里按return键是换行)

[java] view plaincopy
  1. // 用于监听EditText的一个return键事件  
  2. private TextView.OnEditorActionListener mWriteListener =  
  3.     new TextView.OnEditorActionListener() {  
  4.     public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {  
  5.         // If the action is a key-up event on the return key, send the message  
  6.         if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {  
  7.             String message = view.getText().toString();  
  8.             sendMessage(message);  
  9.         }  
  10.         if(D) Log.i(TAG, "END onEditorAction");  
  11.         return true;  
  12.     }  
  13. };  

然后是ListView从底下开始显示:需要XML里一句:
[html] view plaincopy
  1. android:stackFromBottom="true"  

然后我们就可以看到在onCreate方法里有获得BluetoothAdapter实例的语句:BluetoothAdapter代表的是一个本地蓝牙设备

[java] view plaincopy
  1. mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();  

在onStart方法里,有判断本地蓝牙是否可用的语句,不在的话想系统发送一个是否开启蓝牙的请求:

[java] view plaincopy
  1.         if (!mBluetoothAdapter.isEnabled()) { // 判断蓝牙是否可用<span style="white-space:pre"> </span>  
  2.             Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); // 用于发送启动请求的Intent  
  3.             startActivityForResult(enableIntent, REQUEST_ENABLE_BT);   
  4.             // 这里会启动系统的一个Activity,然后也会根据REQUEST_ENABLE_BT在OnActivityResult方法里处理  
  5.         } else { // 可用的情况下初始化界面和一些控件,比如ListView的适配器,BluetoothChatService实例  
  6.             if (mChatService == null) setupChat();  
  7.         }  

在onResume方法里,对BluetoothChatService实例进行判断有无以及状态,如果是‘无’状态,即刚启动,活stop了,就调用其start方法。

start方法作用就是开启BluetoothChatService里的AcceptThread线程,进行蓝牙服务端socket监听,开启之前它会确保连接线程和通信线程是关闭的,不然就代码强制关闭,因为蓝牙通信基本是点对点的。并且设置状态为监听STATE_LISTEN。

此外,还有一个ensureDiscoverable方法,目的在于使本机蓝牙可见,对应于菜单键menu的第二个选项

[java] view plaincopy
  1. private void ensureDiscoverable() {  
  2.     if(D) Log.d(TAG, "ensure discoverable");  
  3.     if (mBluetoothAdapter.getScanMode() !=  
  4.         BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { // 如果现在蓝牙是不可见的模式  
  5.         Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);  
  6.         discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); // 请求可见时间为300秒  
  7.         startActivity(discoverableIntent); // 同样开始一个系统的Activity  
  8.     }  
  9. }  

菜单menu的第一个选项是扫描周围可用的蓝牙设备进行连接:这里使用了请求结果的开启Activity方法(startActivityForResult)开启了DeviceListActivity。

这个Activity显示出来是一个对话框(AlterDialog)的样式,实现方法是在Mannifest.xml文件里声明这个Activity时定义它的主题为

[java] view plaincopy
  1. android:theme="@android:style/Theme.Dialog"  
这个是用了android-sdk\platforms\android-x\data\res\values\styles.xml 的样式

随便一提的是menu里也用了sdk里的图片文件:

[html] view plaincopy
  1. <menu xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <item android:id="@+id/scan"  
  3.           android:icon="@android:drawable/ic_menu_search" // 这里  
  4.           android:title="@string/connect" />  
  5.     <item android:id="@+id/discoverable"  
  6.           android:icon="@android:drawable/ic_menu_mylocation" // 这里  
  7.           android:title="@string/discoverable" />  
  8. </menu>  

路径都是android-sdk\platforms\android-x\data\res\drawable-x里面的图片



DeviceListActivity:

这Activity里主要是ListView的显示,以及关于蓝牙搜索的一下方法。

在OnCreate方法里,先适配器的实例化,然后是ListView的实例化,并设置适配器已经注册Item点击监听器。监听器事件里处理了点击搜索到的设备的名字,并将其传回到BluetoothChat那个Activity里做后续处理:

[java] view plaincopy
  1. private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {  
  2.     public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {  
  3.         // Cancel discovery because it's costly and we're about to connect  
  4.         mBtAdapter.cancelDiscovery(); // 本地设备取消扫描  
  5.   
  6.         // 获取扫描到的设备的MAC地址,是随后的17个字符  
  7.         String info = ((TextView) v).getText().toString();  
  8.         String address = info.substring(info.length() - 17);  
  9.   
  10.         // 建立包含MAC地址的Intent,用于传回BluetoothChat那个Activity  
  11.         Intent intent = new Intent();  
  12.         intent.putExtra(EXTRA_DEVICE_ADDRESS, address);  
  13.   
  14.         // 设置结果并关闭当前Activity  
  15.         setResult(Activity.RESULT_OK, intent);  
  16.         finish();  
  17.     }  
  18. };  

在搜索设备中,还需要显示之前匹配过的设备,获取之前匹配过的语句:

[java] view plaincopy
  1. Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();  

关键是搜索的过程:

这里用到了接受者,处理搜索过程中的两个广播:BluetoothDevice.ACTION_FOUND 和 BluetoothDevice.ACTION_DISCOVERY_FINISHED

第一个广播是在搜索到一个设备后就发送一个广播,第二个是搜索完成后发送的广播。随便提一下BluetoothDevice类代表的是远程设备。

接受者代码:

[java] view plaincopy
  1. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {  
  2.     @Override  
  3.     public void onReceive(Context context, Intent intent) {  
  4.         String action = intent.getAction();  
  5.   
  6.         // 当找到一个设备  
  7.         if (BluetoothDevice.ACTION_FOUND.equals(action)) {  
  8.             // 从Intent里面获取一个BluetoothDevice对象  
  9.             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);  
  10.             // 如果它是已经匹配过的,那就不再新设备的那个列表显示了,因为在已经匹配过的列表已经有显示了  
  11.             if (device.getBondState() != BluetoothDevice.BOND_BONDED) {  
  12.                 mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());  
  13.             }  
  14.         // 当扫描完成以后,改变Title,这个Activity是一个带圆形进度条的Activity  
  15.         } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {  
  16.             setProgressBarIndeterminateVisibility(false); // 关闭进度条  
  17.             setTitle(R.string.select_device);  
  18.             if (mNewDevicesArrayAdapter.getCount() == 0) { // 如果扫描不到设备  
  19.                 String noDevices = getResources().getText(R.string.none_found).toString();  
  20.                 mNewDevicesArrayAdapter.add(noDevices); //显示无设备的字符串  
  21.             }  
  22.         }  
  23.     }  
  24. };  

在选择设备,通过关闭Activity返回数据后,BluetoothCaht里通过

[java] view plaincopy
  1. BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);  
获得远程设备对象

然后调用BluetoothChatService实例的connect方法进行连接操作。这个方法实际是开启的ConnectThread线程,之前同样要对连接线程和通信线程进行停止。理由同AcceptThread。之后再改变状态为连接中STATE_CONNECTING。


BluetoothChatService:

这个类的话关键就是3个线程类:

AcceptThread:蓝牙服务端socket监听线程.:

[java] view plaincopy
  1. private class AcceptThread extends Thread {  
  2.     // 本地的服务端socket  
  3.     private final BluetoothServerSocket mmServerSocket;  
  4.   
  5.     public AcceptThread() {  
  6.         BluetoothServerSocket tmp = null;  
  7.   
  8.         // 创建一个用于监听的服务端socket,通过下面这个方法,NAME参数没关系,MY_UUID是确定唯一通道的标示符,用于连接的socket也要通过它产生  
  9.         try {  
  10.             tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);  
  11.         } catch (IOException e) {  
  12.             Log.e(TAG, "listen() failed", e);  
  13.         }  
  14.         mmServerSocket = tmp;  
  15.     }  
  16.   
  17.     public void run() {  
  18.         if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);  
  19.         setName("AcceptThread");  
  20.         BluetoothSocket socket = null;  
  21.   
  22.         // 不断的监听,直到状态被改变成已连接状态  
  23.         while (mState != STATE_CONNECTED) {  
  24.             try {  
  25.                 // 这是一个会产生阻塞的方法accept,要不就是成功地建立一个连接,要不就是返回一个异常  
  26.                 socket = mmServerSocket.accept();  
  27.             } catch (IOException e) {  
  28.                 Log.e(TAG, "accept() failed", e);  
  29.                 break;  
  30.             }  
  31.   
  32.             // 连接已经建立成功  
  33.             if (socket != null) {  
  34.                 synchronized (BluetoothChatService.this) { // 同步块,同一时间,只有一个线程可以访问该区域  
  35.                     switch (mState) {  
  36.                     case STATE_LISTEN:  
  37.                     case STATE_CONNECTING:  
  38.                         // 状态正常,开始进行线程通信,实际就是开启通信线程ConnectedThread  
  39.                         connected(socket, socket.getRemoteDevice());  
  40.                         break;  
  41.                     case STATE_NONE:  
  42.                     case STATE_CONNECTED:  
  43.                         // 未准备或已连接状态. 关闭新建的这个socket.  
  44.                         try {  
  45.                             socket.close();  
  46.                         } catch (IOException e) {  
  47.                             Log.e(TAG, "Could not close unwanted socket", e);  
  48.                         }  
  49.                         break;  
  50.                     }  
  51.                 }  
  52.             }  
  53.         }  
  54.         if (D) Log.i(TAG, "END mAcceptThread");  
  55.     }  
  56.   
  57.     public void cancel() { //关闭服务端的socket  
  58.         if (D) Log.d(TAG, "cancel " + this);  
  59.         try {  
  60.             mmServerSocket.close();  
  61.         } catch (IOException e) {  
  62.             Log.e(TAG, "close() of server failed", e);  
  63.         }  
  64.     }  
  65. }  

ConnectThread:蓝牙socket连接线程:

[java] view plaincopy
  1. private class ConnectThread extends Thread {  
  2.     private final BluetoothSocket mmSocket;  
  3.     private final BluetoothDevice mmDevice;  
  4.   
  5.     public ConnectThread(BluetoothDevice device) {  
  6.         mmDevice = device;  
  7.         BluetoothSocket tmp = null;  
  8.   
  9.         // 通过远程设备以及唯一的UUID创建一个用于连接的socket  
  10.         try {  
  11.             tmp = device.createRfcommSocketToServiceRecord(MY_UUID);  
  12.         } catch (IOException e) {  
  13.             Log.e(TAG, "create() failed", e);  
  14.         }  
  15.         mmSocket = tmp;  
  16.     }  
  17.   
  18.     public void run() {  
  19.         Log.i(TAG, "BEGIN mConnectThread");  
  20.         setName("ConnectThread");  
  21.   
  22.         // 一定要停止扫描,不然会减慢连接速度  
  23.         mAdapter.cancelDiscovery();  
  24.   
  25.         // 连接到服务端的socket  
  26.         try {  
  27.             // connect方法也会造成阻塞,直到成功连接,或返回一个异常  
  28.             mmSocket.connect();  
  29.         } catch (IOException e) {  
  30.             connectionFailed(); //连接失败发送要Toast的消息  
  31.             // 关闭socket  
  32.             try {  
  33.                 mmSocket.close();  
  34.             } catch (IOException e2) {  
  35.                 Log.e(TAG, "unable to close() socket during connection failure", e2);  
  36.             }  
  37.             // 连接失败了,把软件变成监听模式,可以让别的设备来连接  
  38.             BluetoothChatService.this.start();  
  39.             return;  
  40.         }  
  41.   
  42.         // 重置连接线程,因为我们已经完成了  
  43.         synchronized (BluetoothChatService.this) {  
  44.             mConnectThread = null;  
  45.         }  
  46.   
  47.         // 开始进行线程通信,实际就是开启通信线程ConnectedThread  
  48.         connected(mmSocket, mmDevice);  
  49.     }  
  50.   
  51.     public void cancel() { // 关闭连接用的socket  
  52.         try {  
  53.             mmSocket.close();  
  54.         } catch (IOException e) {  
  55.             Log.e(TAG, "close() of connect socket failed", e);  
  56.         }  
  57.     }  
  58. }  

ConnectedThread:连接后的通信线程:

[java] view plaincopy
  1. private class ConnectedThread extends Thread {  
  2.     private final BluetoothSocket mmSocket;  
  3.     private final InputStream mmInStream;  
  4.     private final OutputStream mmOutStream;  
  5.   
  6.     public ConnectedThread(BluetoothSocket socket) {  
  7.         Log.d(TAG, "create ConnectedThread");  
  8.         mmSocket = socket; // 这个是之前的用于连接的socket  
  9.         InputStream tmpIn = null;  
  10.         OutputStream tmpOut = null;  
  11.   
  12.         // 从连接的socket里获取InputStream和OutputStream  
  13.         try {  
  14.             tmpIn = socket.getInputStream();  
  15.             tmpOut = socket.getOutputStream();  
  16.         } catch (IOException e) {  
  17.             Log.e(TAG, "temp sockets not created", e);  
  18.         }  
  19.   
  20.         mmInStream = tmpIn;  
  21.         mmOutStream = tmpOut;  
  22.     }  
  23.   
  24.     public void run() {  
  25.         Log.i(TAG, "BEGIN mConnectedThread");  
  26.         byte[] buffer = new byte[1024];  
  27.         int bytes;  
  28.   
  29.         // 已经连接上以后持续从通道中监听输入流的情况  
  30.         while (true) {  
  31.             try {  
  32.                 // 从通道的输入流InputStream中读取数据到buffer数组中  
  33.                 bytes = mmInStream.read(buffer);  
  34.   
  35.                 // 将获取到数据的消息发送到UI界面,同时也把内容buffer发过去显示  
  36.                 mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)  
  37.                         .sendToTarget();  
  38.             } catch (IOException e) {  
  39.                 Log.e(TAG, "disconnected", e);  
  40.                 connectionLost(); // 连接异常断开的时候发送一个需要Toast的消息,让软件进行Toast  
  41.                 break;  
  42.             }  
  43.         }  
  44.     }  
  45.   
  46.     /** 
  47.      * Write to the connected OutStream. 
  48.      * @param buffer  The bytes to write 
  49.      */  
  50.     public void write(byte[] buffer) { // 这个方法用于把发送内容写到通道的OutputStream中,会在发信息是被调用  
  51.         try {  
  52.             mmOutStream.write(buffer); //将buffer内容写进通道  
  53.   
  54.             // 用于将自己发送给对方的内容也在UI界面显示  
  55.             mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)  
  56.                     .sendToTarget();  
  57.         } catch (IOException e) {  
  58.             Log.e(TAG, "Exception during write", e);  
  59.         }  
  60.     }  
  61.   
  62.     public void cancel() { //关闭socket,即关闭通道  
  63.         try {  
  64.             mmSocket.close();  
  65.         } catch (IOException e) {  
  66.             Log.e(TAG, "close() of connect socket failed", e);  
  67.         }  
  68.     }  
  69. }  

另外就是在读别人发过来的数据的时候,由于别人发过来的是一个byte数组, 然后数组里面不是每个元素都是有效数据,所以要自己对数据进行String再构造处理

[java] view plaincopy
  1. String readMessage = new String(readBuf, 0, msg.arg1);  

第一个参数是字节数组,第二个为偏移量,(内容是从第一个位置写入的),第三个参数是长度。

转自http://blog.csdn.net/wyzxk888/article/details/7543364

0 0