android 蓝牙连接端(客户端)封装
0.权限 AndroidManifest.xml
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<activity
android:name=".DeviceListActivity"
android:configChanges="screenSize|keyboardHidden|orientation"
android:launchMode="singleInstance"
android:screenOrientation="portrait"/>
1.设备列表布局 activity_device_list.xml (主要就一个listview了)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tvTitle" android:layout_width="match_parent" android:layout_height="48dp" android:gravity="center" android:text="扫描到的蓝牙"/> <ListView android:id="@+id/listViewMessage" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:fastScrollEnabled="true"/> </LinearLayout>
2.设备列表java代码 DeviceListActivity.java
package de.bvb.bluetoothchat; import android.app.Activity; import android.app.Dialog; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; import java.io.IOException; import java.util.ArrayList; import java.util.List; import de.bvb.bluetoothchat.utils.BlueToothConnectCallback; import de.bvb.bluetoothchat.utils.BluetoothDeviceInfo; import de.bvb.bluetoothchat.utils.ClientUtil; /** * Created by Administrator on 2017/06/01. */ public class DeviceListActivity extends Activity implements AdapterView.OnItemClickListener { List<String> list; ArrayAdapter<String> adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_device_list); ListView listView = (ListView) findViewById(R.id.listViewMessage); list = new ArrayList<>(); adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, list); listView.setAdapter(adapter); listView.setOnItemClickListener(this); ClientUtil.getInstance().onCreate(this); ClientUtil.getInstance().setOnFoundUnBondDeviceListener(new ClientUtil.OnFoundUnBondDeviceListener() { @Override public void foundUnBondDevice(BluetoothDevice unBondDevice) { list.add(unBondDevice.getName() + "|" + unBondDevice.getAddress()); adapter.notifyDataSetChanged(); } }); } @Override protected void onResume() { super.onResume(); List<BluetoothDeviceInfo> bluetoothDeviceInfoList = ClientUtil.getInstance().scanDevice(); list.clear(); for (BluetoothDeviceInfo bluetoothDeviceInfo : bluetoothDeviceInfoList) { list.add(bluetoothDeviceInfo.toString()); adapter.notifyDataSetChanged(); } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final Dialog dialog = new Dialog(this); dialog.setTitle("正在连接.."); dialog.show(); String macAddress = list.get(position).split("\\|")[1];// ClientUtil.getInstance().connectRemoteDevice(macAddress, new BlueToothConnectCallback() { @Override public void connecting(String serverBlueToothAddress) { } @Override public void connectSuccess(String serverBlueToothAddress) { dialog.dismiss(); Toast.makeText(DeviceListActivity.this, "连接成功", Toast.LENGTH_SHORT).show(); startActivity(new Intent(DeviceListActivity.this, ClientActivity.class)); } @Override public void connectFailure(IOException e) { dialog.dismiss(); Toast.makeText(DeviceListActivity.this, "连接失败..", Toast.LENGTH_SHORT).show(); } }); } @Override protected void onDestroy() { super.onDestroy(); ClientUtil.getInstance().unregisterReceiver(this); } }
3.通信页面调用代码(收消息,发消息)
// 注册收到消息以后的事件 ClientUtil.getInstance().setOnReceivedMessageListener(new ReceivedMessageListener() { @Override public void onReceiveMessage(final String messageContent) { list.add(new MessageEntity(messageContent, true)); listViewAdapterMessage.setData(list); } @Override public void onConnectionInterrupt(IOException e) { btnSend.setEnabled(false); etMessage.setEnabled(false); Toast.makeText(ClientActivity.this, "连接中断", Toast.LENGTH_SHORT).show(); startActivity(new Intent(ClientActivity.this, DeviceListActivity.class)); } });
// 发送消息 ClientUtil.getInstance().sendMessage(message); list.add(new MessageEntity(message, false)); listViewAdapterMessage.setData(list);
4.工具类
package de.bvb.bluetoothchat.utils; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.text.TextUtils; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Closeable; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; /** * 客户端(连接端)工具类 */ public class ClientUtil { public static final String TAG = "BluetoothManagerUtil"; /////////////////////////////////////////////////////////////////////////// // 单例模式 private ClientUtil() { } public static synchronized ClientUtil getInstance() { return SingletonHolder.instance; } private static final class SingletonHolder { private static ClientUtil instance = new ClientUtil(); } /////////////////////////////////////////////////////////////////////////// private String serverBlueToothAddress; //连接蓝牙地址 private BluetoothSocket socket = null; // 客户端socket private BluetoothAdapter bluetoothAdapter; /** 打开蓝牙,注册扫描蓝牙的广播 onCreate()中执行.连接页面调用 */ public void onCreate(Activity activity) { registerBluetoothScanReceiver(activity); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (null != bluetoothAdapter) { //本地蓝牙存在... if (!bluetoothAdapter.isEnabled()) { //判断蓝牙是否被打开... // 发送打开蓝牙的意图,系统会弹出一个提示对话框,打开蓝牙是需要传递intent的... Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); //打开本机的蓝牙功能...使用startActivityForResult()方法...这里我们开启的这个Activity是需要它返回执行结果给主Activity的... activity.startActivityForResult(enableIntent, Activity.RESULT_FIRST_USER); Intent displayIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); // 设置蓝牙的可见性,最大值3600秒,默认120秒,0表示永远可见(作为客户端,可见性可以不设置,服务端必须要设置) displayIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0); //这里只需要开启另一个activity,让其一直显示蓝牙...没必要把信息返回..因此调用startActivity() activity.startActivity(displayIntent); // 直接打开蓝牙 bluetoothAdapter.enable();//这步才是真正打开蓝牙的部分.... LogUtil.d(TAG, "打开蓝牙成功"); } else { LogUtil.d(TAG, "蓝牙已经打开了..."); } } else { LogUtil.d(TAG, "当前设备没有蓝牙模块"); } } /** 扫描设备 onResume()中执行.连接页面调用 */ public List<BluetoothDeviceInfo> scanDevice() { if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { LogUtil.e(TAG, "蓝牙状态异常"); return null; } List<BluetoothDeviceInfo> bluetoothDeviceInfoList = new ArrayList<>(); if (bluetoothAdapter.isDiscovering()) { // 如果正在处于扫描过程... /** 停止扫描 */ bluetoothAdapter.cancelDiscovery(); // 取消扫描... } else { // 每次扫描前都先判断一下是否存在已经配对过的设备 Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { BluetoothDeviceInfo bluetoothDeviceInfo; for (BluetoothDevice device : pairedDevices) { bluetoothDeviceInfo = new BluetoothDeviceInfo(device.getName() + "", device.getAddress() + ""); bluetoothDeviceInfoList.add(bluetoothDeviceInfo); LogUtil.d(TAG, "已经匹配过的设备:" + bluetoothDeviceInfo.toString()); } } else { LogUtil.d(TAG, "没有已经配对过的设备"); } /* 开始搜索 */ bluetoothAdapter.startDiscovery(); } return bluetoothDeviceInfoList; } /** 通过Mac地址去尝试连接一个设备.连接页面调用 */ public void connectRemoteDevice(final String serverBlueToothAddress, BlueToothConnectCallback connectInterface) { this.serverBlueToothAddress = serverBlueToothAddress; final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(serverBlueToothAddress); ThreadPoolUtil.execute(new ConnectRunnable(device, connectInterface)); } /** 广播反注册.连接页面调用 */ public void unregisterReceiver(Activity activity) { if (receiver != null && receiver.getAbortBroadcast()) { activity.unregisterReceiver(receiver); } } /** 发送消息,在通信页面使用 */ public void sendMessage(String message) { try { writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); writer.write(message + "\n"); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } /** 收到消息的监听事件,在通信页面注册这个事件 */ public void setOnReceivedMessageListener(ReceivedMessageListener listener) { if (listener != null) { // 可以开启读数据线程 // MainHandler.getInstance().post(new ReadRunnable(listener)); ThreadPoolUtil.execute(new ReadRunnable(listener)); } } /** 关闭蓝牙,在app退出时调用 */ public void onExit() { if (bluetoothAdapter != null) { bluetoothAdapter.cancelDiscovery(); // 关闭蓝牙 bluetoothAdapter.disable(); } closeCloseable(writer, socket); } /** 连接线程 */ class ConnectRunnable implements Runnable { private BluetoothDevice device; // 蓝牙设备 private BlueToothConnectCallback connectInterface; public ConnectRunnable(BluetoothDevice device, BlueToothConnectCallback connectInterface) { this.device = device; this.connectInterface = connectInterface; } @Override public void run() { if (null != device) { try { if (socket != null) { closeCloseable(socket); } socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); // 连接 LogUtil.d(TAG, "正在连接 " + serverBlueToothAddress); connectInterface.connecting(serverBlueToothAddress); // Message.obtain(handler, MESSAGE_TYPE_SEND, "请稍候,正在连接服务器: " + serverBlueToothAddress).sendToTarget(); socket.connect(); MainHandler.getInstance().post(new Runnable() { @Override public void run() { connectInterface.connectSuccess(serverBlueToothAddress); LogUtil.d(TAG, "连接 " + serverBlueToothAddress + " 成功 "); } }); // 如果实现了连接,那么服务端和客户端就共享一个RFFCOMM信道... // Message.obtain(handler, MESSAGE_TYPE_SEND, "已经连接上服务端!可以发送信息").sendToTarget(); // 如果连接成功了...这步就会执行...更新UI界面...否则走catch(IOException e) // Message.obtain(handler, MESSAGE_ID_REFRESH_UI).sendToTarget(); // 屏蔽点击事件 // listViewMessage.setOnItemClickListener(null); } catch (final IOException e) { MainHandler.getInstance().post(new Runnable() { @Override public void run() { connectInterface.connectFailure(e); LogUtil.d(TAG, "连接" + serverBlueToothAddress + "失败 " + e.getMessage()); } }); // e.printStackTrace(); } } } } private BufferedWriter writer = null; class ReadRunnable implements Runnable { private ReceivedMessageListener listener; public ReadRunnable(ReceivedMessageListener listener) { this.listener = listener; } public void run() { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String content; while (!TextUtils.isEmpty(content = reader.readLine())) { final String finalContent = content; MainHandler.getInstance().post(new Runnable() { @Override public void run() { listener.onReceiveMessage(finalContent); } }); // Message.obtain(handler, MESSAGE_TYPE_RECEIVED, content).sendToTarget(); } } catch (final IOException e) { MainHandler.getInstance().post(new Runnable() { @Override public void run() { LogUtil.d(TAG, "连接中断 " + e.getMessage()); listener.onConnectionInterrupt(e); } }); // 连接断开 // Message.obtain(handler, MESSAGE_ID_DISCONNECT).sendToTarget(); } closeCloseable(reader); } } private BroadcastReceiver registerBluetoothScanReceiver(Activity activity) { IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); activity.registerReceiver(receiver, filter); return receiver; } public void setOnFoundUnBondDeviceListener(OnFoundUnBondDeviceListener onFoundUnBondDeviceListener) { this.onFoundUnBondDeviceListener = onFoundUnBondDeviceListener; } private OnFoundUnBondDeviceListener onFoundUnBondDeviceListener; public interface OnFoundUnBondDeviceListener { void foundUnBondDevice(BluetoothDevice unBondDevice); } private void closeCloseable(Closeable... closeable) { if (null != closeable && closeable.length > 0) { for (int i = 0; i < closeable.length; i++) { if (closeable[i] != null) { try { closeable[i].close(); } catch (IOException e) { e.printStackTrace(); } finally { closeable[i] = null; } } } } } /** * 下面是注册receiver监听,注册广播...说一下为什么要注册广播... * 因为蓝牙的通信,需要进行设备的搜索,搜索到设备后我们才能够实现连接..如果没有搜索,那还谈什么连接... * 因此我们需要搜索,搜索的过程中系统会自动发出三个广播...这三个广播为: * ACTION_DISCOVERY_START:开始搜索... * ACTION_DISCOVERY_FINISH:搜索结束... * ACTION_FOUND:正在搜索...一共三个过程...因为我们需要对这三个响应过程进行接收,然后实现一些功能,因此 * 我们需要对广播进行注册...知道广播的人应该都知道,想要对广播进行接收,必须进行注册,否则是接收不到的... */ private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) {//正在搜索过程... // 通过EXTRA_DEVICE附加域来得到一个BluetoothDevice设备 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // 如果这个设备是不曾配对过的,添加到list列表 if (device.getBondState() != BluetoothDevice.BOND_BONDED) { if (null != onFoundUnBondDeviceListener) { LogUtil.d(TAG, "发现没有配对过的设备:" + parseDevice2BluetoothDeviceInfo(device)); onFoundUnBondDeviceListener.foundUnBondDevice(device); } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {//搜索结束后的过程... LogUtil.d(TAG, "没有发现设备"); } } } }; private String parseDevice2BluetoothDeviceInfo(BluetoothDevice device) { if (device == null) { return "device == null"; } return new BluetoothDeviceInfo(device.getName(), device.getAddress()).toString(); } }