项目要求实现蓝牙设备搜索连接,Android为蓝牙技术提供了4个工具类,分别是蓝牙适配器BluetoothAdapter、蓝牙设备BluetoothDevice、蓝牙服务端套接字BluetoothServerSocket和蓝牙客户端套接字BluetoothSocket。
蓝牙适配器BluetoothAdapter
BluetoothAdapter的作用其实跟其它的**Manger差不多,可以把它当作蓝牙管理器。下面是BluetoothAdapter的常用方法说明。
getDefaultAdapter:静态方法,获取默认的蓝牙适配器对象;
enable:打开蓝牙功能;
disable:关闭蓝牙功能;
isEnable:判断蓝牙功能是否打开;
startDiscovery:开始搜索周围的蓝牙设备;
cancelDiscovery:取消搜索操作;
isDiscovering:判断当前是否正在搜索设备;
getBondedDevices:获取已绑定的设备列表;
setName:设置本机的蓝牙名称;
getName:获取本机的蓝牙名称;
getAddress:获取本机的蓝牙地址;
getRemoteDevice:根据蓝牙地址获取远程的蓝牙设备;
getState:获取本地蓝牙适配器的状态;
listenUsingRfcommWithServiceRecord:根据名称和UUID创建并返回BluetoothServiceSocket;
listenUsingRfcommOn:根据渠道编号创建并返回BluetoothServiceSocket。
蓝牙设备BluetoothDevice
BluetoothDevice用于指代某个蓝牙设备,通常表示对方设备。BluetoothAdapter管理的是本机蓝牙设备。下面是BluetoothDevice的常用方法说明。
- getName:获得该设备的名称;
- getAddress:获得该设备的地址;
- getBondState:获得该设备的绑定状态;
- createBond:创建匹配对象;
- createRfcommSocketToServiceRecord:根据UUID创建并返回一个BluetoothSocket。
蓝牙服务器套接字BluetoothServiceSocket
BluetoothServiceSocket是服务端的Socket,用来接收客户端的Socket连接请求。下面是常用的方法说明。
accept:监听外部的蓝牙连接请求;
close:关闭服务端的蓝牙监听。
蓝牙客户端套接字BluetoothSocket
BluetoothSocket是客户端的Socket,用于与对方设备进行数据通信。下面是常用的方法说明。
- connect:建立蓝牙的socket连接;
- close:关闭蓝牙的socket连接;
- getInputStream:获取socket连接的输入流对象;
- getOutputStream:获取socket连接的输出流对象;
- getRemoteDevice:获取远程设备信息。
layout\activity_bluetooth.xml界面布局代码如下:界面布局代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<!--头部-->
<RelativeLayout
android:id="@+id/rl_title"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/color_2570fd"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="22sp"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
/>
</RelativeLayout>
<LinearLayout
android:id="@+id/ll_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp"
android:layout_below="@id/rl_title"
>
<CheckBox
android:id="@+id/ck_bluetooth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="@null"
android:checked="false"
android:drawableLeft="@drawable/ck_status_selector"
android:text="蓝牙"
android:textColor="#ff000000"
android:textSize="17sp" />
<TextView
android:id="@+id/tv_discovery"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="right|center"
android:textColor="#ff000000"
android:textSize="17sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_text"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal"
android:layout_below="@id/ll_switch"
>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:gravity="center"
android:text="名称"
android:textColor="#ff000000"
android:textSize="17sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="5"
android:gravity="center"
android:text="地址"
android:textColor="#ff000000"
android:textSize="17sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:text="状态"
android:textColor="#ff000000"
android:textSize="17sp" />
</LinearLayout>
<ListView
android:id="@+id/lv_bluetooth"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/ll_text"
android:layout_above="@+id/ll_bottom"
android:listSelector="@null"
/>
<LinearLayout
android:id="@+id/ll_bottom"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<Button
android:id="@+id/bt_search"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="搜索"
android:textColor="#ff000000"
android:textSize="14sp" />
<Button
android:id="@+id/bt_pair"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="配对"
android:textColor="#ff000000"
android:textSize="14sp" />
<Button
android:id="@+id/bt_connect"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="连接"
android:textColor="#ff000000"
android:textSize="14sp" />
<Button
android:id="@+id/bt_cancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="取消配对"
android:textColor="#ff000000"
android:textSize="14sp" />
</LinearLayout>
</RelativeLayout>
BluetoothActivity.java逻辑代码如下:
package com.example.bluetoothtest; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.UUID; import android.Manifest; 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.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ListView; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.example.bluetoothtest.adapter.BlueListAdapter; import com.example.bluetoothtest.bean.BlueDevice; import com.example.bluetoothtest.thread.ReadThread; import com.example.bluetoothtest.thread.WriteThread; import com.example.bluetoothtest.util.CommonUtil; public class BluetoothActivity extends AppCompatActivity implements OnClickListener, OnItemClickListener, OnCheckedChangeListener { private static final String TAG = "BluetoothActivity ";
private CheckBox ck_bluetooth; private TextView tv_discovery; private ListView lv_bluetooth; private Button bt_search; private Button bt_pair; private Button bt_connect; private Button bt_cancel; private BluetoothAdapter mBluetooth; private ArrayList<BlueDevice> mDeviceList = new ArrayList<BlueDevice>(); private BlueListAdapter adapter; private BluetoothDevice bluetoothDevice; private BluetoothSocket mBlueSocket; private InputStream inputStream;//输入流 private OutputStream outputStream;//输出流 private WriteThread writeThread; private ReadThread readThread; private boolean isConnect = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bluetooth); bluetoothPermissions(); ck_bluetooth = findViewById(R.id.ck_bluetooth); tv_discovery = findViewById(R.id.tv_discovery); lv_bluetooth = findViewById(R.id.lv_bluetooth); bt_starmap = findViewById(R.id.bt_starmap); bt_location = findViewById(R.id.bt_location); bt_search = findViewById(R.id.bt_search); bt_pair = findViewById(R.id.bt_pair); bt_connect = findViewById(R.id.bt_connect); bt_cancel = findViewById(R.id.bt_cancel); bt_starmap.setOnClickListener(this); bt_location.setOnClickListener(this); bt_search.setOnClickListener(this); bt_pair.setOnClickListener(this); bt_connect.setOnClickListener(this); bt_cancel.setOnClickListener(this); if (CommonUtil.getBlueToothStatus(this) == true) { ck_bluetooth.setChecked(true); } ck_bluetooth.setOnCheckedChangeListener(this); mBluetooth = BluetoothAdapter.getDefaultAdapter(); if (mBluetooth == null) { Toast.makeText(this, "本机未找到蓝牙功能", Toast.LENGTH_SHORT).show(); finish(); } beginDiscovery(); } // 定义获取基于地理位置的动态权限 private void bluetoothPermissions() { if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions(this, new String[]{ android.Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 1); } } /** * 重写onRequestPermissionsResult方法 * 获取动态权限请求的结果,再开启蓝牙 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (CommonUtil.getBlueToothStatus(this) == true) { ck_bluetooth.setChecked(true); } ck_bluetooth.setOnCheckedChangeListener(this); mBluetooth = BluetoothAdapter.getDefaultAdapter(); if (mBluetooth == null) { Toast.makeText(this, "本机未找到蓝牙功能", Toast.LENGTH_SHORT).show(); finish(); } beginDiscovery(); } else { Toast.makeText(this, "用户拒绝了权限", Toast.LENGTH_SHORT).show(); } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView.getId() == R.id.ck_bluetooth) { if (isChecked == true) { beginDiscovery(); Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); startActivityForResult(intent, 1); } else { cancelDiscovery(); CommonUtil.setBlueToothStatus(this, false); mDeviceList.clear(); adapter.notifyDataSetChanged(); } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (requestCode == 1) { if (resultCode == RESULT_OK) { Toast.makeText(this, "允许本地蓝牙被附近的其它蓝牙设备发现", Toast.LENGTH_SHORT).show(); } else if (resultCode == RESULT_CANCELED) { Toast.makeText(this, "不允许蓝牙被附近的其它蓝牙设备发现", Toast.LENGTH_SHORT).show(); } } } private void beginDiscovery() { if (mBluetooth.isDiscovering() != true) { mDeviceList.clear(); adapter = new BlueListAdapter(BluetoothActivity.this, mDeviceList); lv_bluetooth.setAdapter(adapter); lv_bluetooth.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); tv_discovery.setText("正在搜索蓝牙设备"); mBluetooth.startDiscovery(); } } private void cancelDiscovery() { // tv_discovery.setText("取消搜索蓝牙设备"); if (mBluetooth.isDiscovering() == true) { mBluetooth.cancelDiscovery(); } } @Override protected void onStart() { super.onStart(); blueReceiver = new BluetoothReceiver(); //需要过滤多个动作,则调用IntentFilter对象的addAction添加新动作 IntentFilter foundFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); foundFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); foundFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); registerReceiver(blueReceiver, foundFilter); } private BluetoothReceiver blueReceiver; private class BluetoothReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // Log.d(TAG, "onReceive action=" + action); // 获得已经搜索到的蓝牙设备 if (action.equals(BluetoothDevice.ACTION_FOUND)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); BlueDevice item = new BlueDevice(device.getName(), device.getAddress(), device.getBondState() - 10); for (int i = 0; i < mDeviceList.size(); i++) { if (item.address.equals(mDeviceList.get(i).address)) { mDeviceList.remove(i);//去掉重复项 } } mDeviceList.add(item); adapter = new BlueListAdapter(BluetoothActivity.this, mDeviceList); lv_bluetooth.setAdapter(adapter); lv_bluetooth.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); lv_bluetooth.setOnItemClickListener(BluetoothActivity.this); } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { tv_discovery.setText("蓝牙设备搜索完成"); } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() == BluetoothDevice.BOND_BONDING) { tv_discovery.setText("正在配对" + device.getName()); } else if (device.getBondState() == BluetoothDevice.BOND_BONDED) { tv_discovery.setText("完成配对" + device.getName()); refreshAddress(device.getAddress(), BlueListAdapter.BINDED); } else if (device.getBondState() == BluetoothDevice.BOND_NONE) { tv_discovery.setText("取消配对" + device.getName()); } } } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { cancelDiscovery(); BlueDevice item = mDeviceList.get(position); bluetoothDevice = mBluetooth.getRemoteDevice(item.address); adapter.setSelectedItem(position); adapter.notifyDataSetChanged(); // Toast.makeText(BluetoothActivity.this, "选择" + bluetoothDevice.getAddress(), Toast.LENGTH_SHORT).show(); } public void createBond(BluetoothDevice device) { Method createBondMethod = null; try { createBondMethod = BluetoothDevice.class.getMethod("createBond"); Boolean result = (Boolean) createBondMethod.invoke(device); } catch (Exception e) { e.printStackTrace(); } } public void cancelBond(BluetoothDevice device) { Method createBondMethod = null; try { createBondMethod = BluetoothDevice.class.getMethod("removeBond"); Boolean returnValue = (Boolean) createBondMethod.invoke(device); tv_discovery.setText("取消配对" + device.getAddress()); refreshAddress(device.getAddress(), BlueListAdapter.UNBIND); isConnect = false; if (readThread != null) { readThread.interrupt(); readThread = null; } } catch (Exception e) { e.printStackTrace(); } } public void connetBle(final BluetoothDevice device) { Log.d(TAG, "---开始连接蓝牙=" + device.getName()); try { UUID SERIAL_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); // bluetooth serial port service final BluetoothSocket socket = device.createRfcommSocketToServiceRecord(SERIAL_UUID); //加密传输,Android系统强制配对,弹窗显示配对码 //这里建立蓝牙连接 socket.connect() new Thread() { @Override public void run() { try { socket.connect(); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); if (readThread != null) { readThread.interrupt(); readThread = null; } readThread = new ReadThread(inputStream); readThread.start(); writeThread = new WriteThread(outputStream); writeThread.start(); } catch (IOException e) { Log.d(TAG, "---connetBle e=" + e.toString()); } } }.start(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(BluetoothActivity.this, "连接成功", Toast.LENGTH_SHORT).show(); onBlueConnect(device.getAddress(), socket); } }); } catch (Exception e) { e.printStackTrace(); } } //客户端主动连接 public void onBlueConnect(String address, BluetoothSocket socket) { mBlueSocket = socket; tv_discovery.setText("连接成功"); isConnect = true; refreshAddress(address, BlueListAdapter.CONNECTED); } //刷新蓝牙状态 private void refreshAddress(String address, int state) { for (int i = 0; i < mDeviceList.size(); i++) { BlueDevice item = mDeviceList.get(i); if (item.address.equals(address) == true) { item.state = state; mDeviceList.set(i, item); } } adapter.notifyDataSetChanged(); } @Override public void onClick(View v) { int id = v.getId(); switch (id) { case R.id.bt_search: beginDiscovery(); break; case R.id.bt_pair: createBond(bluetoothDevice); break; case R.id.bt_connect: connetBle(bluetoothDevice); break; case R.id.bt_cancel: cancelBond(bluetoothDevice); break; default: break; } } @Override protected void onDestroy() { super.onDestroy(); cancelDiscovery(); unregisterReceiver(blueReceiver); if (mBlueSocket != null) { try { mBlueSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
添加蓝牙所需的相应权限:
<!-- 蓝牙 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
<!--基于地理位置-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
6.0以上系统记得动态获取ACCESS_COARSE_LOCATION权限,不然可能会出现蓝牙设备检测不到。
Demo程序运行效果界面截图如下:
socket.connect();蓝牙连接成功之后,根据自己的需要做后续处理,楼主这里是通过InputStream和OutputStream来做数据的读写操作,所以各自开了一个读写进程。关于蓝牙连接就这么多,希望对大家有帮助呀~