Android 蓝牙连接与通讯,BLE蓝牙和经典蓝牙一起的蓝牙连接与通讯案例
Android 蓝牙有两种,一种是BLE蓝牙,另外一种是经典蓝牙。
BLE蓝牙连接与通讯使用的是 BluetoothKit 框架,BluetoothKit 框架源码地址与说明:
https://gitee.com/www163/Android-BluetoothKit
BluetoothKit 的弊端是经典蓝牙连接失败。
所以经典蓝牙,自己写了连接和通讯方式。
对于通讯来讲,分为服务端BtServer与客户端BtClient,一般是安装app方为服务端,主要是本身的蓝牙mac是固定,所以作为服务器端,被连接方为客户端。
对BLE的连接与通讯,大家可以看 BluetoothKit 框架详细说明,这里就不累赘。
下面主要是介绍经典蓝牙的连接和通讯。
蓝牙连接是通过UUID来进行监听连接的,就是服务器端与客户端的UUID要一致,这样才能连接成功。
ps:在蓝牙连接通讯时,前提先要配对才能正常通讯。
最下面有项目源码下载。
在经典蓝牙连接时,经常出现“run: read failed, socket might closed or timeout, read ret: -1”
主要原因是UUID的错误。
非手机终端的UUID:00001101-0000-1000-8000-00805f9B34FB
手机终端的UUID:00001105-0000-1000-8000-00805f9b34fb
1、AndroidManifest.xml 清单设置蓝牙权限
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
2、蓝牙通讯BtBase 基类:
package com.inuker.bluetooth; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.os.Environment; import android.util.Log; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.util.UUID; public class BtBase { //00001101-0000-1000-8000-00805F9B34FB - 00001101-0000-1000-8000-00805f9b34fb //00001106-0000-1000-8000-00805F9B34FB - 00001105-0000-1000-8000-00805f9b34fb //00001105-0000-1000-8000-00805f9B34FB - 00001105-0000-1000-8000-00805f9b34fb //非手机终端的UUID public static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9B34FB"); //手机终端的UUID public static final UUID MOB_UUID = UUID.fromString("00001105-0000-1000-8000-00805f9B34FB"); private static final String FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/bluetooth/"; private static final int FLAG_MSG = 0; //消息标记 private static final int FLAG_FILE = 1; //文件标记 private BluetoothSocket mSocket; private DataOutputStream mOut; private Listener mListener; public boolean isRead; public boolean isSending; BtBase(Listener listener) { mListener = listener; } /** * 循环读取对方数据(若没有数据,则阻塞等待) */ void loopRead(BluetoothSocket socket) { mSocket = socket; try { Log.i("test","---------loopRead111--------"); if (!mSocket.isConnected()) mSocket.connect(); Log.i("test","---------loopRead2222--------"); notifyUI(Listener.CONNECTED, mSocket.getRemoteDevice()); mOut = new DataOutputStream(mSocket.getOutputStream()); DataInputStream in = new DataInputStream(mSocket.getInputStream()); isRead = true; while (isRead) { //死循环读取 Log.i("Test","------------------server in.readInt="+in.readInt()); switch (in.readInt()) { case FLAG_MSG: //读取短消息 String msg = in.readUTF(); notifyUI(Listener.MSG, "接收短消息:" + msg); break; case FLAG_FILE: //读取文件 Util.mkdirs(FILE_PATH); String fileName = in.readUTF(); //文件名 long fileLen = in.readLong(); //文件长度 // 读取文件内容 long len = 0; int r; byte[] b = new byte[4 * 1024]; FileOutputStream out = new FileOutputStream(FILE_PATH + fileName); notifyUI(Listener.MSG, "正在接收文件(" + fileName + "),请稍后..."); while ((r = in.read(b)) != -1) { out.write(b, 0, r); len += r; if (len >= fileLen) break; } notifyUI(Listener.MSG, "文件接收完成(存放在:" + FILE_PATH + ")"); break; } } } catch (Throwable e) { e.printStackTrace(); Log.i("Test","-------------BtBase close-----------------"); close(); } } void loopRead2(BluetoothSocket socket) { mSocket = socket; try { Log.i("test","---------loopRead111--------"); if (!mSocket.isConnected()) mSocket.connect(); Log.i("test","---------loopRead2222--------"); notifyUI(Listener.CONNECTED, mSocket.getRemoteDevice()); } catch (Throwable e) { e.printStackTrace(); Log.i("Test","-------------BtBase close-----------------"); close(); } } /** * 循环读取对方数据(若没有数据,则阻塞等待) */ void loopReadMsg() { try { // notifyUI(Listener.CONNECTED, mSocket.getRemoteDevice()); Log.i("Test","-----------BtBase-loopReadMsg2---------"); // mOut = new DataOutputStream(mSocket.getOutputStream()); DataInputStream in = new DataInputStream(mSocket.getInputStream()); isRead = true; Log.i("Test","-----------BtBase-loopReadMsg3---------"); while (isRead) { //死循环读取 String msg = in.readUTF(); Log.i("Test","-----------BtBase-loopReadMsg4---------"); notifyUI(Listener.MSG, "接收短消息:" + msg); } } catch (Throwable e) { e.printStackTrace(); close(); } } /** * 发送短消息 */ public void sendMsg2(String msg) { Log.i("Test","-----------sendMsg3----2-----"); if (checkSend()) return; isSending = true; Log.i("Test","-----------sendMsg3----3-----"); try { Log.i("Test","----------sendMsg2--mSocket="+mSocket); OutputStream out = mSocket.getOutputStream(); Log.i("Test","----------sendMsg2--out="+out); mOut = new DataOutputStream(out); mOut.writeInt(FLAG_MSG); //消息标记 mOut.writeUTF(msg); mOut.flush(); notifyUI(Listener.MSG, "发送短消息:" + msg); } catch (Throwable e) { e.printStackTrace(); // close(); } isSending = false; } /** * 发送短消息 */ public void sendMsg(String msg) { Log.i("Test","-----------sendMsg3----2-----"); if (checkSend()) return; isSending = true; Log.i("Test","-----------sendMsg3----3-----"); try { mOut = new DataOutputStream(mSocket.getOutputStream()); mOut.writeInt(FLAG_MSG); //消息标记 mOut.writeUTF(msg); mOut.flush(); notifyUI(Listener.MSG, "发送短消息:" + msg); } catch (Throwable e) { e.printStackTrace(); // close(); } isSending = false; } /** * 发送文件 */ public void sendFile(final String filePath) { if (checkSend()) return; isSending = true; Util.EXECUTOR.execute(new Runnable() { @Override public void run() { try { FileInputStream in = new FileInputStream(filePath); File file = new File(filePath); mOut.writeInt(FLAG_FILE); //文件标记 mOut.writeUTF(file.getName()); //文件名 mOut.writeLong(file.length()); //文件长度 int r; byte[] b = new byte[4 * 1024]; notifyUI(Listener.MSG, "正在发送文件(" + filePath + "),请稍后..."); while ((r = in.read(b)) != -1) mOut.write(b, 0, r); mOut.flush(); notifyUI(Listener.MSG, "文件发送完成."); } catch (Throwable e) { close(); } isSending = false; } }); } /** * 释放监听引用(例如释放对Activity引用,避免内存泄漏) */ public void unListener() { mListener = null; } /** * 关闭Socket连接 */ public void close() { try { Log.i("Test","---------关闭Socket连接-----------"); isRead = false; if(mSocket!=null){ mSocket.close(); notifyUI(Listener.DISCONNECTED, null); } // notifyUI(Listener.DISCONNECTED, null); } catch (Throwable e) { e.printStackTrace(); } } /** * 当前设备与指定设备是否连接 */ public boolean isConnected(BluetoothDevice dev) { boolean connected = (mSocket != null && mSocket.isConnected()); if (dev == null) return connected; return connected && mSocket.getRemoteDevice().equals(dev); } // ============================================通知UI=========================================================== public boolean checkSend() { if (isSending) { MyApplication.toast("正在发送其它数据,请稍后再发...", 0); return true; } return false; } public void notifyUI(final int state, final Object obj) { MyApplication.runUi(new Runnable() { @Override public void run() { try { if (mListener != null) mListener.socketNotify(state, obj); } catch (Throwable e) { e.printStackTrace(); } } }); } public interface Listener { int DISCONNECTED = 0; int CONNECTED = 1; int MSG = 2; void socketNotify(int state, Object obj); } }
3、服务器端BtServer 代码:
package com.inuker.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.util.Log; import java.lang.reflect.Method; import java.util.UUID; public class BtServer extends BtBase{ private static final String TAG = BtServer.class.getSimpleName(); private BluetoothServerSocket mSSocket; private BluetoothSocket socket; BtServer(Listener listener) { super(listener); // listen(); } /** * 监听客户端发起的连接 */ public void listen() { try { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); mSSocket = adapter.listenUsingInsecureRfcommWithServiceRecord(TAG, MOB_UUID); //明文传输(不安全),无需配对 // 开启子线程 Util.EXECUTOR.execute(new Runnable() { @Override public void run() { try { socket = mSSocket.accept(); // 监听连接 // mSSocket.close(); // 关闭监听,只连接一个设备 loopRead(socket); // 循环读取 } catch (Throwable e) { close(); } } }); } catch (Throwable e) { close(); } } public void listen(UUID uuid) { try { Log.i("Test","--------------------uuid="+uuid); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); mSSocket = adapter.listenUsingInsecureRfcommWithServiceRecord(TAG, uuid); //明文传输(不安全),无需配对 // 开启子线程 Util.EXECUTOR.execute(new Runnable() { @Override public void run() { try { socket = mSSocket.accept(); // 监听连接 // mSSocket.close(); // 关闭监听,只连接一个设备 loopRead(socket); // 循环读取 } catch (Throwable e) { close(); } } }); } catch (Throwable e) { close(); } } @Override public void close() { super.close(); try { if(socket!=null){ socket.close(); } if(mSSocket!=null){ mSSocket.close(); } } catch (Throwable e) { e.printStackTrace(); } } }
4、客户端BtClient 代码:
package com.inuker.bluetooth; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.os.ParcelUuid; import android.util.Log; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.util.UUID; public class BtClient extends BtBase { private BluetoothSocket mSocket; boolean flag = false; BtClient(Listener listener) { super(listener); } /** * 与远端设备建立长连接 * * @param dev 远端设备 */ public void connect(BluetoothDevice dev) { close(); try { final BluetoothSocket socket = dev.createInsecureRfcommSocketToServiceRecord(SPP_UUID); //明文传输(不安全),无需配对 Log.i("Test","------------------socket="+socket); // 开启子线程 Util.EXECUTOR.execute(new Runnable() { @Override public void run() { Log.i("Test","-----------connect-------"); loopRead(socket); //循环读取 } }); } catch (Throwable e) { Log.i("Test","-------------BtClient close-----------------"); close(); } } public void connect2(BluetoothDevice dev,UUID uuid) { close(); try { final BluetoothSocket socket = dev.createInsecureRfcommSocketToServiceRecord(uuid); //明文传输(不安全),无需配对 Log.i("Test","------------------socket="+socket); // 开启子线程 new Thread(new Runnable() { @Override public void run() { try { boolean isCon = socket.isConnected(); Log.i("Test","------------------isCon="+isCon); socket.connect(); loopRead2(socket); //循环读取 } catch (IOException e) { e.printStackTrace(); } } }).start(); } catch (Throwable e) { Log.i("Test","-------------BtClient close-----------------"); close(); } } public void connect3(BluetoothDevice dev) { close(); try { final BluetoothSocket socket = dev.createInsecureRfcommSocketToServiceRecord(SPP_UUID); //明文传输(不安全),无需配对 Log.i("Test","------------------socket="+socket); // 开启子线程 new Thread(new Runnable() { @Override public void run() { try { boolean isCon = socket.isConnected(); Log.i("Test","------------------isCon="+isCon); socket.connect(); loopRead2(socket); //循环读取 } catch (IOException e) { e.printStackTrace(); } } }).start(); } catch (Throwable e) { Log.i("Test","-------------BtClient close-----------------"); close(); } } public void loopReadMsg(BluetoothDevice dev) { // close(); try { Log.i("Test","-----------loopReadMsg1---------");// 开启子线程 Util.EXECUTOR.execute(new Runnable() { @Override public void run() { Log.i("Test","-----------loopReadMsg2---------"); loopReadMsg(); //循环读取 } }); } catch (Throwable e) { e.printStackTrace(); Log.i("Test","-----------loopReadMsg3---------ex="+e.getMessage()); close(); } } public void clientSendMsg(String msg) { Log.i("Test","-----------sendMsg3----2-----"); if (checkSend()) return; isSending = true; Log.i("Test","-----------sendMsg3----3-----"); try { Log.i("Test","----------sendMsg2--mSocket="+mSocket); OutputStream out = mSocket.getOutputStream(); Log.i("Test","----------sendMsg2--out="+out); DataOutputStream mOut = new DataOutputStream(out); mOut.writeInt(Listener.MSG); //消息标记 mOut.writeUTF(msg); mOut.flush(); notifyUI(Listener.MSG, "发送短消息:" + msg); } catch (Throwable e) { e.printStackTrace(); // close(); } isSending = false; } }
5、Activity 中调用案例:
package com.inuker.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.ParcelUuid; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import com.inuker.bluetooth.library.connect.listener.BluetoothStateListener; import com.inuker.bluetooth.library.search.SearchResult; import com.inuker.bluetooth.library.utils.BluetoothLog; import com.inuker.bluetooth.view.PullRefreshListView; import com.inuker.bluetooth.view.PullToRefreshFrameLayout; import java.io.File; import java.util.ArrayList; import java.util.UUID; public class CommonFragment extends Fragment implements BtBase.Listener, BtReceiver.Listener, BtDevAdapter.Listener{ private TextView mTips; private EditText mInputMsg; private EditText mInputFile; private TextView mLogs; private BtReceiver mBtReceiver; private BtDevAdapter mBtDevAdapter; private BtClient mClient; private View view; private Context context; private Button scanBtn,sendMsgBtn,sendFileBtn,listenTerBtn,listenMobBtn; private BluetoothDevice remoteDev; private boolean isLoop = true; private BtServer mServer; BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); private boolean isListen = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_common, container, false); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); view = getView(); context = view.getContext(); mBtDevAdapter = new BtDevAdapter(this); mClient = new BtClient(this); mServer = new BtServer(this); listenTerBtn = view.findViewById(R.id.listen_ter); listenMobBtn = view.findViewById(R.id.listen_mob); RecyclerView rv = view.findViewById(R.id.rv_bt); rv.setLayoutManager(new LinearLayoutManager(context)); rv.setAdapter(mBtDevAdapter); mTips = view.findViewById(R.id.tv_tips); mInputMsg = view.findViewById(R.id.input_msg); mInputFile = view.findViewById(R.id.input_file); mLogs = view.findViewById(R.id.tv_log); scanBtn = view.findViewById(R.id.button2); sendMsgBtn = view.findViewById(R.id.btn_sendMsg); sendFileBtn = view.findViewById(R.id.btn_sendFile); mBtReceiver = new BtReceiver(context, this);//注册蓝牙广播 BluetoothAdapter.getDefaultAdapter().startDiscovery(); scanBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { reScan(); } }); sendMsgBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String txt = sendMsgBtn.getText().toString(); Log.i("Test","-----is="+"发送信息".equals(txt)); if("发送信息".equals(txt)){ mLogs.setText(""); sendMsgBtn.setText("关闭"); loopSendMsg(); }else{ handler3.sendEmptyMessage(1); } } }); sendFileBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendFile(); } }); listenTerBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mServer.close(); mServer.listen(BtBase.SPP_UUID); isListen = true; } }); listenMobBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mServer.close(); mServer.listen(BtBase.MOB_UUID); isListen = true; } }); } @Override public void onDestroy() { super.onDestroy(); context.unregisterReceiver(mBtReceiver); mClient.unListener(); mClient.close(); mServer.unListener(); mServer.close(); } @Override public void socketNotify(int state, Object obj) { String msg = null; switch (state) { case BtBase.Listener.CONNECTED: BluetoothDevice dev = (BluetoothDevice) obj; msg = String.format("与%s(%s)连接成功", dev.getName(), dev.getAddress()); remoteDev = dev; mTips.setText(msg); break; case BtBase.Listener.DISCONNECTED: msg = "连接断开"; mTips.setText(msg); break; case BtBase.Listener.MSG: msg = String.format("\n%s", obj); mLogs.append(msg); break; } MyApplication.toast(msg, 0); } @Override public void onItemClick(BluetoothDevice dev) { mServer.close(); mBluetoothAdapter.cancelDiscovery(); Log.i("Test","------name="+dev.getName()+","+dev.getAddress()); if (mClient.isConnected(dev)) { remoteDev = dev; MyApplication.toast("已经连接了", 0); return; } int state = dev.getBondState(); if(state != BluetoothDevice.BOND_BONDED){ boolean isPair = BluetoothUtils.pair(dev.getAddress(),""); // Log.i("Test","-------------------isPair="+isPair); MyApplication.toast("请先配对蓝牙", 0); reScan(); return; } ParcelUuid[] parcel = dev.getUuids(); boolean isContain = isContainUUID(dev); if(isContain){ mServer.listen(BtBase.SPP_UUID); mClient.connect2(dev,BtBase.SPP_UUID); }else{ mServer.listen(BtBase.MOB_UUID); mClient.connect2(dev,BtBase.MOB_UUID); } MyApplication.toast("正在连接...", 0); mTips.setText("正在连接..."); } @Override public void foundDev(BluetoothDevice dev) { mBtDevAdapter.add(dev); } // 重新扫描 public void reScan() { mBtDevAdapter.reScan(); } //发送信息 public void sendMsg2() { if (mClient.isConnected(null)) { String msg = mInputMsg.getText().toString(); if (TextUtils.isEmpty(msg)) MyApplication.toast("消息不能空", 0); else mClient.sendMsg(msg); } else MyApplication.toast("没有连接", 0); } public void sendMsgByClient() { mClient.sendMsg("发送成功"); } //发送信息 public void loopReadMsg() { if (mClient.isConnected(null)) { String msg = "发送成功......"; mClient.loopReadMsg(); } else MyApplication.toast("没有连接", 0); } public void sendMsg() { Log.i("Test","-----------sendMsg1---------"); if(remoteDev == null){ MyApplication.toast("请选连接蓝牙", 0); return; } Log.i("Test","-----------sendMsg2---------"); if (!mClient.isConnected(remoteDev)) { MyApplication.toast("请选连接蓝牙", 0); return; } Log.i("Test","-----------sendMsg3---------"); mClient.loopReadMsg(remoteDev); } //发送文件 public void sendFile() { if (mClient.isConnected(null)) { String filePath = mInputFile.getText().toString(); if (TextUtils.isEmpty(filePath) || !new File(filePath).isFile()) MyApplication.toast("文件无效", 0); else mClient.sendFile(filePath); } else MyApplication.toast("没有连接", 0); } private void loopRead(){ new Thread(new Runnable() { @Override public void run() { while (isLoop){ loopReadMsg(); } } }).start(); } private Handler handler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); String txt = sendMsgBtn.getText().toString(); if("发送信息".equals(txt)){ // loopRead(); // loopRead(); if (mClient.isConnected(null)) { // String msg = "发送成功......"; sendMsgBtn.setText("关闭"); mClient.loopReadMsg(); isLoop = true; } else MyApplication.toast("没有连接", 0); }else{ isLoop = false; sendMsgBtn.setText("发送信息"); } } }; public void serverSendMsg() { if (mServer.isConnected(null)) { mServer.sendMsg("测试。。。。"); } else MyApplication.toast("没有连接", 0); } private boolean isContainUUID(BluetoothDevice dev){ ParcelUuid[] pars = dev.getUuids(); for(ParcelUuid p : pars){ UUID u = p.getUuid(); if(BtBase.SPP_UUID.equals(u)){ return true; } } return false; } private Handler handler3 = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); switch (msg.what){ case 0: // sendMsgBtn.setText("关闭"); sendMsgByClient(); isLoop = true; break; case 1: isLoop = false; sendMsgBtn.setText("发送信息"); break; } } }; private void loopSendMsg(){ new Thread(new Runnable() { @Override public void run() { while(isLoop){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } handler3.sendEmptyMessage(0); } } }).start(); } }
6、效果如下:
XT300 是打印机终端:
K5为手机蓝牙连接如下:
7、项目源码:
https://github.com/hongchuanfeng/Android-Bluetooth-Demo