欢迎转载,但请注明出处!谢谢。http://www.cnblogs.com/weizhxa/p/5792775.html
最近公司在做一个蓝牙串口通讯的App,有一个固定的蓝牙设备,需要实现手机连接相互交换数据。以前没怎么做过蓝牙开发,故查看Android App Guide的蓝牙篇,发现有个chat示例,故此做了点研究。在研究的基础上进行了此App的实现。
1、App特点:
1.1 App中同时存在服务器与客户端,任意手机可以作为服务器或者客户端;
1.2 客户端可以进行蓝牙环境扫描;
1.3 诸多异常处理……均未做,O(∩_∩)O。demo了,主要学习的是蓝牙技术嘛。
2、实现过程中的总结:
2.1 蓝牙串口通讯,谷歌给出了一个固定UUID: 00001101-0000-1000-8000-00805F9B34FB,大多数蓝牙串口设备使用此UUID作为连接用UUID,此UUID在BluetoothDevice的createRfcommSocketToServiceRecord方法中有提到。具体可以看api doc。
3、具体实现代码:
3.1 客户端服务器选择页:MainActivity。此页仅进行客户端与服务器端选择使用。
3.1.1 Activity:
1 package org.fiu.bluetoothdemos; 2 3 import org.fiu.bluetoothchatdemos.R; 4 5 import android.content.Intent; 6 import android.os.Bundle; 7 import android.support.v7.app.ActionBarActivity; 8 import android.view.Menu; 9 import android.view.MenuItem; 10 import android.view.View; 11 12 public class MainActivity extends ActionBarActivity { 13 14 @Override 15 protected void onCreate(Bundle savedInstanceState) { 16 super.onCreate(savedInstanceState); 17 setContentView(R.layout.activity_main); 18 } 19 20 /** 21 * 建立服务器 22 * 23 * @param view 24 */ 25 public void btn_server(View view) { 26 startActivity(new Intent(this, ServerActivity.class)); 27 } 28 29 /** 30 * 建立客户端 31 * 32 * @param view 33 */ 34 public void btn_client(View view) { 35 startActivity(new Intent(this, ClientActivity.class)); 36 } 37 38 @Override 39 public boolean onCreateOptionsMenu(Menu menu) { 40 // Inflate the menu; this adds items to the action bar if it is present. 41 getMenuInflater().inflate(R.menu.main, menu); 42 return true; 43 } 44 45 @Override 46 public boolean onOptionsItemSelected(MenuItem item) { 47 // Handle action bar item clicks here. The action bar will 48 // automatically handle clicks on the Home/Up button, so long 49 // as you specify a parent activity in AndroidManifest.xml. 50 int id = item.getItemId(); 51 if (id == R.id.action_settings) { 52 return true; 53 } 54 return super.onOptionsItemSelected(item); 55 } 56 }
3.1.2 layout:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" 10 tools:context="org.fiu.bluetoothdemos.MainActivity" > 11 12 <Button 13 android:layout_width="match_parent" 14 android:layout_height="wrap_content" 15 android:onClick="btn_server" 16 android:text="开启服务器" /> 17 18 <Button 19 android:layout_width="match_parent" 20 android:layout_height="wrap_content" 21 android:onClick="btn_client" 22 android:text="开启客户端" /> 23 24 </LinearLayout>
3.2 服务器端代码:
3.2.1 服务器页面:点击进入服务器页面后,直接使用上面所说UUID进行服务器建立,等待客户端连接工作。拥有一个消息显示框和发送EditText,可以与客户端进行交互。内部拥有一个BluetoothServer管理类,这个类主要管理了服务器的蓝牙操作。
1 package org.fiu.bluetoothdemos; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Timer; 6 import java.util.TimerTask; 7 8 import org.fiu.bluetoothchatdemos.R; 9 10 import android.app.Activity; 11 import android.os.Bundle; 12 import android.view.View; 13 import android.view.ViewGroup; 14 import android.widget.AbsListView; 15 import android.widget.BaseAdapter; 16 import android.widget.EditText; 17 import android.widget.ListView; 18 import android.widget.TextView; 19 20 /** 21 * 服务器 22 * 23 * @author c 24 * 25 */ 26 public class ServerActivity extends Activity { 27 private EditText et_msg; 28 private BluetoothServer server; 29 private List<String> msgs = new ArrayList<String>(); 30 private TimerTask task = new TimerTask() { 31 32 @Override 33 public void run() { 34 synchronized (msgs) { 35 msgs = server.getMsgs(); 36 } 37 runOnUiThread(new Runnable() { 38 public void run() { 39 msgAdapter.notifyDataSetChanged(); 40 } 41 }); 42 } 43 44 }; 45 private MyAdapter msgAdapter; 46 47 @Override 48 protected void onCreate(Bundle savedInstanceState) { 49 super.onCreate(savedInstanceState); 50 setContentView(R.layout.activity_chat); 51 52 et_msg = (EditText) findViewById(R.id.et_msg); 53 server = new BluetoothServer(this); 54 server.start(); 55 56 ListView lv_msg = (ListView) findViewById(R.id.lv_msg); 57 msgAdapter = new MyAdapter(); 58 lv_msg.setAdapter(msgAdapter); 59 60 Timer timer = new Timer(); 61 timer.schedule(task, 0, 1000); 62 } 63 64 public class MyAdapter extends BaseAdapter { 65 66 @Override 67 public int getCount() { 68 // TODO Auto-generated method stub 69 return msgs.size(); 70 } 71 72 @Override 73 public Object getItem(int position) { 74 return msgs.get(position); 75 } 76 77 @Override 78 public long getItemId(int position) { 79 // TODO Auto-generated method stub 80 return position; 81 } 82 83 @Override 84 public View getView(int position, View convertView, ViewGroup parent) { 85 TextView tv = null; 86 if (convertView != null) { 87 tv = (TextView) convertView; 88 } else { 89 tv = new TextView(ServerActivity.this); 90 AbsListView.LayoutParams params = new AbsListView.LayoutParams( 91 AbsListView.LayoutParams.MATCH_PARENT, 92 AbsListView.LayoutParams.WRAP_CONTENT); 93 tv.setLayoutParams(params); 94 } 95 tv.setTag(msgs.get(position)); 96 tv.setText(msgs.get(position)); 97 return tv; 98 } 99 100 } 101 102 /** 103 * 发送 104 * 105 * @param view 106 */ 107 public void btn_send(View view) { 108 String msg = et_msg.getText().toString().trim(); 109 send(msg); 110 } 111 112 /** 113 * 发送消息到客户端 114 * 115 * @param msg 116 */ 117 private void send(String msg) { 118 server.send(msg); 119 synchronized (msgs) { 120 msgs.add("服务器发送:" + msg); 121 } 122 } 123 }
3.2.2 服务器页面layout
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:layout_margin="5dp" 6 android:orientation="vertical" > 7 8 <LinearLayout 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:layout_marginBottom="5dp" 12 android:orientation="horizontal" > 13 14 <EditText 15 android:id="@+id/et_msg" 16 android:layout_width="0dp" 17 android:layout_height="wrap_content" 18 android:layout_weight="3" 19 android:hint="chatMessage" /> 20 21 <Button 22 android:layout_width="20dp" 23 android:layout_height="wrap_content" 24 android:layout_weight="1" 25 android:onClick="btn_send" 26 android:text="send" /> 27 </LinearLayout> 28 29 <ListView 30 android:id="@+id/lv_msg" 31 android:layout_width="match_parent" 32 android:layout_height="match_parent" > 33 </ListView> 34 35 </LinearLayout>
3.2.3 服务器蓝牙管理类
1 package org.fiu.bluetoothdemos; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.util.ArrayList; 6 import java.util.List; 7 import java.util.UUID; 8 9 import android.bluetooth.BluetoothAdapter; 10 import android.bluetooth.BluetoothServerSocket; 11 import android.bluetooth.BluetoothSocket; 12 import android.content.Context; 13 import android.os.Handler; 14 import android.os.Looper; 15 import android.os.Message; 16 import android.util.Log; 17 import android.widget.Toast; 18 19 /** 20 * 蓝牙服务器 21 * 22 * @author weizh 23 * 24 */ 25 public class BluetoothServer { 26 /** 27 * 消息集合 28 */ 29 private List<String> listMsg = new ArrayList<String>(); 30 /** 31 * 是否工作中 32 */ 33 private boolean isWorking = false; 34 /** 35 * bluetooth name 36 */ 37 private String name = "FIUBluetoothServer"; 38 /** 39 * spp well-known UUID 40 */ 41 public static final UUID MY_UUID = UUID 42 .fromString("00001101-0000-1000-8000-00805F9B34FB"); 43 private static final String TAG = "BluetoothServer"; 44 /** 45 * 蓝牙服务器socket 46 */ 47 private BluetoothServerSocket bluetoothServerSocket; 48 /** 49 * 客户端socket 50 */ 51 private BluetoothSocket mClientSocket; 52 53 Context context; 54 55 public BluetoothServer(Context context) { 56 this.context = context; 57 } 58 59 /** 60 * 开启服务器 61 */ 62 public void start() { 63 listen(); 64 } 65 66 /** 67 * 开始监听 68 */ 69 private void listen() { 70 new Thread(new Runnable() { 71 72 @Override 73 public void run() { 74 // TODO Auto-generated method stub 75 // 判断是否有蓝牙设备 76 if (!BluetoothUtils.checkBluetoothExists()) { 77 throw new RuntimeException("bluetooth module not exists."); 78 } 79 // 打开设备 80 if (!BluetoothUtils.openBluetoothDevice()) { 81 return; 82 } 83 try { 84 if (bluetoothServerSocket == null) { 85 bluetoothServerSocket = BluetoothAdapter 86 .getDefaultAdapter() 87 .listenUsingRfcommWithServiceRecord(name, 88 MY_UUID); 89 } 90 isWorking = true; 91 while (isWorking) { 92 mClientSocket = bluetoothServerSocket.accept(); 93 Log.i(TAG, "客户端已连接:" 94 + mClientSocket.getRemoteDevice().getName()); 95 myHandler.sendEmptyMessage(0x01); 96 new ClientWorkingThread(mClientSocket).start(); 97 } 98 } catch (IOException e) { 99 // TODO Auto-generated catch block 100 e.printStackTrace(); 101 } 102 } 103 }).start(); 104 105 } 106 107 private Handler myHandler = new Handler() { 108 109 @Override 110 public void handleMessage(Message msg) { 111 super.handleMessage(msg); 112 Toast.makeText(context, 113 "客户端已连接:" + mClientSocket.getRemoteDevice().getName(), 0) 114 .show(); 115 } 116 117 }; 118 119 /** 120 * 停止 121 */ 122 public void stop() { 123 isWorking = false; 124 if (bluetoothServerSocket != null) { 125 try { 126 bluetoothServerSocket.close(); 127 } catch (IOException e) { 128 // TODO Auto-generated catch block 129 e.printStackTrace(); 130 } finally { 131 bluetoothServerSocket = null; 132 } 133 } 134 if (mClientSocket != null) { 135 try { 136 mClientSocket.close(); 137 } catch (IOException e) { 138 // TODO Auto-generated catch block 139 e.printStackTrace(); 140 } finally { 141 mClientSocket = null; 142 } 143 } 144 } 145 146 /** 147 * 客户端socket工作类 148 * 149 * @author weizh 150 * 151 */ 152 private class ClientWorkingThread extends Thread { 153 /** 154 * 客户端socket 155 */ 156 private BluetoothSocket mClientSocket; 157 158 public ClientWorkingThread(BluetoothSocket clientSocket) { 159 this.mClientSocket = clientSocket; 160 } 161 162 @Override 163 public void run() { 164 try { 165 InputStream inputStream = mClientSocket.getInputStream();// 输入流 166 // 从输入流中取出数据,插入消息条中 167 byte[] buffer = new byte[1024]; 168 while (isWorking) { 169 int read = inputStream.read(buffer); 170 if (read != -1) { 171 // 有内容 172 // 判断是否取得的消息填充满了buffer,未到字符串结尾符;如果不是,证明读取到了一条信息,并且信息是完整的,这个完整的前提是不能粘包,不粘包可以使用flush进行处理。 173 StringBuilder sb = new StringBuilder(); 174 if (read < buffer.length) { 175 String msg = new String(buffer, 0, read); 176 sb.append(msg); 177 } else { 178 byte[] tempBytes = new byte[1024 * 4]; 179 while (read == buffer.length 180 && buffer[read - 1] != 0x7f) { 181 read = inputStream.read(buffer); 182 } 183 String msg = new String(buffer, 0, read); 184 sb.append(msg); 185 } 186 Log.i(TAG, "服务器收到:" + sb.toString()); 187 synchronized (listMsg) { 188 listMsg.add("客户端发送:" + sb.toString()); 189 } 190 } 191 // try { 192 // Thread.sleep(300); 193 // } catch (InterruptedException e) { 194 // // TODO Auto-generated catch block 195 // e.printStackTrace(); 196 // } 197 } 198 } catch (IOException e) { 199 // TODO Auto-generated catch block 200 e.printStackTrace(); 201 } 202 // 工作完毕,关闭socket 203 try { 204 mClientSocket.close(); 205 } catch (IOException e) { 206 // TODO Auto-generated catch block 207 e.printStackTrace(); 208 } 209 210 } 211 } 212 213 /** 214 * 返回listMsg 215 * 216 * @return 217 */ 218 public List<String> getMsgs() { 219 synchronized (listMsg) { 220 return listMsg; 221 } 222 } 223 224 /** 225 * 发送消息 226 * 227 * @param msg 228 */ 229 public void send(String msg) { 230 if (mClientSocket != null) { 231 try { 232 mClientSocket.getOutputStream().write(msg.getBytes()); 233 mClientSocket.getOutputStream().flush(); 234 } catch (IOException e) { 235 // TODO Auto-generated catch block 236 e.printStackTrace(); 237 } 238 } 239 } 240 }
3.3 客户端页面:客户端除了具有消息框和发送消息框外,加载后首先可以进行蓝牙环境扫描,并且和选择。demo中偷了懒,没在选择后停止蓝牙扫描工作等诸多小细节……
3.3.1 客户端页面代码:
1 package org.fiu.bluetoothdemos; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Timer; 6 import java.util.TimerTask; 7 8 import org.fiu.bluetoothchatdemos.R; 9 import org.fiu.bluetoothdemos.ServerActivity.MyAdapter; 10 11 import android.app.Activity; 12 import android.app.AlertDialog; 13 import android.bluetooth.BluetoothAdapter; 14 import android.bluetooth.BluetoothDevice; 15 import android.content.BroadcastReceiver; 16 import android.content.Context; 17 import android.content.Intent; 18 import android.content.IntentFilter; 19 import android.os.Bundle; 20 import android.util.Log; 21 import android.view.LayoutInflater; 22 import android.view.View; 23 import android.view.View.OnClickListener; 24 import android.view.ViewGroup; 25 import android.view.ViewGroup.LayoutParams; 26 import android.widget.AbsListView; 27 import android.widget.AbsoluteLayout; 28 import android.widget.AdapterView; 29 import android.widget.AdapterView.OnItemClickListener; 30 import android.widget.ArrayAdapter; 31 import android.widget.BaseAdapter; 32 import android.widget.EditText; 33 import android.widget.LinearLayout; 34 import android.widget.ListView; 35 import android.widget.TextView; 36 37 /** 38 * 客户端 39 * 40 * @author c 41 * 42 */ 43 public class ClientActivity extends Activity { 44 /** 45 * 被发现的设备 46 */ 47 private List<BluetoothDevice> discoverDevices = new ArrayList<BluetoothDevice>(); 48 /** 49 * 蓝牙客户端 50 */ 51 private BluetoothClient bluetoothClient; 52 /** 53 * tag 54 */ 55 public final String TAG = "ClientActivity"; 56 /** 57 * 搜索对话框 58 */ 59 private AlertDialog dlgSearch; 60 /** 61 * adapter 62 */ 63 private BaseAdapter adapter; 64 private EditText et_msg; 65 private List<String> msgs = new ArrayList<String>(); 66 private TimerTask task = new TimerTask() { 67 68 @Override 69 public void run() { 70 synchronized (msgs) { 71 msgs = bluetoothClient.getMsgs(); 72 } 73 runOnUiThread(new Runnable() { 74 public void run() { 75 msgAdapter.notifyDataSetChanged(); 76 } 77 }); 78 } 79 80 }; 81 private MyAdapter msgAdapter; 82 /** 83 * 设备搜索广播 84 */ 85 private BroadcastReceiver receiver = new BroadcastReceiver() { 86 87 @Override 88 public void onReceive(Context context, Intent intent) { 89 String action = intent.getAction(); 90 switch (action) { 91 case BluetoothDevice.ACTION_FOUND: 92 // 发现设备,添加到列表,刷新列表 93 discoverDevices.add((BluetoothDevice) intent 94 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); 95 if (adapter != null) { 96 adapter.notifyDataSetChanged(); 97 } 98 break; 99 case BluetoothAdapter.ACTION_DISCOVERY_STARTED: 100 // 开始搜索 101 Log.i(TAG, "开始搜索设备"); 102 discoverDevices.clear(); 103 // 弹出对话框 104 if (dlgSearch == null) { 105 dlgSearch = new AlertDialog.Builder(ClientActivity.this) 106 .create(); 107 // 自定义对话框 108 View view = LayoutInflater.from(ClientActivity.this) 109 .inflate(R.layout.dialog_search, null); 110 ListView lv_devices = (ListView) view 111 .findViewById(R.id.lv_devices); 112 adapter = new DevicesAdapter(ClientActivity.this); 113 lv_devices.setAdapter(adapter); 114 lv_devices 115 .setOnItemClickListener(new OnItemClickListener() { 116 117 @Override 118 public void onItemClick(AdapterView<?> parent, 119 View view, int position, long id) { 120 // 项点击时,进行连接 121 BluetoothDevice device = (BluetoothDevice) view 122 .getTag(); 123 bluetoothClient.connect(device); 124 dlgSearch.dismiss(); 125 dlgSearch = null; 126 127 } 128 }); 129 dlgSearch.setView(view); 130 dlgSearch.setCancelable(true);// 可以按back键取消 131 dlgSearch.setCanceledOnTouchOutside(false);// 不可以按空白地方取消 132 } 133 dlgSearch.show(); 134 break; 135 case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: 136 // 结束搜索 137 Log.i(TAG, "结束搜索设备"); 138 break; 139 140 default: 141 break; 142 } 143 } 144 145 }; 146 147 @Override 148 protected void onCreate(Bundle savedInstanceState) { 149 super.onCreate(savedInstanceState); 150 setContentView(R.layout.activity_chat); 151 152 bluetoothClient = new BluetoothClient(); 153 154 et_msg = (EditText) findViewById(R.id.et_msg); 155 156 ListView lv_msg = (ListView) findViewById(R.id.lv_msg); 157 msgAdapter = new MyAdapter(); 158 lv_msg.setAdapter(msgAdapter); 159 160 Timer timer = new Timer(); 161 timer.schedule(task, 0, 1000); 162 163 // 搜索蓝牙设备 164 bluetoothClient.start(); 165 } 166 167 public class MyAdapter extends BaseAdapter { 168 169 @Override 170 public int getCount() { 171 // TODO Auto-generated method stub 172 return msgs.size(); 173 } 174 175 @Override 176 public Object getItem(int position) { 177 return msgs.get(position); 178 } 179 180 @Override 181 public long getItemId(int position) { 182 // TODO Auto-generated method stub 183 return position; 184 } 185 186 @Override 187 public View getView(int position, View convertView, ViewGroup parent) { 188 TextView tv = null; 189 if (convertView != null) { 190 tv = (TextView) convertView; 191 } else { 192 tv = new TextView(ClientActivity.this); 193 AbsListView.LayoutParams params = new AbsListView.LayoutParams( 194 AbsListView.LayoutParams.MATCH_PARENT, 195 AbsListView.LayoutParams.WRAP_CONTENT); 196 tv.setLayoutParams(params); 197 } 198 tv.setTag(msgs.get(position)); 199 tv.setText(msgs.get(position)); 200 return tv; 201 } 202 203 } 204 205 @Override 206 protected void onResume() { 207 super.onResume(); 208 registerReceiver(); 209 } 210 211 private void registerReceiver() { 212 IntentFilter filter = new IntentFilter(); 213 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); 214 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 215 filter.addAction(BluetoothDevice.ACTION_FOUND); 216 registerReceiver(receiver, filter); 217 } 218 219 @Override 220 protected void onPause() { 221 super.onPause(); 222 unregisterReceiver(receiver); 223 } 224 225 /** 226 * 设备adapter 227 * 228 * @author c 229 * 230 */ 231 private class DevicesAdapter extends BaseAdapter { 232 233 private Context context; 234 235 public DevicesAdapter(Context context) { 236 this.context = context; 237 } 238 239 @Override 240 public int getCount() { 241 return discoverDevices.size(); 242 } 243 244 @Override 245 public Object getItem(int position) { 246 // TODO Auto-generated method stub 247 return discoverDevices.get(position); 248 } 249 250 @Override 251 public long getItemId(int position) { 252 // TODO Auto-generated method stub 253 return position; 254 } 255 256 @Override 257 public View getView(final int position, View convertView, 258 ViewGroup parent) { 259 TextView tv = null; 260 if (convertView != null) { 261 tv = (TextView) convertView; 262 } else { 263 tv = new TextView(context); 264 AbsListView.LayoutParams params = new AbsListView.LayoutParams( 265 AbsListView.LayoutParams.MATCH_PARENT, 266 AbsListView.LayoutParams.WRAP_CONTENT); 267 tv.setLayoutParams(params); 268 } 269 tv.setTag(discoverDevices.get(position)); 270 tv.setText(discoverDevices.get(position).getName()); 271 tv.setFocusable(false); 272 tv.setFocusableInTouchMode(false); 273 // tv.setOnClickListener(new OnClickListener() { 274 // 275 // @Override 276 // public void onClick(View v) { 277 // // 项点击时,进行连接 278 // bluetoothClient.connect(discoverDevices.get(position)); 279 // dlgSearch.dismiss(); 280 // dlgSearch = null; 281 // } 282 // }); 283 return tv; 284 } 285 286 } 287 288 /** 289 * 发送 290 * 291 * @param view 292 */ 293 public void btn_send(View view) { 294 String msg = et_msg.getText().toString().trim(); 295 send(msg); 296 } 297 298 /** 299 * 发送消息到客户端 300 * 301 * @param msg 302 */ 303 private void send(String msg) { 304 bluetoothClient.send(msg); 305 synchronized (msgs) { 306 msgs.add("客户端发送:" + msg); 307 } 308 } 309 }
3.3.2 客户端页面layout
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:layout_margin="5dp" 6 android:orientation="vertical" > 7 8 <LinearLayout 9 android:layout_width="match_parent" 10 android:layout_height="wrap_content" 11 android:layout_marginBottom="5dp" 12 android:orientation="horizontal" > 13 14 <EditText 15 android:id="@+id/et_msg" 16 android:layout_width="0dp" 17 android:layout_height="wrap_content" 18 android:layout_weight="3" 19 android:hint="chatMessage" /> 20 21 <Button 22 android:layout_width="20dp" 23 android:layout_height="wrap_content" 24 android:layout_weight="1" 25 android:onClick="btn_send" 26 android:text="send" /> 27 </LinearLayout> 28 29 <ListView 30 android:id="@+id/lv_msg" 31 android:layout_width="match_parent" 32 android:layout_height="match_parent" > 33 </ListView> 34 35 </LinearLayout>
3.3.3 客户端蓝牙管理类
1 package org.fiu.bluetoothdemos; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 import java.util.ArrayList; 7 import java.util.List; 8 import java.util.UUID; 9 10 import android.annotation.SuppressLint; 11 import android.bluetooth.BluetoothAdapter; 12 import android.bluetooth.BluetoothDevice; 13 import android.bluetooth.BluetoothSocket; 14 import android.util.Log; 15 16 /** 17 * 蓝牙服务器 18 * 19 * @author weizh 20 * 21 */ 22 public class BluetoothClient { 23 private static final String TAG = "BluetoothClient"; 24 /** 25 * 消息集合 26 */ 27 private List<String> listMsg = new ArrayList<String>(); 28 /** 29 * 是否工作中 30 */ 31 private boolean isWorking = false; 32 /** 33 * spp well-known UUID 34 */ 35 public final UUID uuid = UUID 36 .fromString("00001101-0000-1000-8000-00805F9B34FB"); 37 /** 38 * 客户端socket 39 */ 40 private BluetoothSocket mClientSocket; 41 42 public BluetoothClient() { 43 44 } 45 46 /** 47 * 开启服务器 48 */ 49 public void start() { 50 startDiscovery(); 51 } 52 53 /** 54 * 开始检查设备 55 */ 56 private void startDiscovery() { 57 if (!BluetoothUtils.checkBluetoothExists()) { 58 throw new RuntimeException("bluetooth module not exists."); 59 } 60 // 打开设备 61 if (!BluetoothUtils.openBluetoothDevice()) { 62 return; 63 } 64 // 开始扫描设备 65 BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter(); 66 defaultAdapter.startDiscovery(); 67 } 68 69 OutputStream outputStream; 70 private InputStream inputStream; 71 72 /** 73 * 停止 74 */ 75 public void stop() { 76 isWorking = false; 77 if (mClientSocket != null) { 78 try { 79 mClientSocket.close(); 80 } catch (IOException e) { 81 // TODO Auto-generated catch block 82 e.printStackTrace(); 83 } finally { 84 mClientSocket = null; 85 } 86 } 87 } 88 89 /** 90 * 客户端socket工作类 91 * 92 * @author weizh 93 * 94 */ 95 private class ClientWorkingThread extends Thread { 96 97 public ClientWorkingThread() { 98 } 99 100 @SuppressLint("NewApi") 101 @Override 102 public void run() { 103 try { 104 // 从输入流中取出数据,插入消息条中 105 byte[] buffer = new byte[1024]; 106 while (isWorking) { 107 int read = inputStream.read(buffer); 108 if (read != -1) { 109 // 有内容 110 // 判断是否取得的消息填充满了buffer,未到字符串结尾符;如果不是,证明读取到了一条信息,并且信息是完整的,这个完整的前提是不能粘包,不粘包可以使用flush进行处理。 111 StringBuilder sb = new StringBuilder(); 112 if (read < buffer.length) { 113 String msg = new String(buffer, 0, read); 114 sb.append(msg); 115 } else { 116 byte[] tempBytes = new byte[1024 * 4]; 117 while (read == buffer.length 118 && buffer[read - 1] != 0x7f) { 119 read = inputStream.read(buffer); 120 } 121 String msg = new String(buffer, 0, read); 122 sb.append(msg); 123 } 124 Log.i(TAG, "客户端收到:" + sb.toString()); 125 synchronized (listMsg) { 126 listMsg.add("服务器发送:" + sb.toString()); 127 } 128 } 129 } 130 } catch (IOException e) { 131 // TODO Auto-generated catch block 132 e.printStackTrace(); 133 } 134 // 工作完毕,关闭socket 135 try { 136 mClientSocket.close(); 137 } catch (IOException e) { 138 // TODO Auto-generated catch block 139 e.printStackTrace(); 140 } 141 142 } 143 } 144 145 /** 146 * 返回listMsg 147 * 148 * @return 149 */ 150 public List<String> getMsgs() { 151 synchronized (listMsg) { 152 return listMsg; 153 } 154 } 155 156 /** 157 * 发送消息 158 * 159 * @param msg 160 */ 161 public void send(final String msg) { 162 new Thread(new Runnable() { 163 164 @Override 165 public void run() { 166 if (mClientSocket != null) { 167 try { 168 if (outputStream != null) { 169 byte[] bytes = msg.getBytes(); 170 outputStream.write(bytes); 171 outputStream.flush(); 172 } 173 } catch (IOException e) { 174 // TODO Auto-generated catch block 175 e.printStackTrace(); 176 } 177 } 178 } 179 }).start(); 180 181 } 182 183 /** 184 * 进行连接 185 * 186 * @param device 187 */ 188 @SuppressLint("NewApi") 189 public void connect(final BluetoothDevice device) { 190 new Thread(new Runnable() { 191 192 @Override 193 public void run() { 194 // TODO Auto-generated method stub 195 try { 196 mClientSocket = device 197 .createRfcommSocketToServiceRecord(BluetoothServer.MY_UUID); 198 mClientSocket.connect(); 199 isWorking = true; 200 try { 201 outputStream = mClientSocket.getOutputStream(); 202 inputStream = mClientSocket.getInputStream(); 203 } catch (IOException e1) { 204 // TODO Auto-generated catch block 205 e1.printStackTrace(); 206 } 207 new ClientWorkingThread().start(); 208 209 } catch (IOException e) { 210 // TODO Auto-generated catch block 211 e.printStackTrace(); 212 Log.i(TAG, "连接失败"); 213 } 214 } 215 }).start(); 216 } 217 218 }
3.4 其它用到的布局和类
3.4.1 dialog_search
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <RelativeLayout 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:layout_margin="5dp" 11 android:orientation="horizontal" > 12 13 <TextView 14 android:layout_width="wrap_content" 15 android:layout_height="wrap_content" 16 android:layout_alignParentLeft="true" 17 android:text="正在搜索……" /> 18 19 <ProgressBar 20 android:id="@+id/progressBar1" 21 style="?android:attr/progressBarStyleSmall" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:layout_alignParentRight="true" /> 25 </RelativeLayout> 26 27 <View 28 android:layout_width="match_parent" 29 android:layout_height="2dp" 30 android:layout_marginBottom="2dp" 31 android:layout_marginTop="2dp" 32 android:background="@android:color/darker_gray" /> 33 34 <ListView 35 android:id="@+id/lv_devices" 36 android:layout_width="match_parent" 37 android:layout_height="match_parent" 38 android:layout_margin="5dp" > 39 </ListView> 40 41 </LinearLayout>
3.5 BluetoothUtils.java:蓝牙帮助类
1 package org.fiu.bluetoothdemos; 2 3 import java.util.Locale; 4 import java.util.Set; 5 6 import android.bluetooth.BluetoothAdapter; 7 import android.bluetooth.BluetoothDevice; 8 9 /** 10 * 蓝牙帮助模块 11 * 12 * @author c 13 * 14 */ 15 public class BluetoothUtils { 16 /** 17 * 检查蓝牙模块是否存在 18 * 19 * @return 20 */ 21 public static boolean checkBluetoothExists() { 22 BluetoothAdapter bluetoothAdapter = BluetoothAdapter 23 .getDefaultAdapter(); 24 if (bluetoothAdapter != null) { 25 return true; 26 } 27 return false; 28 } 29 30 /** 31 * 打开蓝牙模块 32 * 33 * @return 34 */ 35 public static boolean openBluetoothDevice() { 36 BluetoothAdapter bluetoothAdapter = BluetoothAdapter 37 .getDefaultAdapter(); 38 if (!bluetoothAdapter.isEnabled()) { 39 if (bluetoothAdapter.enable()) { 40 return true; 41 } 42 } else { 43 return true; 44 } 45 return false; 46 } 47 48 /** 49 * 开启蓝牙模块扫描 50 * 51 * @return 52 */ 53 public static void startDiscovery() { 54 BluetoothAdapter bluetoothAdapter = BluetoothAdapter 55 .getDefaultAdapter(); 56 if (!bluetoothAdapter.isDiscovering()) { 57 bluetoothAdapter.startDiscovery(); 58 } 59 } 60 61 /** 62 * Convert hex string to byte[] 把为字符串转化为字节数组 63 * 64 * @param hexString 65 * the hex string 66 * @return byte[] 67 */ 68 public static byte[] hexStringToBytes(String hexString) { 69 hexString = hexString.replaceAll(" ", ""); 70 if (hexString == null || hexString.equals("")) { 71 return null; 72 } 73 hexString = hexString.toUpperCase(Locale.getDefault()); 74 int length = hexString.length() / 2; 75 char[] hexChars = hexString.toCharArray(); 76 byte[] d = new byte[length]; 77 for (int i = 0; i < length; i++) { 78 int pos = i * 2; 79 d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); 80 } 81 return d; 82 } 83 84 /** 85 * Convert char to byte 86 * 87 * @param c 88 * char 89 * @return byte 90 */ 91 private static byte charToByte(char c) { 92 return (byte) "0123456789ABCDEF".indexOf(c); 93 } 94 95 /** 96 * 获取已配对的蓝牙设备集合 97 * 98 * @return 99 */ 100 public static Set<BluetoothDevice> getBondedDevices() { 101 return BluetoothAdapter.getDefaultAdapter().getBondedDevices(); 102 } 103 104 /** 105 * 检测当前device是否已经bonded过 106 * 107 * @param device 108 * @return 109 */ 110 public static boolean isBonded(BluetoothDevice device) { 111 if (checkBluetoothExists()) { 112 // 连接之前先确定是否已经bond过,配对过 113 Set<BluetoothDevice> bondedDevices = BluetoothAdapter 114 .getDefaultAdapter().getBondedDevices(); 115 if (bondedDevices != null) { 116 for (BluetoothDevice bluetoothDevice : bondedDevices) { 117 if (bluetoothDevice.getAddress() 118 .equals(device.getAddress())) { 119 // 该device已经bond过 120 return true; 121 } 122 } 123 } 124 } 125 return false; 126 } 127 }
3.6 AndroidManifest.xml:别忘了添加蓝牙权限
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="org.fiu.bluetoothchatdemos" 4 android:versionCode="1" 5 android:versionName="1.0" > 6 7 <uses-sdk 8 android:minSdkVersion="8" 9 android:targetSdkVersion="21" /> 10 11 <uses-permission android:name="android.permission.BLUETOOTH" /> 12 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 13 14 <application 15 android:allowBackup="true" 16 android:icon="@drawable/ic_launcher" 17 android:label="@string/app_name" 18 android:theme="@style/AppTheme" > 19 <activity 20 android:name="org.fiu.bluetoothdemos.MainActivity" 21 android:label="@string/app_name" > 22 <intent-filter> 23 <action android:name="android.intent.action.MAIN" /> 24 25 <category android:name="android.intent.category.LAUNCHER" /> 26 </intent-filter> 27 </activity> 28 <activity android:name="org.fiu.bluetoothdemos.ServerActivity" /> 29 <activity android:name="org.fiu.bluetoothdemos.ClientActivity" /> 30 </application> 31 32 </manifest>
好了,此文章及至这里,如果有问题,欢迎留言。
整包资源由于公司加密系统,现在放出不好搞,后面有机会了会放出。