Android:蓝牙实现一对一聊天

前言

原来蓝牙如今还分经典蓝牙、低功耗蓝牙和双模蓝牙,技术的发展真的超过我的的认知速度,不学习意味退步。原本写着低功耗蓝牙和智能蓝牙音箱的交互,但写到最后,由于蓝牙音箱尚未作好,没办法给本文的结果作个保障,故最后改为蓝牙聊天。蓝牙聊天可能适合在搭飞机和高铁这种没有网络或者网络很差等特殊状况下使用。本文的Demo能够正常使用。android

本文整体流程:发现蓝牙->配对蓝牙->链接蓝牙->数据交互git

在这个流程,主要是一些细节和异常的处理,如何更好的体现用户体验。github

声明权限

在项目的配置文件AndroidManifest.xml加入以下代码便可,让APP具备蓝牙访问权限和发现周边蓝牙权限。bash

//使用蓝牙须要该权限
<uses-permission android:name="android.permission.BLUETOOTH"/>
//使用扫描和设置须要权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//Android 6.0以上声明一下两个权限之一便可。声明位置权限,否则扫描或者发现蓝牙功能用不了哦
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
复制代码

Android 6.0以上动态申请权限位置权限,不然默认是禁止的,没法获取到蓝牙列表。网络

/**
 * Android 6.0 动态申请受权定位信息权限,不然扫描蓝牙列表为空
 */
private void requestPermissions() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_COARSE_LOCATION)) {
                Toast.makeText(this, "使用蓝牙须要受权定位信息", Toast.LENGTH_LONG).show();
            }
            //请求权限
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                    REQUEST_ACCESS_COARSE_LOCATION_PERMISSION);
        }
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_ACCESS_COARSE_LOCATION_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //用户受权
        } else {
            finish();
        }

    }

    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
复制代码

这里处理受权的用户体验比较很差,受权则继续,不受权则退出。能够根据本身的需求对受权逻辑另外处理。ide

设备是否支持蓝牙

并非全部的Android 设备都支持蓝牙,因此在使用以前,检测当前设备是否支持蓝牙。学习

private void isSupportBluetooth() {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        if (bluetoothAdapter == null
                || !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
            showNotSupportBluetoothDialog();
            Log.e(TAG, "not support bluetooth");
        } else {
            Log.e(TAG, " support bluetooth");
        }
    }
复制代码

类BluetoothAdapter表明着当前设备的蓝牙,能够经过BluetoothAdapter获取全部已绑定的蓝牙,扫描蓝牙,自身的名字和地址,经过地址能够获取周边蓝牙设备。能够经过两种方式得到BluetoothAdapter对象。ui

//方式一
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//方式二
BluetoothManager manager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter= manager.getAdapter();
复制代码

开启蓝牙

先检测蓝牙是否打开。没有打开则提示用户打开,或者直接打开。this

//提示用户开启蓝牙,会有弹出框让用户选择是否赞成打开
    private void enableBLE() {
        if (!bluetoothAdapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, REQUEST_BLE_OPEN);
        }
    }
    
        @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == 1) {
            if (resultCode == RESULT_OK) {
                scanBle();
            }
        }
    }
    
    //直接设置代码开启
   private void enableBLE() {
        if (!bluetoothAdapter.isEnabled()) {
            bluetoothAdapter.enable();
        }
    }
复制代码

设置可被发现

蓝牙打开后,要将自身设置为能够被周边蓝牙搜索到,以即可以进行下一步操做。蓝牙默承认被周边设备在120秒内搜索到,最长设置不过300秒。据说设置0能够永久被搜索哦。spa

/**
     * 设置蓝牙能够被其余设备搜索到
     */
    private void beDiscovered() {
        if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
            startActivity(discoverableIntent);
        }
    }
复制代码

收集周边蓝牙设备

经过注册广播监听,对发现的蓝牙设备添加到集合中,在listview进行展现。

private void registerBluetoothReceiver() {
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        registerReceiver(bluetoothReceiver, filter);
    }
    
    BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (action.equals(BluetoothDevice.ACTION_FOUND)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                list.add(device);
                Log.e(TAG, "discovery:" + device.getName());
                bleAdapter.notifyDataSetChanged();
            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                // Toast.makeText(MainActivity.this, "discovery finished", Toast.LENGTH_LONG).show();
                btnSearch.setText("从新搜索");
                mBluetoothAdapter.cancelDiscovery();
            }
        }
    };
复制代码

搜索结果以下,能够看到,部分蓝牙设备只有Mac地址,没有名称。这里没有进一步处理,若是须要处理,参考蓝牙名为空处理方案

搜索到周边设备

设备详情

获取蓝牙列表以后,点击对应的蓝牙,进入蓝牙详情。一个BluetoothDeviced对象表明着一个周边蓝牙设备,经过该对象,能够获取该蓝牙设备的名称,绑定状态,Mac地址,uuid等。

未配对蓝牙
未配对蓝牙设备经过 mDevice.createBond();进行配对。

聊天框实现

配对成功后,经过BluetoothDevice的createRfcommSocketToServiceRecord()方法和蓝牙设备链接,链接成功会返回BluetoothSocket对象,进而得到输入输出流,进行数据交互。

mSocket = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid));

 mSocket.connect();
 
 mOutputStream = mSocket.getOutputStream();

mInputStream = mSocket.getInputStream();
复制代码

链接过程会堵塞线程,请在子线程执行。uuid和服务端的一致且惟一就能够(这里本身定义),经过uuid和服务端绑定BluetoothSocket。咱们将输入输出的内容同步到聊天框,就达到了聊天效果。

服务端的BluetoothSocket对象的实现和此相似,具体的话能够看源码哦。

聊天框

总结

本文简单描述蓝牙链接到实现聊天框的流程,源代码可打开GitHub下载运行,有用就star一下。

因为BluetoothSocket的关闭或者读写异常,还有一些未能同步到,各位客官根据本身须要进行处理。另一些耗时的操做,例如链接蓝牙,没有作进度条反馈,能够根据本身需求进行定制。Demo能够正常食用。

点个赞,老铁

若是以为文章有用,给文章点个赞,铁子
相关文章
相关标签/搜索