ESP32S蓝牙09
经过了半个多月的学习,我们从米思齐的积木编程到Arduino IDE的模块编程、从APPInventor在线积木APP制作到Eclipse的纯代码编程、从传统蓝牙到BLE低功耗蓝牙、从蓝牙客户端到蓝牙服务端,由浅入深,足迹遍布蓝牙开发编程的方方面面。如今已经可以迎来了收官之作,我们准备做一个实用性的BLE蓝牙测试APP。
我们准备把BLE蓝牙的客户端和服务端两个程序整合在一起,本来以为这个是很简单的整合,不就1+1吗,但是在实际的操作过程中,还是状况频发,搞得我晕头转向的,经过了反反复复的摸索,不断地踩坑,加上一点瞎猫碰上死耗子的狗屎运,最后跌跌撞撞地完成了这个测试APP。
先上实用效果图吧。这个测试APP其实就是把之前的BLE客户端和服务端的两个程序整合在一起而已,使用如图所示,两部手机安装的其实是同一个整合完的程序。打开APP,在手机屏幕的顶端会有两个按钮“扫描蓝牙”和“蓝牙服务”,用户可以在客户端和服务器端。
首先选择一部(右边),点击“蓝牙服务”按钮,即进入了蓝牙服务端的页面,点击“开启服务”,会显示开启服务成功。这样,右边的这部手机运行的就是蓝牙服务端的程序,就会不断地向外广播服务消息,告诉周围的BLE蓝牙:我是服务器,我已经准备好了,可以随时连接我。
其次选择另外一部(左边),点击“扫描蓝牙”按钮,即开始了蓝牙客户端的程序,同时把“蓝牙服务”的按钮设置为不可用(程序在使用之前,必须选择只能运行客户端或服务器端的一种模式)。经过扫描就能扫到右边那部手机的蓝牙服务信号了,选择列表就可以连接,然后进行收发信息的通讯了。
经过这样的整合,用户使用起来很方便,只要下载安装同一个测试APP,就可以进行BLE蓝牙设备的测试了。这个是我们亲手制作的,比起网上下载的一些蓝牙测试,已经先进多了,可以说已经把网上的哪些测试APP甩出了好几条大街了,这难道不香吗。
这个程序是把之前的客户端和服务端的连个程序整合在一起,为了方便编程,我们使用了两个不同的视图界面Activity。首先我们新建一个安卓工程,(再说一下,我使用的是Eclipse编程环境,安装的是Android API24版本,具体的安装过程可以看我的另外一篇博客《升级Android SDK API24笔记》),工程名字BlueToothLE,默认设置就行。其次,我们要给这个程序添加第二个视图界面(窗口),我们在Eclipse左边的窗口中,选择工程下src文件夹右击鼠标,然后选择“new—Other—Android—Android Activity”菜单,会打开一个新建视图的对话框,输入新建窗口的名字为activity_server,其他的选择默认就可以,这样Eclipse就会为我们新建了一个视图窗口,我们会在src文件夹、res\layout文件夹、配置文件AndroidManifest. Xml中都能看到相应的新建窗口的消息了。
然后我们就是把之前的客户端和服务端的界面设计、程序代码分别复制到这两个视图中,在配置文件AndroidManifest. Xml中申请安卓版本号和蓝牙等权限后,就差不多了。当然有些地方的代码还是需要整合修改的。
踩坑过程记录:
然而程序的调试并不顺利,状况频发,这最大的坑是显示收发消息的标签控件显示不正常,基本上处于罢工的状态,偶尔能显示部分内容,算是你烧高香、他心情好的结局了。这种状况绝对不行,用户肯定不会答应,必须彻底解决:
1. 首先我猜想,会不会是消息在蓝牙设备的传输过程中,因为某种原因搁置了,消息没有传送过去。根据之前我做WiFi测试时的经验,我在每发送一条消息的后面,加上了回车换行符(”\n\r”),这样是为了让蓝牙设备立刻马上发送消息。测试结果没有改善,还是显示不顺。
2. 我查阅了网上别人的代码,有人说需要用Handler来接收消息,更新标签的内容。这个是因为我们从蓝牙接收消息时,用的是蓝牙的服务“线程+回调”,也就是在蓝牙的侦听线程里面接收到了别人发送过来的消息的。这样在非主线程中,是无法直接更改界面控件内容的(界面中的输入文本获取、标签文本输出、按钮侦听等,都是APP的主线程控制的),这里面涉及到了线程的内容,也就是安卓在线程控制中是这样规定的,非主线程(这里的蓝牙侦听)是不能直接控制和改变主线程的内容(这里的界面,标签文本的显示内容),而是需要使用Handler + Message来实现。结果更改了代码,测试还是没有结果。
3. 究竟是传输过程出了问题,还是显示过程出了问题呢。在测试的过程中(我们在两部手机中都安装了这个测试APP,分别运行客户端和服务端,两部手机进行收发消息的测试),结果一次非常偶然的操作,我按了一下手机底部中间的Home软按钮,手机显示了桌面,而测试程序退出到后台运行;当我点击手机底部的三条横线的软按钮,把这个测试程序从后台调到前台运行时,奇迹般的出现了,之前没有显示的收发消息,一下子全部都显示出来了。这个偶然的发现,基本上肯定了消息的收发传输过程是没问题的,问题应该出现在显示的部分了。 等之前的消息全部显示出来后,再次让测试程序继续收发操作,显示还是不顺利,还是要等到程序从后台转到前台的时候,才能整批的显示。
4. 然后还是经过了反反复复的尝试、修改代码、测试效果依然无果,显示问题一直不正常,整个屏幕好像睡着了似的,非得我们给他触碰一下,他才动一动。
还是一个偶然的机会,在测试收发的过程中,我不经意地点击了一下发送按钮前面的文本输入框,结果之前没有显示的消息一下子都显示了!这样也能显示!!而且接着尝试收发的消息,都能及时地显示出来!!! 我就点击了一下输入文本框,整个程序就真的好像从昏睡中醒来一样,生龙活虎,显示哗哗哗的,一点问题都没有。
那我们把原来显示收发消息的标签TextView更换成输入文本EditText,这样不就可以了吗?(当然,改为输入文本框的话,默认会获得活动的插入光标的,也就是在这个文本框中,会有一个一闪一闪的活动输入光标,这样影响使用效果,也影响使用心情,还是决定把这个光标关掉msgstr02.setFocusable(false);),这样这个输入文本框显示起来就和标签一样的效果了。
这个就是两天的测试过程中最大的发现,收获最大的成果。把显示收发消息的标签控件,替换为隐藏光标的输入文本框,在程序的运行中,显示不及时的问题彻彻底底地解决了。我们在两部手机中互相收发消息,都等立马显示出来了,这个测试APP也算是功德圆满,可以发布使用了。
回顾一下这次的踩坑过程,真的拼着一口不肯放松的劲,在加上踩狗屎的运气。当然奇迹真滴是给有准备的人,这才真正明白,所谓的灵感,都是经过了无数次的试错的结果。
最后贴上程序源码,算是笔记吧。这个是主程序 MainActivity. java
package com.example.bluetoothle;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@SuppressLint("NewApi")
public class MainActivity extends AppCompatActivity {
//public final static UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //通用蓝牙的标志
private UUID mServiceUUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
private UUID mReadUUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
private UUID mWriteUUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
private Button scan_button, send_button, discon_button, server_button;
private TextView msgstr;
private EditText editstr, msgstr02;
private BluetoothAdapter bleadapter;
private BluetoothGatt bluetoothGatt;
private BluetoothGattService bluetoothGattServices;
private BluetoothGattCharacteristic character_read, character_write;
private BluetoothDevice bledevice;
private List<BluetoothDevice> mDeviceList;
private ArrayList list;
private ArrayAdapter adapter;
private ListView mListView;
private boolean connected = false;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
msgstr02.setText(msgstr02.getText().toString() + "in: " + (String) msg.obj);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
msgstr = (TextView) findViewById(R.id.txt_msg);
msgstr02 = (EditText) findViewById(R.id.txt_msg02);
editstr = (EditText) findViewById(R.id.txt_edit01);
scan_button = (Button) findViewById(R.id.btn_client);
send_button = (Button) findViewById(R.id.btn_send);
discon_button = (Button) findViewById(R.id.btn_discon);
server_button = (Button) findViewById(R.id.btn_server);
mListView = (ListView) findViewById(R.id.listView1);
msgstr02.setFocusable(false);
editstr.setFocusable(false);
editstr.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
editstr.setFocusable(true);
editstr.setFocusableInTouchMode(true);
editstr.requestFocus();
return false;
}
});
//搜索按钮
scan_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//初始化ble设配器
server_button.setEnabled(false);
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bleadapter = manager.getAdapter();
//判断蓝牙是否开启,若是关闭则请求打开蓝牙
if (bleadapter == null || !bleadapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 1);
}
msgstr.setText("扫描中,请稍候 ...");
//初始化列表变量,一个用于存储自定义类,一个用于存储字符串
mDeviceList=new ArrayList<BluetoothDevice>();
list = new ArrayList();
//把扫描过程放进一个线程里面进行
new Thread(new Runnable() {
@Override
public void run() {
//如果发现一个BLE设备,就会执行一次callback回调函数
bleadapter.startLeScan(callback);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bleadapter.stopLeScan(callback);
msgstr.setText("");
}
}).start();
}
});
//发送消息按钮
send_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
editstr.setFocusable(false);
if(connected == true) {
String str = editstr.getText().toString() + "\n\r";
character_write.setValue(str);
bluetoothGatt.writeCharacteristic(character_write);
msgstr02.setText(msgstr02.getText().toString() + "ou: " + str);
}
}
});
//断开连接按钮
discon_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//断开连接
if(connected == true) {
bluetoothGatt.disconnect();
connected = false;
}
}
});
//跳转到蓝牙服务页面按钮
server_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.setClass(MainActivity.this, ServerActivity.class);
MainActivity.this.startActivity(intent);
}
});
//列表框侦听,当用户点击选择蓝牙时,连接蓝牙
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//获取用户选中的蓝牙设备
bledevice = mDeviceList.get(position);
msgstr.setText("connect: " + bledevice.getAddress());
//连接设备的方法,返回值为bluetoothgatt类型
//根据手机的版本,版本较高 或较低的时候
//回调函数gattcallback,管理蓝牙的连接、获取服务、读写消息
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
bluetoothGatt = bledevice.connectGatt(MainActivity.this, false, gattcallback, BluetoothDevice.TRANSPORT_LE);
else
bluetoothGatt = bledevice.connectGatt(MainActivity.this, false, gattcallback);
}
});
}
//这个是蓝牙扫描的回调函数,每当扫描到一个BLE设备时,就会运行一次这个函数
public BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
if (bluetoothDevice != null){
//这里给大家另外两种显示扫描结果的方法,可以用消息框或标签来显示
//Toast.makeText(MainActivity.this, bluetoothDevice.getName(), Toast.LENGTH_SHORT).show();
//showresult.append(bleDevice.getName() + " " + bleDevice.getMac() + "\n");
if(!mDeviceList.contains(bluetoothDevice)) {
mDeviceList.add(bluetoothDevice);
//list是存储字符串的集合,adapter是连接字符串到列表框的工具
list.add(bluetoothDevice.getName() + " " + bluetoothDevice.getAddress());
adapter = new ArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1, list);
mListView.setAdapter(adapter);
}
}
}
};
//这个是蓝牙管理的回调函数,管理BLE的连接、获取服务、读写消息
private BluetoothGattCallback gattcallback = new BluetoothGattCallback() {
//连接状态,当APP与BLE连接成功、或者连接断开时,都会触发这个事件
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, final int newState) {
super.onConnectionStateChange(gatt, status, newState);
runOnUiThread(new Runnable() {
@Override
public void run() {
switch (newState) {
//已经连接
case BluetoothProfile.STATE_CONNECTED:
msgstr.setText("已连接" + "\n");
//当连接成功,就获取BLE的服务,并触发获取服务的事件
bluetoothGatt.discoverServices();
break;
//正在连接
case BluetoothProfile.STATE_CONNECTING:
msgstr.setText("正在连接" + "\n");
break;
//连接断开
case BluetoothProfile.STATE_DISCONNECTED:
msgstr.setText("已断开" + "\n");
bluetoothGatt.close();
msgstr02.setText("");
break;
//正在断开
case BluetoothProfile.STATE_DISCONNECTING:
msgstr.setText("断开中" + "\n");
break;
}
}
});
}
//这个是获取BLE服务的事件,如果获取成功
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattService gattService = bluetoothGatt.getService(mServiceUUID);
//获取指定的UUID服务不为空时
if(gattService != null){
//获取指定的UUID读写通道
bluetoothGattServices = gattService;
character_read = gattService.getCharacteristic(mReadUUID);
character_write = gattService.getCharacteristic(mWriteUUID);
//把读取通道设置为可侦听、可读取状态
if (character_read != null)
setCharacteristicNotification(character_read, true);
connected = true;
msgstr.setText("已连接,获取服务成功");
}else{
msgstr.setText("获取服务失败");
}
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
//这个是侦听事件,当有数据从BLE设备传入APP的时候,就会引发这个事件
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
if(characteristic == character_read) {
Message mesg = new Message();
mesg.what = 1;
mesg.obj = new String(characteristic.getValue()) ;
MainActivity.this.handler.sendMessage(mesg);
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
}
} ;
//这个是把某个通道设置为可侦听状态
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
boolean isEnableNotification = bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
if(isEnableNotification) {
//一个读写通道里面,可能一次就传递多个类型的数值,每个类型数字都要设置侦听属性
List<BluetoothGattDescriptor> descriptorList = characteristic.getDescriptors();
if(descriptorList != null && descriptorList.size() > 0) {
for(BluetoothGattDescriptor descriptor : descriptorList) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
}
}
}
}
这个是界面设计 activity_main. xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FF00FFFF"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_client"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FF0080FF"
android:text="蓝牙扫描" />
<Button
android:id="@+id/btn_server"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FFFFFF00"
android:text="蓝牙服务" />
</LinearLayout>
<TextView
android:id="@+id/txt_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ListView
android:id="@+id/listView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible" />
<Button
android:id="@+id/btn_discon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="断开蓝牙" />
<EditText
android:id="@+id/txt_edit01"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hello server" />
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送消息" />
<EditText
android:id="@+id/txt_msg02"
android:minLines="12"
android:gravity="top"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
这个是第二个视图程序 ServerActivity. java
package com.example.bluetoothle;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
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.Intent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.UUID;
@SuppressLint("NewApi")
//public class ServerActivity extends ActionBarActivity {
public class ServerActivity extends AppCompatActivity {
//public final static UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //通用蓝牙的标志
private UUID mServiceUUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
private UUID mReadUUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
private UUID mWriteUUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
private Button scan_button, send_button, discon_button;
private TextView msgstr;
private EditText editstr, msgstr02;
private BluetoothAdapter bleadapter;
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
private BluetoothGatt bluetoothGatt;
private BluetoothGattServer gattServer;
private BluetoothGattService bluetoothGattServices;
private BluetoothDevice bluetoothDevice;
private BluetoothGattCharacteristic character_read, character_write;
private boolean connected = false;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
msgstr02.setText(msgstr02.getText().toString() + "in: " + (String) msg.obj);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server);
msgstr = (TextView) findViewById(R.id.txt_msg);
msgstr02 = (EditText) findViewById(R.id.txt_msg02);
editstr = (EditText) findViewById(R.id.txt_edit01);
scan_button = (Button) findViewById(R.id.btn_scan);
send_button = (Button) findViewById(R.id.btn_send);
discon_button = (Button) findViewById(R.id.btn_discon);
msgstr02.setFocusable(false);
editstr.setFocusable(false);
editstr.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
editstr.setFocusable(true);
editstr.setFocusableInTouchMode(true);
editstr.requestFocus();
return false;
}
});
//初始化ble设配器
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bleadapter = manager.getAdapter();
//判断蓝牙是否开启,若是关闭则请求打开蓝牙
if (bleadapter == null || !bleadapter.isEnabled()) {
//方式一:请求打开蓝牙
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 1);
}
// 获取蓝牙ble广播对象
assert bleadapter != null;
mBluetoothLeAdvertiser = bleadapter.getBluetoothLeAdvertiser();
gattServer = manager.openGattServer(this, bluetoothGattServerCallback);
//BLE服务的初始化配置,很重要
//配置的顺序是 服务service 特征(通道)Characteristic 属性Descriptor
//配置服务名、 主服务类型
BluetoothGattService service=new BluetoothGattService(mServiceUUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
//配置一个读的特征 (可读、可写、可侦听)
character_read=new BluetoothGattCharacteristic(mReadUUID,
BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
BluetoothGattDescriptor descriptor=new BluetoothGattDescriptor(mReadUUID,
BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);
character_read.addDescriptor(descriptor);
service.addCharacteristic(character_read);
//配置一个写的特征 (可写)
character_write=new BluetoothGattCharacteristic(mWriteUUID, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);
service.addCharacteristic(character_write);
//添加到
gattServer.addService(service);
//开启广播按钮
scan_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开启广播需要传入三个参数: 基本设置 附加数据 回调函数
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setConnectable(true) //是否被连接
.setTimeout(0) //超时时间
.build();
//广播数据设置
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true) //是否在广播中携带设备的名称
.setIncludeTxPowerLevel(true) //是否在广播中携带信号强度
.build();
//扫描回应的广播设置
AdvertiseData scanResponseData = new AdvertiseData.Builder()
.setIncludeTxPowerLevel(true) //是否在广播中携带设备的名称
.addServiceData(new ParcelUuid(mServiceUUID), new byte[]{1,2}) //在scanrecord中添加的数据
.build();
//设置BLE设备的名称
bleadapter.setName("BLEServer");
//开启广播
mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponseData, mAdvertiseCallback);
}
});
//发送消息按钮
send_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
editstr.setFocusable(false);
if(connected == true) {
String str = editstr.getText().toString() + "\n\r";
character_write.setValue(str);
gattServer.notifyCharacteristicChanged(bluetoothDevice, character_write, false);
msgstr02.setText(msgstr02.getText().toString() + "ou: " + str);
}
}
});
//停止广播按钮
discon_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (connected == true) {
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
connected = false;
}
}
});
}
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
msgstr.setText("开启广播成功" + "\n");
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
msgstr.setText("开启广播失败" + "\n");
}
};
//广播服务管理状态回调
private BluetoothGattServerCallback bluetoothGattServerCallback=new BluetoothGattServerCallback() {
//连接状态的回调
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
if(newState == BluetoothProfile.STATE_CONNECTED) {
bluetoothDevice = device;
msgstr.setText("已连接到 " + device.getName() + "\n");
connected = true;
}
if(newState == BluetoothProfile.STATE_DISCONNECTED) {
msgstr.setText("连接已断开 " + "\n");
msgstr02.setText("");
}
}
@Override
public void onServiceAdded(int status,BluetoothGattService service){
super.onServiceAdded(status,service);
msgstr.setText("添加服务成功");
}
//获取接收,接收的数据为参数中的value
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
Message mesg = new Message();
mesg.what = 1;
mesg.obj = new String(value);
ServerActivity.this.handler.sendMessage(mesg);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
}
};
}
这个是第二个视图界面设计 activity_server. xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FFFFFF00"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/btn_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始广播" />
<TextView
android:id="@+id/txt_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_discon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止广播" />
<EditText
android:id="@+id/txt_edit01"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hello client" />
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送消息" />
<EditText
android:id="@+id/txt_msg02"
android:minLines="12"
android:gravity="top"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
这个是安卓的版本号以及权限申请 在AndroidManifest. xml
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="21" />
<!-- 添加蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-feature android:name="android.hardware.location.gps" />
<!-- Android6.0 蓝牙扫描才须要-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>