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、项目源码: