ESP32S蓝牙05
继续ESP32S的经典蓝牙学习。今天我们准备重做蓝牙手机客户端APP,不过今天不是用APPInventor积木式编程,而是使用Eclipse的代码编程,继续向着蓝牙通讯的底层探究。
Eclipse是我最近才升级的,Android API24的版本(差不多是Android7.1的系统)。
程序主要还是按照前面的客户端APP界面要求来写的,先上源代码吧:
这个是程序代码,在MainActivity.java
package com.example.bluetooth;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
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.BluetoothSocket;
import android.content.Intent;
import android.graphics.Bitmap;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
private Button onoff, send;
private TextView msgstr;
private EditText sendstr;
private Set<BluetoothDevice> pairedDevices;
private ArrayList list;
private ArrayAdapter adapter;
private ListView lv;
private Intent intent;
private BluetoothAdapter BA;
private ConnectThread mConnectThread;
private ConnectedThread mConnectedThread;
private int mState = 0;
public static final int SUCCESS = 0;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SUCCESS:
msgstr.setText((String) msg.obj);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
onoff = (Button)findViewById(R.id.button1);
send = (Button)findViewById(R.id.button2);
msgstr = (TextView)findViewById(R.id.textView1);
sendstr = (EditText)findViewById(R.id.edittext1);
lv = (ListView)findViewById(R.id.listView1);
//获取蓝牙适配器
mState = 0;
BA = BluetoothAdapter.getDefaultAdapter();
if (BA.isEnabled()) { //如果蓝牙已经开启
mState = 1;
//设置本机蓝牙为可搜索状态
intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(intent, 0);
//直接查找设备列表,并把设备名称显示在列表中
pairedDevices = BA.getBondedDevices();
list = new ArrayList();
for(BluetoothDevice bt : pairedDevices)
list.add(bt.getAddress() + " " +bt.getName());
adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1, list);
lv.setAdapter(adapter);
onoff.setText("关闭蓝牙");
}else {
onoff.setText("打开蓝牙");
}
onoff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!BA.isEnabled()) {
msgstr.setText("请稍候");
intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0);
try {
Thread.sleep(12000);
} catch (InterruptedException e) {
}
intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(intent, 0);
pairedDevices = BA.getBondedDevices();
list = new ArrayList();
for(BluetoothDevice bt : pairedDevices)
list.add(bt.getAddress() + " " +bt.getName());
adapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1, list);
lv.setAdapter(adapter);
onoff.setText("关闭蓝牙");
msgstr.setText("蓝牙开启");
mState = 1;
}else {
if (mState == 5) {
//往蓝牙服务器端发送关闭服务指令
String str = "close";
mConnectedThread.write(str.getBytes());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
}
mConnectThread.cancel();
mConnectThread = null;
mConnectedThread.cancel();
mConnectedThread = null;
}
BA.disable();
list.clear(); //清空list
adapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1, list);
lv.setAdapter(adapter);
onoff.setText("打开蓝牙");
msgstr.setText("蓝牙关闭");
mState = 1;
//Toast.makeText(getApplicationContext(),"Turned off" ,
//Toast.LENGTH_LONG).show();
}
}
});
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String str = sendstr.getText().toString();
if(mState == 5)
mConnectedThread.write(str.getBytes());
}
});
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
BluetoothDevice mDevice;
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//当用户选中列表中的某一个蓝牙设备时
if(mState == 1) {
msgstr.setText(list.get(position).toString());
int i = 0;
for(BluetoothDevice bt : pairedDevices) {
if(i == position) { mDevice = BA.getRemoteDevice(bt.getAddress()); break;} else i += 1;
}
//开启连接蓝牙线程
mState = 2;
mConnectThread = new ConnectThread(mDevice);
mConnectThread.start();
}
}
});
}
//连接蓝牙的线程
private class ConnectThread extends Thread {
private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";
private BluetoothSocket socket;
private BluetoothDevice device;
//构造函数中启动连接到服务器
public ConnectThread(BluetoothDevice device) {
this.device = device;
BluetoothSocket tmp = null;
try {
tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
} catch (IOException e) {
e.printStackTrace();
}
this.socket = tmp;
}
//进入线程等待
public void run() {
BA.cancelDiscovery();
Message msg = new Message();
try {
//当成功连接到服务器的时候
socket.connect();
mState = 3;
msg.what = SUCCESS;
msg.obj = "连接成功01";
handler.sendMessage(msg);
//创建一个侦听输入、发送输出的服务线程
mConnectedThread = new ConnectedThread(socket);
mConnectedThread.start();
//从这里结束连接线程
} catch (IOException e) {
try {
socket.close();
} catch (IOException ee) {
ee.printStackTrace();
}
msg.what = SUCCESS;
msg.obj = "连接失败01";
handler.sendMessage(msg);
return;
}
}
public void cancel() {
try {
socket.close();
} catch (IOException e) {
}
}
}
// 双方蓝牙连接成功后一直运行的输入输出服务线程
private class ConnectedThread extends Thread {
private BluetoothSocket socket;
private InputStream inputStream;
private OutputStream outputStream;
//构造函数中定义了输入输出的流
public ConnectedThread(BluetoothSocket socket) {
this.socket = socket;
InputStream input = null;
OutputStream output = null;
try {
input = socket.getInputStream();
output = socket.getOutputStream();
mState = 5;
} catch (IOException e) {
e.printStackTrace();
}
this.inputStream = input;
this.outputStream = output;
}
public void run() {
byte[] buff = new byte[1024];
int bytes;
//这里设置一个循环,让线程的不停地侦听输入
while (true) {
try {
bytes = inputStream.read(buff);
if(bytes > 0) {
String str = new String(buff, "ISO-8859-1");
str = str.substring(0, bytes);
Message msg = new Message();
msg.what = SUCCESS;
msg.obj = str;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
//这个是输出到服务器的操作
public void write(byte[] bytes) {
try {
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void cancel() {
try {
inputStream.close();
outputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//下面这个是作为服务器端的侦听线程
//private class AcceptThread extend Thread{
// private final BluetoothServerSocket mServerSocket;
// public AcceptThread(){
// mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);
// }
//
// public void run(){
// BluetoothSocket socket = null;
// while(true){
// socket = mServerSocket.accept();
// if(socket!=null){
// // 自定义方法
// manageConnectedSocket(socket);
// mServerSocket.close();
// break;
// }
// }
// }
// public void cancle(){
// mServerSocket.close();
// }
//}
}
这个是界面文件,在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: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" >
<ListView
android:id="@+id/listView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="断开蓝牙" />
<EditText
android:id="@+id/edittext1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hello server" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送" />
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</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"/>
<!--6.0以上才要加的额外权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
这个是确定模拟器的版本号,添加在project.properties文件的末尾
sdk.buildtools=24.0.0
到了代码编程的时候,没有点耐心是真的受不了了,都是一大片一大片的代码,都是精雕细琢的过程。
当然,在编译测试这个程序的时候,因为要用到蓝牙,所以必须连接真机(必须连接一部真正的手机到电脑,把程序传入手机中测试运行情况),还是出现了一点诡异的事情:我们在点击编译程序是,总是提示启动模拟器失败,但是模拟器是可以正常启动的,但是还是在编译启动模拟器时出现了错误。(后来我是这样理解的,可能是当我们手机连接电脑时,因为电脑中360手机助手的原因,导致连接手机出现了问题。而当我们在Eclipse中启动编译时,Eclipse发现电脑时有连接真机的,所以就尝试连接真机,但有连接不同,所以出现了模拟器启动失败的警告)。 解决的办法是,先在拔下手机连接,再清除电脑中360手机助手的进程,让电脑接触所有连接手机的痕迹。接着启动Eclipse的编译,这时候,Eclipse发现电脑中没有连接真机,所以自然就选择启动软件模拟器进行编译。等软件模拟器打开了,程序编译成功了,这时候关掉软件模拟器,在换连接真机(把手机插入电脑,打开电脑360手机助手连接好手机),这时候重新编辑编译,Eclipse就会和真正的手机相连,并把程序发送到手机中测试运行的情况了。
经过测试,这个蓝牙客户端程序已经能正常和其他的蓝牙服务器设备通讯的,及时做前面的三方会话的通讯连接测试,也能通过了。