前言
我之前做了个人机对战的五子棋, AI算法很垃圾, 然后各种逻辑很糟糕, 已经很久没有维护了, 今天看了篇文章, 是用了蓝牙和Wifi的五子棋对战, 我觉得很有意思, 毕竟自己没做过蓝牙连接这方面的项目, 然后我就把以前做的五子棋搬了出来.
蓝牙基础知识
官方知识:
设置蓝牙、查找局部区域内的配对设备或可用设备、连接设备,以及在设备之间传输数据。
设置权限
权限是一定要设置的,不然根本无法使用,在Manifest设置以下权限:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
关于6.0以上的蓝牙权限问题
我是基于6.0开发的, 然而我开启了蓝牙却搜索不到其他设备, 感觉很奇怪,搜索了一下,原来:还要添加一个权限(其中一个):
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
设置蓝牙
获取BluetoothAdapter
所有蓝牙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<BluetoothDevice> mDevices; private List<Bluetooth> mBluetoothList; public OnReceiverListener mOnReceiverListener; public BluetoothReceiver(List<BluetoothDevice> devices, List<Bluetooth> 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<Bluetooth> bluetooths, List<BluetoothDevice> devices); void showText(); } }
注意: 别忘了再Activity里注册和销毁Receiver
BluetoothActivity里的扫描
直接调用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<Bluetooth> bluetooths, List<BluetoothDevice> 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<BluetoothDevice> 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.
获取BluetoothServerSocket
通过调用listenUsingRfcommWithServiceRecord(String , UUID)获取BluetoothServerSocket.其中字符串是任意名称, UUID必须唯一标识, 两个UUID必须匹配.
UUID: 通用唯一标识符, UUID足够庞大,可以使用随机, 可使用UUID.fromString(String)初始化
侦听连接
调用accept()来侦听, 需要放在子线程中, 只有当发送的请求中的UUID与服务器套接字UUID相匹配时, 才会被接受.返回已连接的BluetoothSocket.
释放Server
如果不需要与更多的连接,那么可以调用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接收到请求.
获取BluetoothSocket
先获取需要连接设备的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(); } }
总结
这里简单的说了下蓝牙的连接和管理,其实差不多都是官网的教程,百度到的教程十分的少,都是去官网看的,官网也有中文版,所以担心自己英语不好的兄弟们不要怕,就是干。
完整版的篇章:http://blog.csdn.net/williamchew/article/details/69566878
完整版对蓝牙连接的Socket线程进行了优化, 并使用了管理类,可能看起来会比较难懂,其实很简单,也有写的不好的地方,请原谅。