【Android App】蓝牙的设备配对、音频传输、点对点通信的讲解及实战(附源码和演示 超详细)

news2024/11/23 20:59:34

需要源码请点赞关注收藏后评论区留言私信~~~

一、蓝牙设备配对

Android提供了蓝牙模块的管理工具,名叫BluetoothAdapter。下面是BluetoothAdapter类常用的方法说明:

getDefaultAdapter:获取默认的蓝牙适配器。

getState:获取蓝牙的开关状态。

enable:启用蓝牙功能。

disable:禁用蓝牙功能。

getBondedDevices:获取已配对的设备集合。

getRemoteDevice:根据设备地址获取远程的设备对象。

startDiscovery:开始搜索周围的蓝牙设备。

cancelDiscovery:取消搜索周围的蓝牙设备。

蓝牙配对实现步骤如下

(1)初始化蓝牙适配器

(2)启用蓝牙功能

(3)搜索周围的蓝牙设备

(4)与指定的蓝牙设备配对

首先会搜索附近的蓝牙并显示他们的相关信息 

 配对完成后各自手机上申请如下

 代码如下

package com.example.iot;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.iot.adapter.BlueListAdapter;
import com.example.iot.bean.BlueDevice;
import com.example.iot.util.BluetoothUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@SuppressLint("SetTextI18n")
public class BluetoothPairActivity extends AppCompatActivity implements
        OnCheckedChangeListener, OnItemClickListener {
    private static final String TAG = "BluetoothPairActivity";
    private CheckBox ck_bluetooth; // 声明一个复选框对象
    private TextView tv_discovery; // 声明一个文本视图对象
    private ListView lv_bluetooth; // 声明一个用于展示蓝牙设备的列表视图对象
    private BluetoothAdapter mBluetooth; // 声明一个蓝牙适配器对象
    private BlueListAdapter mListAdapter; // 声明一个蓝牙设备的列表适配器对象
    private List<BlueDevice> mDeviceList = new ArrayList<>(); // 蓝牙设备列表
    private Handler mHandler = new Handler(Looper.myLooper()); // 声明一个处理器对象
    private int mOpenCode = 1; // 是否允许扫描蓝牙设备的选择对话框返回结果代码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth_pair);
        initBluetooth(); // 初始化蓝牙适配器
        ck_bluetooth = findViewById(R.id.ck_bluetooth);
        tv_discovery = findViewById(R.id.tv_discovery);
        lv_bluetooth = findViewById(R.id.lv_bluetooth);
        ck_bluetooth.setOnCheckedChangeListener(this);
        if (BluetoothUtil.getBlueToothStatus()) { // 已经打开蓝牙
            ck_bluetooth.setChecked(true);
        }
        initBlueDevice(); // 初始化蓝牙设备列表
    }

    // 初始化蓝牙适配器
    private void initBluetooth() {
        // 获取系统默认的蓝牙适配器
        mBluetooth = BluetoothAdapter.getDefaultAdapter();
        if (mBluetooth == null) {
            Toast.makeText(this, "当前设备未找到蓝牙功能", Toast.LENGTH_SHORT).show();
            finish(); // 关闭当前页面
        }
    }

    // 初始化蓝牙设备列表
    private void initBlueDevice() {
        mDeviceList.clear();
        // 获取已经配对的蓝牙设备集合
        Set<BluetoothDevice> bondedDevices = mBluetooth.getBondedDevices();
        for (BluetoothDevice device : bondedDevices) {
            mDeviceList.add(new BlueDevice(device.getName(), device.getAddress(), device.getBondState()));
        }
        if (mListAdapter == null) { // 首次打开页面,则创建一个新的蓝牙设备列表
            mListAdapter = new BlueListAdapter(this, mDeviceList);
            lv_bluetooth.setAdapter(mListAdapter);
            lv_bluetooth.setOnItemClickListener(this);
        } else { // 不是首次打开页面,则刷新蓝牙设备列表
            mListAdapter.notifyDataSetChanged();
        }
    }

    private Runnable mDiscoverable = new Runnable() {
        public void run() {
            // Android8.0要在已打开蓝牙功能时才会弹出下面的选择窗
            if (BluetoothUtil.getBlueToothStatus()) { // 已经打开蓝牙
                // 弹出是否允许扫描蓝牙设备的选择对话框
                Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
                startActivityForResult(intent, mOpenCode);
            } else {
                mHandler.postDelayed(this, 1000);
            }
        }
    };

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (buttonView.getId() == R.id.ck_bluetooth) {
            if (isChecked) { // 开启蓝牙功能
                ck_bluetooth.setText("蓝牙开");
                if (!BluetoothUtil.getBlueToothStatus()) { // 还未打开蓝牙
                    BluetoothUtil.setBlueToothStatus(true); // 开启蓝牙功能
                }
                mHandler.post(mDiscoverable);
            } else { // 关闭蓝牙功能
                ck_bluetooth.setText("蓝牙关");
                cancelDiscovery(); // 取消蓝牙设备的搜索
                BluetoothUtil.setBlueToothStatus(false); // 关闭蓝牙功能
                initBlueDevice(); // 初始化蓝牙设备列表
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode == mOpenCode) { // 来自允许蓝牙扫描的对话框
            // 延迟50毫秒后启动蓝牙设备的刷新任务
            mHandler.postDelayed(mRefresh, 50);
            if (resultCode == RESULT_OK) {
                Toast.makeText(this, "允许本地蓝牙被附近的其他蓝牙设备发现",
                        Toast.LENGTH_SHORT).show();
            } else if (resultCode == RESULT_CANCELED) {
                Toast.makeText(this, "不允许蓝牙被附近的其他蓝牙设备发现",
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    // 定义一个刷新任务,每隔两秒刷新扫描到的蓝牙设备
    private Runnable mRefresh = new Runnable() {
        @Override
        public void run() {
            beginDiscovery(); // 开始扫描周围的蓝牙设备
            // 延迟30秒后再次启动蓝牙设备的刷新任务
            mHandler.postDelayed(this, 30*1000);
        }
    };

    // 开始扫描周围的蓝牙设备
    private void beginDiscovery() {
        // 如果当前不是正在搜索,则开始新的搜索任务
        if (!mBluetooth.isDiscovering()) {
            initBlueDevice(); // 初始化蓝牙设备列表
            tv_discovery.setText("正在搜索蓝牙设备");
            mBluetooth.startDiscovery(); // 开始扫描周围的蓝牙设备
        }
    }

    // 取消蓝牙设备的搜索
    private void cancelDiscovery() {
        mHandler.removeCallbacks(mRefresh);
        tv_discovery.setText("取消搜索蓝牙设备");
        // 当前正在搜索,则取消搜索任务
        if (mBluetooth.isDiscovering()) {
            mBluetooth.cancelDiscovery(); // 取消扫描周围的蓝牙设备
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        mHandler.postDelayed(mRefresh, 50);
        // 需要过滤多个动作,则调用IntentFilter对象的addAction添加新动作
        IntentFilter discoveryFilter = new IntentFilter();
        discoveryFilter.addAction(BluetoothDevice.ACTION_FOUND);
        discoveryFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        discoveryFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        // 注册蓝牙设备搜索的广播接收器
        registerReceiver(discoveryReceiver, discoveryFilter);
    }

    @Override
    protected void onStop() {
        super.onStop();
        cancelDiscovery(); // 取消蓝牙设备的搜索
        unregisterReceiver(discoveryReceiver); // 注销蓝牙设备搜索的广播接收器
    }

    // 蓝牙设备的搜索结果通过广播返回
    private BroadcastReceiver discoveryReceiver = new 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);
                Log.d(TAG, "name=" + device.getName() + ", state=" + device.getBondState());
                refreshDevice(device, device.getBondState()); // 将发现的蓝牙设备加入到设备列表
            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { // 搜索完毕
                //mHandler.removeCallbacks(mRefresh); // 需要持续搜索就要注释这行
                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());
                    mHandler.postDelayed(mRefresh, 50);
                } else if (device.getBondState() == BluetoothDevice.BOND_NONE) {
                    tv_discovery.setText("取消配对" + device.getName());
                    refreshDevice(device, device.getBondState()); // 刷新蓝牙设备列表
                }
            }
        }
    };

    // 刷新蓝牙设备列表
    private void refreshDevice(BluetoothDevice device, int state) {
        int i;
        for (i = 0; i < mDeviceList.size(); i++) {
            BlueDevice item = mDeviceList.get(i);
            if (item.address.equals(device.getAddress())) {
                item.state = state;
                mDeviceList.set(i, item);
                break;
            }
        }
        if (i >= mDeviceList.size()) {
            mDeviceList.add(new BlueDevice(device.getName(), device.getAddress(), device.getBondState()));
        }
        mListAdapter.notifyDataSetChanged();
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        //cancelDiscovery();
        BlueDevice item = mDeviceList.get(position);
        // 根据设备地址获得远端的蓝牙设备对象
        BluetoothDevice device = mBluetooth.getRemoteDevice(item.address);
        Log.d(TAG, "getBondState="+device.getBondState()+", item.state="+item.state);
        if (device.getBondState() == BluetoothDevice.BOND_NONE) { // 尚未配对
            BluetoothUtil.createBond(device); // 创建配对信息
        } else if (device.getBondState() == BluetoothDevice.BOND_BONDED) { // 已经配对
            boolean isSucc = BluetoothUtil.removeBond(device); // 移除配对信息
            if (!isSucc) {
                refreshDevice(device, BluetoothDevice.BOND_NONE); // 刷新蓝牙设备列表
            }
        }
    }

}

二、蓝牙音频传输 

通过蓝牙连接音箱,进而把手机上的音乐同步到音箱上播放,具体的编码过程主要有以下三个步骤:

(1)定义并设置A2DP的蓝牙代理(A2DP全称是蓝牙音频传输模型协定)

(2)发现蓝牙音箱,并进行配对和连接

(3)定义A2DP的广播接收器,并注册相关广播事件 蓝牙音箱连接成功,接下来播放手机音乐,就会由蓝牙音箱发出声音。

手机上开始播放放可以在音箱中听到音乐 

 

 代码如下

package com.example.iot;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.iot.adapter.BlueListAdapter;
import com.example.iot.bean.BlueDevice;
import com.example.iot.util.BluetoothUtil;
import com.example.iot.widget.AudioPlayer;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@SuppressLint("SetTextI18n")
public class BluetoothA2dpActivity extends AppCompatActivity implements
        OnCheckedChangeListener, OnItemClickListener {
    private static final String TAG = "BluetoothA2dpActivity";
    private Context mContext; // 声明一个上下文对象
    private CheckBox ck_bluetooth; // 声明一个复选框对象
    private TextView tv_discovery; // 声明一个文本视图对象
    private ListView lv_bluetooth; // 声明一个用于展示蓝牙设备的列表视图对象
    private AudioPlayer ap_music; // 声明一个音频播放器对象
    private BluetoothAdapter mBluetooth; // 声明一个蓝牙适配器对象
    private BlueListAdapter mListAdapter; // 声明一个蓝牙设备的列表适配器对象
    private List<BlueDevice> mDeviceList = new ArrayList<>(); // 蓝牙设备列表
    private Handler mHandler = new Handler(Looper.myLooper()); // 声明一个处理器对象
    private int mOpenCode = 1; // 是否允许扫描蓝牙设备的选择对话框返回结果代码

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth_a2dp);
        mContext = this;
        initBluetooth(); // 初始化蓝牙适配器
        ck_bluetooth = findViewById(R.id.ck_bluetooth);
        tv_discovery = findViewById(R.id.tv_discovery);
        lv_bluetooth = findViewById(R.id.lv_bluetooth);
        ap_music = findViewById(R.id.ap_music);
        ck_bluetooth.setOnCheckedChangeListener(this);
        if (BluetoothUtil.getBlueToothStatus()) { // 已经打开蓝牙
            ck_bluetooth.setChecked(true);
        }
        initBlueDevice(); // 初始化蓝牙设备列表
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            Toast.makeText(this, "Android10开始无法调用A2DP的隐藏方法", Toast.LENGTH_SHORT).show();
        }
    }

    // 初始化蓝牙适配器
    private void initBluetooth() {
        // 获取系统默认的蓝牙适配器
        mBluetooth = BluetoothAdapter.getDefaultAdapter();
        if (mBluetooth == null) {
            Toast.makeText(this, "当前设备未找到蓝牙功能", Toast.LENGTH_SHORT).show();
            finish(); // 关闭当前页面
        }
    }

    // 初始化蓝牙设备列表
    private void initBlueDevice() {
        mDeviceList.clear();
        // 获取已经配对的蓝牙设备集合
        Set<BluetoothDevice> bondedDevices = mBluetooth.getBondedDevices();
        for (BluetoothDevice device : bondedDevices) {
            mDeviceList.add(new BlueDevice(device.getName(), device.getAddress(), device.getBondState()));
        }
        if (mListAdapter == null) { // 首次打开页面,则创建一个新的蓝牙设备列表
            mListAdapter = new BlueListAdapter(this, mDeviceList);
            lv_bluetooth.setAdapter(mListAdapter);
            lv_bluetooth.setOnItemClickListener(this);
        } else { // 不是首次打开页面,则刷新蓝牙设备列表
            mListAdapter.notifyDataSetChanged();
        }
    }

    private Runnable mDiscoverable = new Runnable() {
        public void run() {
            // Android8.0要在已打开蓝牙功能时才会弹出下面的选择窗
            if (BluetoothUtil.getBlueToothStatus()) { // 已经打开蓝牙
                // 弹出是否允许扫描蓝牙设备的选择对话框
                Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
                startActivityForResult(intent, mOpenCode);
            } else {
                mHandler.postDelayed(this, 1000);
            }
        }
    };

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (buttonView.getId() == R.id.ck_bluetooth) {
            if (isChecked) { // 开启蓝牙功能
                ck_bluetooth.setText("蓝牙开");
                if (!BluetoothUtil.getBlueToothStatus()) { // 还未打开蓝牙
                    BluetoothUtil.setBlueToothStatus(true); // 开启蓝牙功能
                }
                mHandler.post(mDiscoverable);
            } else { // 关闭蓝牙功能
                ck_bluetooth.setText("蓝牙关");
                cancelDiscovery(); // 取消蓝牙设备的搜索
                BluetoothUtil.setBlueToothStatus(false); // 关闭蓝牙功能
                initBlueDevice(); // 初始化蓝牙设备列表
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode == mOpenCode) { // 来自允许蓝牙扫描的对话框
            // 延迟50毫秒后启动蓝牙设备的刷新任务
            mHandler.postDelayed(mRefresh, 50);
            if (resultCode == RESULT_OK) {
                Toast.makeText(this, "允许本地蓝牙被附近的其他蓝牙设备发现",
                        Toast.LENGTH_SHORT).show();
            } else if (resultCode == RESULT_CANCELED) {
                Toast.makeText(this, "不允许蓝牙被附近的其他蓝牙设备发现",
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

    // 定义一个刷新任务,每隔两秒刷新扫描到的蓝牙设备
    private Runnable mRefresh = new Runnable() {
        @Override
        public void run() {
            beginDiscovery(); // 开始扫描周围的蓝牙设备
            // 延迟2秒后再次启动蓝牙设备的刷新任务
            mHandler.postDelayed(this, 2000);
        }
    };

    // 开始扫描周围的蓝牙设备
    private void beginDiscovery() {
        // 如果当前不是正在搜索,则开始新的搜索任务
        if (!mBluetooth.isDiscovering()) {
            initBlueDevice(); // 初始化蓝牙设备列表
            tv_discovery.setText("正在搜索蓝牙设备");
            mBluetooth.startDiscovery(); // 开始扫描周围的蓝牙设备
        }
    }

    // 取消蓝牙设备的搜索
    private void cancelDiscovery() {
        mHandler.removeCallbacks(mRefresh);
        tv_discovery.setText("取消搜索蓝牙设备");
        // 当前正在搜索,则取消搜索任务
        if (mBluetooth.isDiscovering()) {
            mBluetooth.cancelDiscovery(); // 取消扫描周围的蓝牙设备
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        mHandler.postDelayed(mRefresh, 50);
        // 注册蓝牙设备搜索的广播接收器
        IntentFilter discoveryFilter = new IntentFilter();
        discoveryFilter.addAction(BluetoothDevice.ACTION_FOUND);
        discoveryFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        discoveryFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        registerReceiver(discoveryReceiver, discoveryFilter);
        // 获取A2DP的蓝牙代理
        mBluetooth.getProfileProxy(this, serviceListener, BluetoothProfile.A2DP);
        IntentFilter a2dpFilter = new IntentFilter(); // 创建一个意图过滤器
        // 指定A2DP的连接状态变更广播
        a2dpFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        // 指定A2DP的播放状态变更广播
        a2dpFilter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
        registerReceiver(a2dpReceiver, a2dpFilter); // 注册A2DP连接管理的广播接收器
    }

    @Override
    protected void onStop() {
        super.onStop();
        cancelDiscovery(); // 取消蓝牙设备的搜索
        unregisterReceiver(discoveryReceiver); // 注销蓝牙设备搜索的广播接收器
        unregisterReceiver(a2dpReceiver); // 注销A2DP连接管理的广播接收器
    }

    // 蓝牙设备的搜索结果通过广播返回
    private BroadcastReceiver discoveryReceiver = new 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);
                Log.d(TAG, "name=" + device.getName() + ", state=" + device.getBondState());
                refreshDevice(device, device.getBondState()); // 将发现的蓝牙设备加入到设备列表
            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { // 搜索完毕
                //mHandler.removeCallbacks(mRefresh); // 需要持续搜索就要注释这行
                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());
                    mHandler.postDelayed(mRefresh, 50);
                } else if (device.getBondState() == BluetoothDevice.BOND_NONE) {
                    tv_discovery.setText("取消配对" + device.getName());
                    refreshDevice(device, device.getBondState()); // 刷新蓝牙设备列表
                }
            }
        }
    };

    // 刷新蓝牙设备列表
    private void refreshDevice(BluetoothDevice device, int state) {
        int i;
        for (i = 0; i < mDeviceList.size(); i++) {
            BlueDevice item = mDeviceList.get(i);
            if (item.address.equals(device.getAddress())) {
                item.state = state;
                mDeviceList.set(i, item);
                break;
            }
        }
        if (i >= mDeviceList.size()) {
            mDeviceList.add(new BlueDevice(device.getName(), device.getAddress(), device.getBondState()));
        }
        mListAdapter.notifyDataSetChanged();
    }

    private String mAddress;
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        mAddress = mDeviceList.get(position).address;
        // 根据设备地址获得远端的蓝牙设备对象
        BluetoothDevice device = mBluetooth.getRemoteDevice(mAddress);
        if (device.getBondState() == BluetoothDevice.BOND_NONE) { // 尚未配对
            BluetoothUtil.connectA2dp(bluetoothA2dp, device); // 创建配对信息
        } else if (device.getBondState() == BluetoothDevice.BOND_BONDED) { // 已经配对
            BluetoothUtil.removeBond(device); // 移除配对信息
        } else if (device.getBondState() == BlueListAdapter.CONNECTED) { // 已经建立A2DP连接
            BluetoothUtil.disconnectA2dp(bluetoothA2dp, device); // 断开A2DP连接
        }
    }

    private BluetoothA2dp bluetoothA2dp; // 声明一个蓝牙音频传输对象
    // 定义一个A2DP的服务监听器,类似于Service的绑定方式启停,
    // 也有onServiceConnected和onServiceDisconnected两个接口方法
    private BluetoothProfile.ServiceListener serviceListener = new BluetoothProfile.ServiceListener() {

        // 在服务断开连接时触发
        @Override
        public void onServiceDisconnected(int profile) {
            if (profile == BluetoothProfile.A2DP) {
                Toast.makeText(mContext, "onServiceDisconnected", Toast.LENGTH_SHORT).show();
                bluetoothA2dp = null; // A2DP已连接,则释放A2DP的蓝牙代理
            }
        }

        // 在服务建立连接时触发
        @Override
        public void onServiceConnected(int profile, final BluetoothProfile proxy) {
            if (profile == BluetoothProfile.A2DP) {
                Toast.makeText(mContext, "onServiceConnected", Toast.LENGTH_SHORT).show();
                bluetoothA2dp = (BluetoothA2dp) proxy; // A2DP已连接,则设置A2DP的蓝牙代理
            }
        }
    };

    // 定义一个A2DP连接的广播接收器
    private BroadcastReceiver a2dpReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                // 侦听到A2DP的连接状态变更广播
                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
                    BluetoothDevice device = mBluetooth.getRemoteDevice(mAddress);
                    int connectState = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE,
                            BluetoothA2dp.STATE_DISCONNECTED);
                    if (connectState == BluetoothA2dp.STATE_CONNECTED) {
                        // 收到连接上的广播,则更新设备状态为已连接
                        refreshDevice(device, BlueListAdapter.CONNECTED); // 刷新蓝牙设备列表
                        ap_music.initFromRaw(mContext, R.raw.mountain_and_water);
                        Toast.makeText(mContext, "已连上蓝牙音箱。快来播放音乐试试",
                                Toast.LENGTH_SHORT).show();
                    } else if (connectState == BluetoothA2dp.STATE_DISCONNECTED) {
                        // 收到断开连接的广播,则更新设备状态为已断开
                        refreshDevice(device, BluetoothDevice.BOND_NONE); // 刷新蓝牙设备列表
                        Toast.makeText(mContext, "已断开蓝牙音箱",
                                Toast.LENGTH_SHORT).show();
                    }
                    break;
                // 侦听到A2DP的播放状态变更广播
                case BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED:
                    int playState = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE,
                            BluetoothA2dp.STATE_NOT_PLAYING);
                    if (playState == BluetoothA2dp.STATE_PLAYING) {
                        Toast.makeText(mContext, "蓝牙音箱正在播放",
                                Toast.LENGTH_SHORT).show();
                    } else if (playState == BluetoothA2dp.STATE_NOT_PLAYING) {
                        Toast.makeText(mContext, "蓝牙音箱停止播放",
                                Toast.LENGTH_SHORT).show();
                    }
                    break;
            }
        }
    };

}

三、点对点蓝牙通信

要想不通过网络服务器中转,直接把数据从A手机传给B手机,就要借助于蓝牙技术。 Android为蓝牙技术提供了4个工具类:

(1)蓝牙适配器BuletoothAdapter

(2)蓝牙设备BluetoothDevice

(3)蓝牙服务端套接字BluetoothServerSocket

(4)蓝牙客户端套接字BluetoothSocket

使用蓝牙传输数据的完整步骤

(1)开启蓝牙功能

(2)确认配对并完成绑定

(3)建立蓝牙连接

(4)通过蓝牙发送消息

运行测试效果如下

 代码如下

package com.example.iot;

import androidx.appcompat.app.AppCompatActivity;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.iot.constant.BleConstant;
import com.example.iot.util.BluetoothUtil;
import com.example.iot.util.ChatUtil;
import com.example.iot.util.DateUtil;
import com.example.iot.util.Utils;
import com.example.iot.util.ViewUtil;

import java.util.List;

public class BleServerActivity extends AppCompatActivity {
    private static final String TAG = "BleServerActivity";
    private TextView tv_hint; // 声明一个文本视图对象
    private ScrollView sv_chat; // 声明一个滚动视图对象
    private LinearLayout ll_show; // 声明一个线性视图对象
    private LinearLayout ll_input; // 声明一个线性视图对象
    private EditText et_input; // 声明一个编辑框对象
    private Handler mHandler = new Handler(Looper.myLooper()); // 声明一个处理器对象
    private int dip_margin; // 每条聊天记录的四周空白距离
    private String mMinute = "00:00";

    private BluetoothManager mBluetoothManager; // 声明一个蓝牙管理器对象
    private BluetoothAdapter mBluetoothAdapter; // 声明一个蓝牙适配器对象
    private BluetoothDevice mRemoteDevice; // 声明一个蓝牙设备对象
    private BluetoothGattServer mGattServer; // 声明一个蓝牙GATT服务器对象
    private BluetoothGattCharacteristic mReadChara; // 客户端读取数据的特征值

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ble_server);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮
        initView(); // 初始化视图
        initBluetooth(); // 初始化蓝牙适配器
        mHandler.postDelayed(mAdvertise, 200); // 延迟200毫秒开启低功耗蓝牙广播任务
    }

    // 初始化视图
    private void initView() {
        dip_margin = Utils.dip2px(this, 5);
        tv_hint = findViewById(R.id.tv_hint);
        sv_chat = findViewById(R.id.sv_chat);
        ll_show = findViewById(R.id.ll_show);
        ll_input = findViewById(R.id.ll_input);
        et_input = findViewById(R.id.et_input);
        findViewById(R.id.btn_send).setOnClickListener(v -> sendMesssage());
    }

    // 初始化蓝牙适配器
    private void initBluetooth() {
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "当前设备不支持低功耗蓝牙", Toast.LENGTH_SHORT).show();
            finish(); // 关闭当前页面
        }
        // 获取蓝牙管理器,并从中得到蓝牙适配器
        mBluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter(); // 获取蓝牙适配器
        if (!BluetoothUtil.getBlueToothStatus()) { // 还未打开蓝牙
            BluetoothUtil.setBlueToothStatus(true); // 开启蓝牙功能
        }
    }

    // 创建一个低功耗蓝牙广播任务
    private Runnable mAdvertise = new Runnable() {
        @Override
        public void run() {
            if (BluetoothUtil.getBlueToothStatus()) { // 已经打开蓝牙
                stopAdvertise(); // 停止低功耗蓝牙广播
                String server_name = getIntent().getStringExtra("server_name");
                startAdvertise(server_name); // 开始低功耗蓝牙广播
                tv_hint.setText("“"+server_name+"”服务端正在广播,请等候客户端连接");
            } else {
                mHandler.postDelayed(this, 2000);
            }
        }
    };

    // 开始低功耗蓝牙广播
    private void startAdvertise(String ble_name) {
        // 设置广播参数
        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setConnectable(true) // 是否允许连接
                .setTimeout(0) // 设置超时时间
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
                .build();
        // 设置广播内容
        AdvertiseData advertiseData = new AdvertiseData.Builder()
                .setIncludeDeviceName(true) // 是否把设备名称也广播出去
                .setIncludeTxPowerLevel(true) // 是否把功率电平也广播出去
                .build();
        mBluetoothAdapter.setName(ble_name); // 设置BLE服务端的名称
        // 获取BLE广播器
        BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
        // BLE服务端开始广播,好让别人发现自己
        advertiser.startAdvertising(settings, advertiseData, mAdvertiseCallback);
    }

    // 停止低功耗蓝牙广播
    private void stopAdvertise() {
        if (mBluetoothAdapter != null) {
            // 获取BLE广播器
            BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
            if (advertiser != null) {
                advertiser.stopAdvertising(mAdvertiseCallback); // 停止低功耗蓝牙广播
            }
        }
    }

    // 创建一个低功耗蓝牙广播回调对象
    private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settings) {
            Log.d(TAG, "低功耗蓝牙广播成功:"+settings.toString());
            addService(); // 添加读写服务UUID,特征值等
        }

        @Override
        public void onStartFailure(int errorCode) {
            Log.d(TAG, "低功耗蓝牙广播失败,错误代码为"+errorCode);
        }
    };

    // 添加读写服务UUID,特征值等
    private void addService() {
        BluetoothGattService gattService = new BluetoothGattService(
                BleConstant.UUID_SERVER, BluetoothGattService.SERVICE_TYPE_PRIMARY);
        // 只读的特征值
        mReadChara = new BluetoothGattCharacteristic(BleConstant.UUID_CHAR_READ,
                BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_READ);
        // 只写的特征值
        BluetoothGattCharacteristic charaWrite = new BluetoothGattCharacteristic(BleConstant.UUID_CHAR_WRITE,
                BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_WRITE);
        gattService.addCharacteristic(mReadChara); // 将特征值添加到服务里面
        gattService.addCharacteristic(charaWrite); // 将特征值添加到服务里面
        // 开启GATT服务器等待客户端连接
        mGattServer = mBluetoothManager.openGattServer(this, mGattCallback);
        mGattServer.addService(gattService); // 向GATT服务器添加指定服务
    }

    private BluetoothGattServerCallback mGattCallback = new BluetoothGattServerCallback() {
        // BLE连接的状态发生变化时回调
        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            super.onConnectionStateChange(device, status, newState);
            Log.d(TAG, "onConnectionStateChange device=" + device.toString() + " status=" + status + " newState=" + newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mRemoteDevice = device;
                runOnUiThread(() -> {
                    String desc = String.format("已连接BLE客户端,对方名称为“%s”,MAC地址为%s",
                            device.getName(), device.getAddress());
                    tv_hint.setText(desc);
                    ll_input.setVisibility(View.VISIBLE);
                });
            }
        }

        // 收到BLE客户端写入请求时回调
        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic chara, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onCharacteristicWriteRequest(device, requestId, chara, preparedWrite, responseNeeded, offset, value);
            String message = new String(value); // 把客户端发来的数据转成字符串
            Log.d(TAG, "收到了客户端发过来的数据 " + message);
            // 向GATT客户端发送应答,告诉它成功收到了要写入的数据
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, chara.getValue());
            runOnUiThread(() -> appendChatMsg(message, false)); // 往聊天窗口添加聊天消息
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopAdvertise(); // 停止低功耗蓝牙广播
        if (mGattServer != null) {
            mGattServer.close(); // 关闭GATT服务器
        }
    }

    // 发送聊天消息
    private void sendMesssage() {
        String message = et_input.getText().toString();
        if (TextUtils.isEmpty(message)) {
            Toast.makeText(this, "请先输入聊天消息", Toast.LENGTH_SHORT).show();
            return;
        }
        et_input.setText("");
        ViewUtil.hideOneInputMethod(this, et_input); // 隐藏软键盘
        List<String> msgList = ChatUtil.splitString(message, 20); // 按照20字节切片
        for (String msg : msgList) {
            mReadChara.setValue(msg); // 设置读特征值
            // 发送本地特征值已更新的通知
            mGattServer.notifyCharacteristicChanged(mRemoteDevice, mReadChara, false);
        }
        appendChatMsg(message, true); // 往聊天窗口添加聊天消息
    }

    // 往聊天窗口添加聊天消息
    private void appendChatMsg(String content, boolean isSelf) {
        appendNowMinute(); // 往聊天窗口添加当前时间
        // 把单条消息的线性布局添加到聊天窗口上
        ll_show.addView(ChatUtil.getChatView(this, content, isSelf));
        // 延迟100毫秒后启动聊天窗口的滚动任务
        new Handler(Looper.myLooper()).postDelayed(() -> {
            sv_chat.fullScroll(ScrollView.FOCUS_DOWN); // 滚动到底部
        }, 100);
    }

    // 往聊天窗口添加当前时间
    private void appendNowMinute() {
        String nowMinute = DateUtil.getNowMinute();
        if (!mMinute.substring(0, 4).equals(nowMinute.substring(0, 4))) {
            mMinute = nowMinute;
            ll_show.addView(ChatUtil.getHintView(this, nowMinute, dip_margin));
        }
    }

}

创作不易 觉得有帮助请点赞关注收藏~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/53728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nodejs事件循环

简介 单线程 提到node&#xff0c;我们就可以立刻想到单线程、异步IO、事件驱动等字眼。首先要明确的是node真的是单线程的吗&#xff0c;如果是单线程的&#xff0c;那么异步IO&#xff0c;以及定时事件&#xff08;setTimeout、setInterval等&#xff09;又是在哪里被执行的…

Qt “$$“符号的讲解

一.方便调试pro工程 举例方便理解。 在.pro文件中添加如下代码。 编译运行后在“概要信息”可以看到如下结果。 若将$$去掉&#xff0c;则打印的内容为message括号里本身的内容。 保存后可以看到 CSDN QT技术栈大纲&#xff1a;Qt开发必备技术栈学习路线和资料 二.方便其他工…

SpringMVC异常处理

SpringMVC异常简介 系统中异常包括两类&#xff1a;预期异常(检查型异常)和运行时异常 RuntimeException&#xff0c;前者通过捕获异常从而获取异常信息&#xff0c; 后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。 系统的 dao、service、controller 出现都通过…

C++ 语言学习 day15 复习 (7)

linux 上面&#xff1a; 操作步骤&#xff1a; 1. 2.找到 share 这个文件夹 3.找到 opencv 这个文件夹 4. 1.今天学习的摄像头 识别 人脸&#xff08; 独立开一个窗口&#xff09; &#xff0c; mian.cpp #include <iostream> #include "opencv2/core/core.hpp…

nnDetection复现Luna16 附模型

前提概要&#xff1a; 淘论文发现nnDetection框架对肺结节的检测效果挺好&#xff0c;便跑了跑复现了下&#xff0c; 作者诚不欺人&#xff0c;确实挺好&#xff0c; 并附上我依据文档训练的模型。 复现步骤&#xff1a; 1.安装 cuda11.4, cudnn8.2.4 2.创建虚拟环境 cond…

JAVA12_01学习总结(MySQL,约束)

今日内容 1. MySql基本查询 --ifnull(字段名称,预期值)-如果两个int类型数据求和,其中一个为null,那么结果就是null,使用ifnull来解决 -- 需求--查询姓名和成绩和 -- 创建表 CREATE TABLE test(id INT , -- 编号NAME VARCHAR(20) , -- 姓名age INT , -- 年龄math INT , -- 数…

html转pdf(总结五种方法Java)

html转pdf&#xff08;总结五种方法Java&#xff09; Java 实现html转pdf&#xff0c;总结五种方法。 推荐使用wkhtmltopdf,Itext 方法一&#xff1a;使用wkhtmltopdf 1、下载插件wkhtmltopdf https://wkhtmltopdf.org/downloads.html 2、本机测试 本目录下cmd进入 输入命…

解读数仓中的数据对象及相关关系

摘要&#xff1a;为实现不同的功能&#xff0c;GaussDB&#xff08;DWS&#xff09;提供了不同的数据对象类型&#xff0c;包括索引、行存表、列存表及其辅助表等。这些数据对象在特定的条件下实现不同的功能&#xff0c;为数据库的快速高效提供了保证&#xff0c;本文对部分数…

深聊性能测试,从入门到放弃之: Windows系统性能监控(一) 性能监视器介绍及使用。

性能监视器介绍及使用1、引言2、性能监视器2.1 打开方式2.2 基本介绍2.3 计数器介绍2.3.1 处理器性能计数器2.3.2 内存性能计数器2.3.3 网络性能计数器2.4 创建及使用2.4.1 用户自定义创建2.4.2 直接添加计数器3、总结1、引言 小屌丝&#xff1a;鱼哥&#xff0c;你有没有监控…

G1D28-hinge loss fuction-RAGA pre总结-DeBERTa-杂七杂八visiomathtypeexcel

一、hinge loss和交叉熵对比 (一 )hinge loss主要思想 让正确分类和错误分类的距离达到λ。λ用于控制两种分类样本之间的距离。 &#xff08;二&#xff09;对比学习 自监督学习的一种&#xff0c;不依赖标注数据进行学习。蛮有意思的&#xff0c;但是今天没时间了&#x…

逻辑学三大定律是什么?

逻辑思维三大定律: 同一律&#xff0c;矛盾律&#xff0c; 排中律。 同一律&#xff1a;A 是 A。 前后思维中&#xff0c;概念要同一。白马非马论违反同一律。商家的买一赠一&#xff0c;前后两个一不是同一个概念。违反同一律。矛盾律&#xff1a;A 是 B&#xff0c; A 不是B,…

【Android App】发送BLE广播及通过主从BLE实现聊天应用讲解及实战(附源码和演示 超详细)

需要源码请点赞关注收藏后评论区留言私信~~~ 一、发送BLE广播 调用蓝牙适配器的getBluetoothLeAdvertiser方法&#xff0c;获得BluetoothLeAdvertiser广播器对象。 广播器的主要方法说明如下&#xff1a; startAdvertising方法表示开始发送BLE广播&#xff0c; stopAdvertis…

springBoot开源MES生产制造执行系统源码带文字搭建教程

源码分享&#xff01;需要源码学习参考可私信。 技术框架&#xff1a;springBoot mybatis-plus shiro hutool layui swagger freemarker mysql8 echarts 运行环境&#xff1a;IntelliJ IDEA 2022 maven nginx 宝塔面板 系统功能 用户管理&#xff1a;用户是系统操…

用VS开发一款“飞机大战“单机游戏<C++>

显示界面如上图所示 自己找的背景和飞机素材,先将素材奉上. 接下来我先简单分析一下这个单机游戏的运行逻辑: 就像显示界面所显示的那样,我们想要实现的是自己的飞机在发射子弹(子弹在上图没显示),然后当子弹射到敌方飞机,这里设置了两种类型的飞机,如果读者想定义更多类型的…

基于51单片机农业大棚温控系统

资料编号&#xff1a;197 大棚种植温控系统概述&#xff1a; 本文介绍的是一个由单片机构成的温度控制系统&#xff0c;主要用来提供测温的解决方案&#xff0c;同时还能实时监控温度变化趋势&#xff0c;以及报警功能。它利用STC89C52RC单片机&#xff0c;DS18B20&#xff0c…

概率图模型:HMM(隐马),MEMM(最大熵),CRF(条件随机场)

1.概率图模型&#xff1a;HMM&#xff08;隐马&#xff09;,MEMM&#xff08;最大熵&#xff09;,CRF&#xff08;条件随机场&#xff09;概率&#xff1a;既然是一个图那么就是一个有圈有边的结构&#xff0c;圈代表随机向量&#xff0c;随机变量之间有边&#xff0c;边上有概…

数字验证学习笔记——UVM学习2 覆盖方法

一、覆盖方法 覆盖机制可以将原来所属的类型替换为另外一个新的类型。 在覆盖之后&#xff0c;原本用来创建原属类型的请求&#xff0c;将由工厂来创建新的替换类型。 无需再修改原始代码&#xff0c;继而保证了原有代码的封装性。新的替换类型必须与被替换类型兼容&#xff…

OpenStack 学习之 OVN : L2网络 ( Logical switches 逻辑交换机)

OVN Manual install & Configuration Open vSwitch 官网 参考 OVN学习&#xff08;一&#xff09; OVN实战一之GNS3操作指南及OVN入门 简单理解和知识 按照 OVN Manual install & Configuration 分别叫做 Controller 节点和 Compute 节点 &#xff0c;其他一般叫做…

Android Studio 打一个正式签名的Apk

如何打一个带正式签名文件的app (给自己的劳动成果冠名) 1. 选择build -> generate signed bundle/apk 2. 这里有两个选择, bundle or apk, 我们选择apk 于是勾选 apk, 并点下一步 3. 来到选择证书文件的地方, 但是我们这是第一次做, 还没有证书文件, 所以选择新建一个证…