因项目需要做一个Android 的蓝牙app来通过手机蓝牙传输数据以及控制飞行器,在此,我对这段时间里写的蓝牙app的代码进行知识梳理和出现错误的总结。

该应用的Compile Sdk Version 和targetSdkVersion均为26,Min Sdk Version为22,基于Android studio平台开发。

一、声明蓝牙权限

首先,要在新建项目中的AndroidManifest.xml中声明两个权限:BLUETOOTH权限和BLUETOOTH_ADMIN权限。其中,BLUETOOTH权限用于请求连接和传送数据;BLUETOOTH_ADMIN权限用于启动设备、发现或进行蓝牙设置,如果要拥有该权限,必须现拥有BLUETOOTH权限。

其次,因为android 6.0之后采用新的权限机制来保护用户的隐私,如果我们设置的targetSdkVersion大于或等于23,则需要另外添加ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION权限,否则,可能会出现搜索不到蓝牙设备的问题。

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>

二、 启动和关闭蓝牙

1.首先,要获取BluetoothAdapter蓝牙适配器的对象,然后检测设备是否支持蓝牙。

BluetoothAdapter blueadapter = BluetoothAdapter.getDefaultAdapter();
//获取蓝牙适配器
if(blueadapter==bull)
//表示手机不支持蓝牙
return;

2.启动蓝牙功能:isEnable()方法用来检查蓝牙当前状态,如果方法返回false,则蓝牙没启动。enable()方法用来打开本地蓝牙适配器。

 if (!blueadapter.isEnabled())
//判断本机蓝牙是否打开
{//如果没打开,则打开蓝牙
blueadapter.enable();
}

3.使用disable()可以关闭本地蓝牙适配器。

三、发现蓝牙设备

1.开启当前蓝牙的可见性
       Android 设备默认是不能被搜索的,如果想要本机设备可被搜索,可以以BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE动作为startActivity()方法的参数,这个方法会提交一个开启蓝牙可见的请求。默认的情况下,设备在120秒内可以被搜索,也可以自定义一个间隔时间,但是规定的最大值为300秒,0秒则表示设备可以一直被搜索,自定义时间通过EXTRA_DISCOVERABLE_DURATION来定义,代码如下。

if (blueadapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) //不在可被搜索的范围
{
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//设置本机蓝牙在300秒内可见
startActivity(discoverableIntent);
}

2.调用startDiscover()搜索蓝牙

开启蓝牙后,调用startDiscover()方法搜索蓝牙,注意,只有开启了蓝牙可见性的设备才会响应。该搜索过程为异步操作,调用后讲以广播的机制返回搜索到的对象,搜索的过程一般为12秒,搜索过程页面会显示搜索到的设备。

public void doDiscovry() {
if (blueadapter.isDiscovering()) {
//判断蓝牙是否正在扫描,如果是调用取消扫描方法;如果不是,则开始扫描
blueadapter.cancelDiscovery();
} else
blueadapter.startDiscovery(); }

3.注册广播
       通过blueadapter.startDiscovery()来搜索蓝牙设备,要获取到搜索的结果需要注册广播。

定义一个列表

public ArrayAdapter adapter;
ListView listView = (ListView) findViewById(R.id.list);//控件 列表
//定义一个列表,存蓝牙设备的地址。
public ArrayList<String> arrayList=new ArrayList<>();
//定义一个列表,存蓝牙设备地址,用于显示。
public ArrayList<String> deviceName=new ArrayList<>();

将搜索到的显示在控件列表上

adapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1, deviceName);
listView.setAdapter(adapter);

定义广播和处理广播消息

IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//注册广播接收信号
registerReceiver(bluetoothReceiver, intentFilter);//用BroadcastReceiver 来取得结果 private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
deviceName.add("设备名:"+device.getName()+"\n" +"设备地址:"+device.getAddress() + "\n");//将搜索到的蓝牙名称和地址添加到列表。
arrayList.add( device.getAddress());//将搜索到的蓝牙地址添加到列表。
adapter.notifyDataSetChanged();//更新
}
}
};

搜索完设备后,要记得注销广播。注册后的广播对象在其他地方有强引用,如果不取消,activity会释放不了资源 。

protected void onDestroy(){
super.onDestroy();//解除注册
unregisterReceiver(bluetoothReceiver);
}

4.了解targetSdkVersion是否大于或等于23

若是大于或等于23,除了添加了蓝牙权限外,还要动态获取位置权限,才能将搜索到的蓝牙设备显示出来。若是小于,则不需要动态获取权限。
动态申请权限,网上例子如下。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
textView.setText("打开蓝牙成功");
}
if (resultCode == RESULT_CANCELED) {
textView.setText("放弃打开蓝牙");
}
} else {
textView.setText("蓝牙异常");
}
} @Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_COARSE_LOCATION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
}
break;
}
}

 四、配对蓝牙设备

蓝牙的配对和连接有两种方式。一种是每个设备作为一个客户端去连接一个服务端,向对方发起连接。另一种则是作为服务端来接收客户端发来连接的消息。蓝牙之间的数据传输采用的是和TCP传输类似的传输机制。

1.作为客户端连接
       首先要获取一个代表远程设备BluetoothDevice的对象,然后使用该BluetoothDevice的对象来获取一个BluetoothSocket对象。BluetoothSocket对象调用connect()可以建立连接。

蓝牙连接整个过程需要在子线程中执行的,并且要将 scoket.connect()放在一个新的子线程中,因为如果将这个方法也放在同一个子线程中解决的话,就会永远报错read failed, socket might closed or timeout, read ret: -1;借鉴网上的方法:再开一个子线程专门执行socket.connect()方法,问题可以解决;

另外,借鉴网上方法和建议,在获得socket的时候 ,尽量不使用uuid方式;因为这样虽然能够获取到socket 但是不能进行自动,所以使用的前提是已经配对了的设备连接;

使用反射的方式,能够自动提示配对,也适合手机间通信。

final BluetoothSocket socket = (BluetoothSocket) device.getClass().getDeclaredMethod("createRfcommSocket", new Class[]{int.class}).invoke(device, 1);

代码中的device需要把注册广播时的device作为参数传进线程中。注意,传进来的device的值要为远程设备的地址,若不是或有出入,则可能会出现NullPointerException异常,并提示尝试调用一个空的对象。为了解决这个问题,可以把显示获得的device名字、地址和传入线程的device的地址分在不同的集合类。传入线程的device使用只有设备地址的集合类。

在连接蓝牙之前,还要先取消蓝牙设备的扫描,否则容易连接失败。

adapter.cancelDiscovery();//adapter为获取到的蓝牙适配器
socket.connect();//连接

2.作为服务端连接
       服务端接收连接需要使用BluetoothServerSocket类,它的作用是监听进来的连接,在一个连接被接收之后,会返回一个BluetoothSocket对象,这个对象可以用来和客户端进行通信。

与客户端一样,服务端也要在子线程中实现。通过调用listenUsingRfcommWithServiceRecord(String,UUID)方法可以得到一个BluetoothServerSocket的对象,然后再用这个对象来调用accept()来返回一个BluetoothSocket对象。由于accept()是个阻塞的方法,它会直到接收到一个连接或异常之后才会返回,所以要放在子线程中。


bluetoothServerSocket=bluetoothAdapter.listenUsingRfcommWithServiceRecord(bluetoothAdapter.getDefaultAdapter().getName(), UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//bluetoothServerSocket= (BluetoothServerSocket) bluetoothAdapter.getClass().getMethod("listenUsingRfcommOn",new Class[]{int.class}).invoke(bluetoothAdapter,10);
socket=bluetoothServerSocket.accept();//接收连接

代码中注释掉的内容是通过反射的方式来接收,由于我使用时出现了异常,所以暂时不考虑这个方法。

还有,与TCP不同的是,这个连接时只允许一个客户端连接,因此在BluetoothServerSocket对象接收到一个连接请求时就要立刻调用close()方法把服务端关闭。

五、客户端发送数据

当两个设备成功连接之后,双方都会有一个BluetoothSocket对象,这时,就可以在设备之间传送数据了。

1.使用getOutputStream()方法来获取输出流来处理传输。

2.调用write()。

os = socket.getOutputStream();//获取输出流
if (os != null) {//判断输出流是否为空
os.write(message.getBytes("UTF-8"));
}
os.flush();//将输出流的数据强制提交
os.close();//关闭输出流
}

将输出流中的数据提交后,要记得关闭输出流,否则,可能会造成只能发送一次数据。

六、服务端接收数据

1.使用getInputStream()方法来获取输入流来处理传输。

2.调用read()。

InputStream im=null;
im=bluetoothSocket.getInputStream();
byte buf[] = new byte[1024];
if (is != null) {
is.read(buf, 0, buf.length);//读取发来的数据
String message = new String(buf);//把发来的数据转化为String类型
BuletoothMainActivity.UpdateRevMsg(message);//更新信息在显示文本框
is.close();//关闭输入流

使用服务端接收数据时,要先从客户端向服务端发起连接,只有接收到连接请求之后,才会返回一个BluetoothSocket对象。有BluetoothSocket对象才能获取到输入流。

下面是将接收到数据显示在界面的方法:

在Activity中定义Handler类的对象handler。

public static void UpdateRevMsg(String revMsg) {
mRevMsg=revMsg;
handler.post(RefreshTextView);
} private static Runnable RefreshTextView=new Runnable() {
@Override
public void run() {
textView2.setText(mRevMsg);
}
};

Android Studio 蓝牙开发实例——基于Android 6.0的更多相关文章

  1. Android BLE 蓝牙开发——扫码枪基于BLESSED

    一.蓝牙模式HID与BLE 当扫码枪与手机连接时,通常采用的是蓝牙HID(Human Interface Device)模式.本质上是一个把扫码枪作为一个硬件键盘,按照键盘协议把扫码后的结果逐个输入到 ...

  2. 【Android】Android Studio NDK 开发

    Android Studio NDK 开发 记录在Android Studio中NDK简单开发的步骤 用到的Android Studio版本为3.5. 配置NDK 下载NDK 一般在SDK下已经有自带 ...

  3. Google Android Studio Kotlin 开发环境配置

    Google 近日开发者大会宣布Kotlin成为Android开发的第一级语言,即Android官方开发语言,可见Google对Kotlin的重视,本文就介绍一下Android Studio下的Kot ...

  4. Android Studio JNI开发入门教程

    Android Studio JNI开发入门教程 2016-08-29 14:38 3269人阅读 评论(0) 收藏 举报  分类: JNI(3)    目录(?)[+]   概述 在Andorid ...

  5. Android Studio获取开发版SHA1值和发布版SHA1值,详细过程

    转自原文 Android Studio获取开发版SHA1值和发布版SHA1值的史上最详细方法 前言: 今天我想把百度地图的定位集成到项目中来,想写个小小的案例,实现一下,但在集成百度地图时首先要申请秘 ...

  6. cordova开发插件,并在android studio中开发、调试

    之前用过cordova Lib包装H5页面,自己写插件,但做法是野路子,不符合cordova插件的开发思路,这次项目又需要包装H5页面,同时需要自定义插件.所以又折腾了一次cordova自定义插件. ...

  7. Android Studio xcode单步调试 WebRTC Android & iOS

    mac环境 如何在 Android Studio 里单步调试 WebRTC Android 的 native 代码. WebRTC 代码下载 depot tools 是 chromium 代码库管理工 ...

  8. 【Android Studio使用教程3】Android Studio的一些设置 体验更好了

    Android Studio 简单设置 界面设置 默认的 Android Studio 为灰色界面,可以选择使用炫酷的黑色界面. Settings --> Appearance --> T ...

  9. Android studio 安装与配置【Android学习入门】

    终于下定决心认真学习Android开发了. 之前在很多平台看到很多大牛们学习Android的经验和心得,纸上得来终觉浅. 这里推荐stormzhang老师总结的Android学习之路. 为了防止电脑卡 ...

随机推荐

  1. 预习初三物理电学部分的心得体会&知识梳理(持续更新)

    DAY 1 一.摩擦起电 用摩擦的方式使两个不同的物体带电的现象. 二.带电体 如果一个物体能够吸引轻小物体,我们就说这个物体带电或者说带了电荷. (注:吸引轻小物体是作用效果,带电体对任何物体都有吸 ...

  2. 【React】存储全局数据

    参考链接:https://segmentfault.com/a/1190000012057010?utm_source=tag-newest webstorage webstorage是本地存储,存储 ...

  3. PHP学习(1)

  4. yarn or npm 版本固化如何选择

    前言 作为前端开发者,npm这个包管理工具的重要性显而易见.优点不再表述,但一些缺点是为使用者诟病比较多的:速度慢.版本控制.下面主要讨论下npm的版本固化问题,即lock文件. npm语义化版本管理 ...

  5. 在eclipse中使用git创建本地库,以及托管项目到GitHub超详细教程

    关于安装git的教程,由于比较简单,并且网上教程特别多,而且即使不按照网上教程,下载好的windows版本git,安装时候一路默认设置就行. 安装好之后,在桌面上有git图标:右键菜单中有Git Ba ...

  6. Everything-1.4.1.917 绿色版

    Everything是一款搜索软件,可以瞬间搜索到你需要的文件.如果你用过Windows自带的搜索工具.Total Commander的搜索.Google 桌面搜索或百度硬盘搜索,都因为速度或其他原因 ...

  7. Java项目实例之---学生选课(面向对象复习)

    学生选课(面向对象复习) 设计一个学生选课的程序,分别有学生类(Student)和课程类(Course) 学生类的属性有:学号(String),姓名(String),性别(char),所选科目(Cou ...

  8. web安全测试必须注意的五个方面

    随着互联网的飞速发展,web应用在软件开发中所扮演的角色变得越来越重要,同时,web应用遭受着格外多的安全攻击,其原因在于,现在的网站以及在网站上运行的应用在某种意义上来说,它是所有公司或者组织的虚拟 ...

  9. stixel_world+Multi_stioxel_world+semantic_stixel_world知识拓展

    Semantic_Stixel_World 学习笔记 因项目方向更改,该研究暂停, 转为opengl等3D渲染. Author: Ian 星期四, 20. 六月 2019 06:11下午 最近看网络上 ...

  10. Neo4j电影关系图

    “电影关系图”实例将电影.电影导演.演员之间的复杂网状关系作为蓝本,使用Neo4j创建三者关系的图结构,虽然实例数据规模小但五脏俱全. 步骤: 一. 创建图数据:将电影.导演.演员等图数据导入Neo4 ...