因项目须要作一个Android 的蓝牙app来经过手机蓝牙传输数据以及控制飞行器,在此,我对这段时间里写的蓝牙app的代码进行知识梳理和出现错误的总结。android
该应用的Compile Sdk Version 和targetSdkVersion均为26,Min Sdk Version为22,基于Android studio平台开发。app
1、声明蓝牙权限异步
首先,要在新建项目中的AndroidManifest.xml中声明两个权限:BLUETOOTH权限和BLUETOOTH_ADMIN权限。其中,BLUETOOTH权限用于请求链接和传送数据;BLUETOOTH_ADMIN权限用于启动设备、发现或进行蓝牙设置,若是要拥有该权限,必须现拥有BLUETOOTH权限。socket
其次,由于android 6.0以后采用新的权限机制来保护用户的隐私,若是咱们设置的targetSdkVersion大于或等于23,则须要另外添加ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION权限,不然,可能会出现搜索不到蓝牙设备的问题。ide
<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"/>
2、 启动和关闭蓝牙post
1.首先,要获取BluetoothAdapter蓝牙适配器的对象,而后检测设备是否支持蓝牙。ui
BluetoothAdapter blueadapter = BluetoothAdapter.getDefaultAdapter(); //获取蓝牙适配器
if(blueadapter==bull) //表示手机不支持蓝牙 return;
2.启动蓝牙功能:isEnable()方法用来检查蓝牙当前状态,若是方法返回false,则蓝牙没启动。enable()方法用来打开本地蓝牙适配器。
this
if (!blueadapter.isEnabled()) //判断本机蓝牙是否打开 {//若是没打开,则打开蓝牙 blueadapter.enable(); }
3.使用disable()能够关闭本地蓝牙适配器。spa
3、发现蓝牙设备线程
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; } }
4、配对蓝牙设备
蓝牙的配对和链接有两种方式。一种是每一个设备做为一个客户端去链接一个服务端,向对方发起链接。另外一种则是做为服务端来接收客户端发来链接的消息。蓝牙之间的数据传输采用的是和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()方法把服务端关闭。
5、客户端发送数据
当两个设备成功链接以后,双方都会有一个BluetoothSocket对象,这时,就能够在设备之间传送数据了。
1.使用getOutputStream()方法来获取输出流来处理传输。
2.调用write()。
os = socket.getOutputStream();//获取输出流 if (os != null) {//判断输出流是否为空 os.write(message.getBytes("UTF-8")); } os.flush();//将输出流的数据强制提交 os.close();//关闭输出流 }
将输出流中的数据提交后,要记得关闭输出流,不然,可能会形成只能发送一次数据。
6、服务端接收数据
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); } };