Android 蓝牙开发(一)蓝牙通信「建议收藏」

Android 蓝牙开发(一)蓝牙通信「建议收藏」随着可穿戴设备的流行,研究蓝牙是必不可少的一门技术了。总结了下蓝牙开发使用的一些东西分享一下。首先需要AndroidManifest.xml文件中添加操作蓝牙的权限。允许程序连接到已配对的蓝牙设备。

大家好,又见面了,我是你们的朋友全栈君。

1 蓝牙基本操作

随着可穿戴设备的流行,研究蓝牙是必不可少的一门技术了。

总结了下蓝牙开发使用的一些东西分享一下。

蓝牙权限

首先需要AndroidManifest.xml文件中添加操作蓝牙的权限。

<uses-permissionandroid:name=”android.permission.BLUETOOTH” />

允许程序连接到已配对的蓝牙设备。

<uses-permissionandroid:name=”android.permission.BLUETOOTH_ADMIN” />

允许程序发现和配对蓝牙设备。


BluetoothAdapter

操作蓝牙主要用到的类 BluetoothAdapter类,使用时导包
import android.bluetooth.BluetoothAdapter;
源码具体位置frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java

BluetoothAdapter 代表本地设备的蓝牙适配器。该BluetoothAdapter可以执行基本的蓝牙任务,例如启
动设备发现,查询配对的设备列表,使用已知的MAC地址实例化一个BluetoothDevice类,并创建一个
BluetoothServerSocket监听来自其他设备的连接请求。

获取蓝牙适配器

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

开启蓝牙

 if(!mBluetoothAdapter.isEnabled()){
//弹出对话框提示用户是后打开
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler, REQUEST_ENABLE);
      //不做提示,直接打开,不建议用下面的方法,有的手机会有问题。
      // mBluetoothAdapter.enable();
}

获取本地蓝牙信息

//获取本机蓝牙名称
String name = mBluetoothAdapter.getName();
//获取本机蓝牙地址
String address = mBluetoothAdapter.getAddress();
Log.d(TAG,"bluetooth name ="+name+" address ="+address);
//获取已配对蓝牙设备
Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
Log.d(TAG, "bonded device size ="+devices.size());
for(BluetoothDevice bonddevice:devices){
	Log.d(TAG, "bonded device name ="+bonddevice.getName()+" address"+bonddevice.getAddress());
}

搜索设备

mBluetoothAdapter.startDiscovery();

停止搜索

mBluetoothAdapter.cancelDiscovery();

搜索蓝牙设备,该过程是异步的,通过下面注册广播接受者,可以监听是否搜到设备。

IntentFilter filter = new IntentFilter();
//发现设备
filter.addAction(BluetoothDevice.ACTION_FOUND);
//设备连接状态改变
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//蓝牙设备状态改变
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);

监听扫描结果

通过广播接收者查看扫描到的蓝牙设备,每扫描到一个设备,系统都会发送此广播(BluetoothDevice.ACTION_FOUNDE)。其中参数intent可以获取蓝牙设备BluetoothDevice

该demo中是连接指定名称的蓝牙设备,BLUETOOTH_NAME为”Galaxy Nexus“,如果扫描不到,记得改这个蓝牙名称。

private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver(){
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			Log.d(TAG,"mBluetoothReceiver action ="+action);
			if(BluetoothDevice.ACTION_FOUND.equals(action)){//每扫描到一个设备,系统都会发送此广播。
				//获取蓝牙设备
				BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				if(scanDevice == null || scanDevice.getName() == null) return;
				Log.d(TAG, "name="+scanDevice.getName()+"address="+scanDevice.getAddress());
				//蓝牙设备名称
				String name = scanDevice.getName();
				if(name != null && name.equals(BLUETOOTH_NAME)){
					mBluetoothAdapter.cancelDiscovery();
					//取消扫描
					mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting));					//连接到设备。
					mBlthChatUtil.connect(scanDevice);
				}
			}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){

			}
		}
		
	};

设置蓝牙可见性

有时候扫描不到某设备,这是因为该设备对外不可见或者距离远,需要设备该蓝牙可见,这样该才能被搜索到。

可见时间默认值为120s,最多可设置300。

if (mBluetoothAdapter.isEnabled()) {
	if (mBluetoothAdapter.getScanMode() != 
			BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
		Intent discoverableIntent = new Intent(
				BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		discoverableIntent.putExtra(
				BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
		startActivity(discoverableIntent);
	}
}

2 服务端

android 蓝牙之间可以通过SDP协议建立连接进行通信,通信方式类似于平常使用socket。

首先创建BluetoothServerSocket ,BluetoothAdapter中提供了两种创建BluetoothServerSocket 方式,如下图所示为创建安全的RFCOMM Bluetooth socket,该连接是安全的需要进行配对。而通过listenUsingInsecureRfcommWithServiceRecord创建的RFCOMM Bluetooth socket是不安全的,连接时不需要进行配对。

其中的uuid需要服务器端和客户端进行统一。

private class AcceptThread extends Thread {
        // 本地服务器套接字
        private final BluetoothServerSocket mServerSocket;
        public AcceptThread() {       	
            BluetoothServerSocket tmp = null;
            // 创建一个新的侦听服务器套接字
            try {
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(
                		SERVICE_NAME, SERVICE_UUID);
            	//tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, SERVICE_UUID);
            } catch (IOException e) {
                Log.e(TAG, "listen() failed", e);
            }
            mServerSocket = tmp;
        }

        public void run() {
            BluetoothSocket socket = null;
            // 循环,直到连接成功
            while (mState != STATE_CONNECTED) {
                try {
                    // 这是一个阻塞调用 返回成功的连接
                    // mServerSocket.close()在另一个线程中调用,可以中止该阻塞
                    socket = mServerSocket.accept();
                } catch (IOException e) {
                    Log.e(TAG, "accept() failed", e);
                    break;
                }
                // 如果连接被接受
                if (socket != null) {
                    synchronized (BluetoothChatUtil.this) {
                        switch (mState) {
                        case STATE_LISTEN:
                        case STATE_CONNECTING:
                            // 正常情况。启动ConnectedThread。
                            connected(socket, socket.getRemoteDevice());
                            break;
                        case STATE_NONE:
                        case STATE_CONNECTED:
                            // 没有准备或已连接。新连接终止。
                            try {
                                socket.close();
                            } catch (IOException e) {
                                Log.e(TAG, "Could not close unwanted socket", e);
                            }
                            break;
                        }
                    }
                }
            }
            if (D) Log.i(TAG, "END mAcceptThread");
        }

        public void cancel() {
            if (D) Log.d(TAG, "cancel " + this);
            try {
                mServerSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of server failed", e);
            }
        }
    }

mServerSocket通过accept()等待客户端的连接(阻塞),直到连接成功或失败。


3 客户端

客户端主要用来创建RFCOMM socket,并连接服务端。

先扫描周围的蓝牙设备,如果扫描到指定设备则进行连接。mBlthChatUtil.connect(scanDevice)连接到设备,

连接过程主要在ConnectThread线程中进行,先创建socket,方式有两种,

如下代码中是安全的(createRfcommSocketToServiceRecord)。另一种不安全连接对应的函数是createInsecureRfcommSocketToServiceRecord

private class ConnectThread extends Thread {
        private BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;
            // 得到一个bluetoothsocket
            try {
            	mmSocket = device.createRfcommSocketToServiceRecord
            			(SERVICE_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
                mmSocket = null;
            }
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            try { 
                // socket 连接,该调用会阻塞,直到连接成功或失败
                mmSocket.connect();
            } catch (IOException e) {
                connectionFailed();
                try {//关闭这个socket
                    mmSocket.close();
                } catch (IOException e2) {
                    e2.printStackTrace();
                }
                return;
            }
            // 启动连接线程
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

接着客户端socket主动连接服务端。连接过程中会自动进行配对,需要双方同意才可以连接成功。


4 数据传输

客户端与服务端连接成功后都会调用connected(mmSocket, mmDevice),创建一个ConnectedThread线程()。

该线程主要用来接收和发送数据。客户端和服务端处理方式一样。该线程通过socket获得输入输出流。

private  InputStream mmInStream = socket.getInputStream();

private  OutputStream mmOutStream =socket.getOutputStream();

发送数据

public void write(byte[] buffer) {
    try {
        mmOutStream.write(buffer);
        // 分享发送的信息到Activity
        mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)
                .sendToTarget();
    } catch (IOException e) {
        Log.e(TAG, "Exception during write", e);
    }
}

接收数据

线程循环进行接收数据。

public void run() {
    // 监听输入流
    while (true) {
        try {
            byte[] buffer = new byte[1024];
            // 读取输入流
            int bytes = mmInStream.read(buffer);
            // 发送获得的字节的ui activity
            Message msg = mHandler.obtainMessage(MESSAGE_READ);
            Bundle bundle = new Bundle();
            bundle.putByteArray(READ_MSG, buffer);
            msg.setData(bundle);
            mHandler.sendMessage(msg);          
        } catch (IOException e) {
            Log.e(TAG, "disconnected", e);
                connectionLost();
                break;
            }
        }
   }


demo下载:http://www.demodashi.com/demo/10676.html

欢迎扫一扫关注我的微信公众号,定期推送优质技术文章

Android 蓝牙开发(一)蓝牙通信「建议收藏」

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/148756.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • RPM卸载 (Linux 使用)[通俗易懂]

    RPM卸载 (Linux 使用)[通俗易懂]可以先用rpm-q’xxx’或者rpm-qf’xxx/bin/xxxx.xx’来查询一下所属的rpm包的名字。然后用rpm-e’xxxxxx’来删之。’xxx/bin/xxxx.xx’是一个包中任意的文件’xxxxxx’是查询得到的rpm包的名称rpm-e的时候后面的文件名不用加版本号详细说明:安全地卸载RPM卸载软件包,并不是简单地将原来安

    2022年9月2日
    3
  • pycharm企业版激活码(破解版激活)

    pycharm企业版激活码(破解版激活),https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月16日
    59
  • Python画图

    Python画图帮别人实现rubberfriction的运算公式。公式不是我专业的内容,完全不懂,只是照着公式用python计算并画图。做出的图,与论文中的图进行对比,完全一致。之所以用python,是因为mat

    2022年7月5日
    22
  • Exploiting Visual Artifacts to Expose Deepfakes and Face Manipulations论文详记

    Exploiting Visual Artifacts to Expose Deepfakes and Face Manipulations论文详记ExploitingVisualArtifactstoExposeDeepfakesandFaceManipulations论文详记一、论文简述二、论文内容A、篡改伪影①全局一致性②光照估计③几何估计B、基于视觉伪影的分类①、完全生成脸部的检测②、DeepFakes的检测③、Face2Face的检测三、论文实验及结果一、论文简述提取眼睛、牙齿以及脸部轮廓等位置的特征来检测DeepFake视频,属于基于帧内图像伪影的检测方法,使用Logistic回归或浅层全连接网络分类,属于浅层分类器方法

    2022年5月17日
    40
  • 正则替换括号但保留内容(常用的正则表达式)

    正则表达式保留部分内容替换需求:把trim(ABC)替换成trim(replace(ABC,char(9),”)需要把ABC保留不变,替换其它的。实现:trim\(([^).]*)\)替换成trim\(replace\($1,char\(9\),”\)在查找的时候用括号括起来的代表一部分,在替换的时候可以用$1,$2…引用。注意:有写编…

    2022年4月10日
    110
  • mysql命令查看版本_查看mysql

    mysql命令查看版本_查看mysql在日常项目开发过程中,我们经常要连接自己的数据库,此时不知道数据库的版本是万万不可的。今天小树懒来给大家总结一下查看mysql版本的方法。MySQL查看版本的方法主要有以下四种方法1:没有连接到MySQL终端下直接使用mysql命令。打开cmd,切换至MySQL的bin目录,运行以下命令:[root@localhostlib]#mysql-VmysqlVer15.1Distrib5.5.47-MariaDB,forLinux(x86_64)usingreadl

    2022年9月28日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号