我之前做了个人机对战的五子棋, AI算法很垃圾, 然后各种逻辑很糟糕, 已经很久没有维护了, 今天看了篇文章, 是用了蓝牙和Wifi的五子棋对战, 我觉得很有意思, 毕竟自己没做过蓝牙连接这方面的项目, 然后我就把以前做的五子棋搬了出来.
设置蓝牙、查找局部区域内的配对设备或可用设备、连接设备,以及在设备之间传输数据。
权限是一定要设置的,不然根本无法使用,在Manifest设置以下权限:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
我是基于6.0开发的, 然而我开启了蓝牙却搜索不到其他设备, 感觉很奇怪,搜索了一下,原来:还要添加一个权限(其中一个):
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
所有蓝牙Activity都需要BluetoothAdapter,获取这个Adapter很简单, 只需要调用BluetoothAdapter.getDefaultAdapter()这个静态方法即可,它会返回本机的蓝牙适配器.
首先检测是否已开启蓝牙可见性, 然后使用
if(!mBluetoothAdapter.isEnabled())
{
Intent openIntent=new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
openIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 500); //设置可见性的时间500s, 0为一直可见
startActivity(openIntent);
}
提示:启用可见性就会启动蓝牙.
当我们调用BluetoothAdapter.startDiscovery()扫描的时候, 会发出有ACTION_FOUND的广播, 并且Intent还带有EXTRA_DEVICE(包含BluetoothDevice),请看下面代码:
public class BluetoothReceiver extends BroadcastReceiver {
private List mDevices;
private List mBluetoothList;
public OnReceiverListener mOnReceiverListener;
public BluetoothReceiver(List devices, List bluetooths, OnReceiverListener onReceiverListener) {
mOnReceiverListener = onReceiverListener;
mDevices = devices;
mBluetoothList = bluetooths;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
///获取扫描到的Device信息
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (!mDevices.contains(device)) {
Bluetooth bluetooth = new Bluetooth(device.getName(), device.getAddress());
mBluetoothList.add(bluetooth);
mDevices.add(device);
}
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
mOnReceiverListener.showText();
}
if (mOnReceiverListener != null) {
mOnReceiverListener.setBluetoothList(mBluetoothList, mDevices);
}
}
//设置给BluetoothActivity的回调接口
public interface OnReceiverListener {
void setBluetoothList(List bluetooths, List devices);
void showText();
}
}
注意: 别忘了再Activity里注册和销毁Receiver
直接调用startDiscovery()就可以了,很简单.
//初始化
private void init() {
mBluetooths = new ArrayList<>();
mDevices = new ArrayList<>();
//初始化BluetoothAdpter
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (!mBluetoothAdapter.isEnabled()) {
openBluetooth();
}
mName = mBluetoothAdapter.getName();
mAdress = mBluetoothAdapter.getAddress();
//初始化ListView
mAdapter = new BluetoothDevicesAdapter(mBluetooths, this);
mLvBluetooth.setAdapter(mAdapter);
//实例化广播
mReceiver = new BluetoothReceiver(mDevices, mBluetooths, new BluetoothReceiver.OnReceiverListener() {
@Override
public void setBluetoothList(List bluetooths, List devices) {
mBluetooths = bluetooths;
mDevices = devices;
mAdapter.setDevices(mBluetooths);
mAdapter.notifyDataSetChanged();
}
@Override
public void showText() {
Toast.makeText(BluetoothActivity.this, "搜索完成!", Toast.LENGTH_SHORT).show();
}
});
//注册广播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
mLvBluetooth.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
connectBluetooth(position);
}
});
}
//查找设备
private void findBluetooth() {
if (mBluetoothAdapter.isDiscovering()) { //中断搜索
mBluetoothAdapter.cancelDiscovery();
}
mBluetooths.clear();
mDevices.clear();
//获取已配对的设备
Set devices = mBluetoothAdapter.getBondedDevices();
if (devices.size() > 0) {
for (BluetoothDevice device : devices) {
if (!mDevices.contains(device)) {
mBluetooths.add(new Bluetooth(device.getName(), device.getAddress()));
mDevices.add(device);
}
}
}
mAdapter.setDevices(mBluetooths);
mAdapter.notifyDataSetChanged();
mBluetoothAdapter.startDiscovery();
}
连接设备需要同时实现服务端和客服端, 因为其中一台设备必须开放服务器套接字, 另一台设备发起连接(使用服务器设备MAC地址), 当服务器和客户端在同一RFCOMM通道上分别拥有已连接的BluetoothSocket时, 此时就是彼此连接.
提示: 若两台设备未配对, 则会先进行配对, 知道配对成功才会进行连接.
当需要连接时, 其中一台设备需要开放BluetoothServerSocket来充当服务器, 服务器套接字是用来侦听传入的连接请求, 并在接收后提供已连接的BluetoothSocket.
通过调用listenUsingRfcommWithServiceRecord(String , UUID)获取BluetoothServerSocket.其中字符串是任意名称, UUID必须唯一标识, 两个UUID必须匹配.
UUID: 通用唯一标识符, UUID足够庞大,可以使用随机, 可使用UUID.fromString(String)初始化
调用accept()来侦听, 需要放在子线程中, 只有当发送的请求中的UUID与服务器套接字UUID相匹配时, 才会被接受.返回已连接的BluetoothSocket.
如果不需要与更多的连接,那么可以调用close()来释放所有资源.并不会关闭accept的已连接的BluetoothSocket.
//初始化Scocket
private void initSocket() {
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(mName, ConfigData.UUID);
mSocket = mServerSocket.accept();
if (mSocket.isConnected()) {
InputStream is = mSocket.getInputStream();
byte[] buffer = new byte[1024];
int count = is.read(buffer);
final String result = new String(buffer, 0, count, "utf-8");
runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialog aDialog = new AlertDialog.Builder(BluetoothActivity.this)
.setTitle("消息")
.setMessage("是否接受" + result + "的挑战?")
.setPositiveButton("是", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(BluetoothActivity.this, "连接成功!", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("否", null)
.show();
}
});
if(is!=null){
is.close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}).start();
}
主要是为了能然上面的ServerSocket接收到请求.
先获取需要连接设备的Device, 然后调用 device.createRfcommSocketToServiceRecord(ConfigData.UUID);就能够获取然后再调用bluetoothSocket.connect()就可以发起连接了
注: 调用connect()时, 最好不要进行其他的蓝牙操作, 不然连接速度很慢, 也有可能会失败.
// 开始连接对方
private void connectBluetooth(int position) {
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mBluetooths.get(position).getAdress());
try {
mThisSocket = device.createRfcommSocketToServiceRecord(ConfigData.UUID);
//弹出对话框
AlertDialog dialog = new AlertDialog.Builder(BluetoothActivity.this)
.setTitle("发起挑战")
.setMessage("确认挑战玩家: " + mBluetooths.get(position).getName())
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new Thread(new Runnable() {
@Override
public void run() {
OutputStream os = null;
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
try {
mThisSocket.connect(); //发起连接
if (!mBluetoothAdapter.isEnabled()) { //若未开启蓝牙,则打开
mBluetoothAdapter.enable();
}
if (mThisSocket.isConnected()) { //若已连接成功
os = mThisSocket.getOutputStream(); //获得输出流
os.write(mName.getBytes("utf-8")); //向服务端发送本机蓝牙名称
runOnUiThread(new Runnable() { //在UI线程发送提示.
@Override
public void run() {
Toast.makeText(BluetoothActivity.this, "连接成功!!!", Toast.LENGTH_SHORT).show();
}
});
}
if (os != null) {
os.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
})
.setNegativeButton("取消", null)
.show();
} catch (Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(BluetoothActivity.this, "连接失败!", Toast.LENGTH_SHORT).show();
}
});
e.printStackTrace();
}
}