關(guān)于如何設(shè)置筆記本電腦的藍(lán)牙 串口并且和手機(jī)藍(lán)牙配對,以及pc串口調(diào)試工具的下載請看這篇博客:
https://blog.csdn.net/weixin_44902943/article/details/113114481
通過上面這篇博客的操作再進(jìn)入app就能實現(xiàn)本Demo 和 pc串口調(diào)試工具的數(shù)據(jù)互傳了。(如果在app中總是顯示連接出錯,多半是因為藍(lán)牙沒有配對好,關(guān)閉藍(lán)牙再多配對幾次)
效果圖
先點擊開啟藍(lán)牙(即使藍(lán)牙已打開),然后再搜索設(shè)備,列表里沒有找到繼續(xù)點擊搜索設(shè)備
點擊要連接的設(shè)備跳轉(zhuǎn)到通訊頁面



源碼(帶注釋)
建議先熟悉一下藍(lán)牙開發(fā)的相關(guān)api再來看。
1、布局代碼
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/open_bluetooth_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="開啟藍(lán)牙"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.27"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.07" />
<Button
android:id="@+id/fount_device_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="搜索設(shè)備"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.694"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.07" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="20dp"
android:text="藍(lán)牙設(shè)備列表"
android:textColor="@color/black"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/open_bluetooth_btn"
app:layout_constraintVertical_bias="0.0" />
<ListView
android:id="@+id/bluetooth_device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
communication_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/send_text_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:text="發(fā)送"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<EditText
android:id="@+id/send_edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="10dp"
android:hint="輸入要發(fā)送的信息"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/send_text_btn"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/bluetooth_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="藍(lán)牙設(shè)備名稱"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/send_edit_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintBottom_toTopOf="@+id/send_edit_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bluetooth_name">
<TextView
android:id="@+id/received_text_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:background="@drawable/communication_list_shape"
android:padding="4dp"
android:text="我收到的信息:"
android:textColor="@color/black" />
<TextView
android:id="@+id/send_text_content"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:background="@drawable/communication_list_shape"
android:padding="4dp"
android:text="我發(fā)出的信息:"
android:textColor="@color/black" />
</LinearLayout>
<TextView
android:id="@+id/cancel_conn_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="結(jié)束"
android:textColor="@color/purple_500"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@+id/bluetooth_name"
app:layout_constraintEnd_toStartOf="@+id/bluetooth_name"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
2、Java代碼
MainActivity.java
package com.example.bluetoothdemo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import com.example.bluetoothdemo.adapter.MyArrayAdapter;
import com.example.bluetoothdemo.bean.DeviceInformation;
import com.example.bluetoothdemo.utils.ToastUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final int MY_PERMISSION_REQUEST_CONSTANT = 1;
private Button mOpenBluetoothBtn;
private Button mFoundDeviceBtn;
private ListView mDeviceList;
private MyArrayAdapter mAdapter;
private List<DeviceInformation> mDatas = new ArrayList<>();
private BluetoothAdapter mBluetoothAdapter;
private ToastUtil mToast;
private BroadcastReceiver mBluetoothReceiver;//用于接收藍(lán)牙狀態(tài)改變廣播的廣播接收者
private String TAG = "MainActivity";
private BroadcastReceiver mBLuetoothStateReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initReceiver();
initView();
initListener();
}
/*
注冊廣播接收者
*/
private void initReceiver() {
//創(chuàng)建用于接收藍(lán)牙狀態(tài)改變廣播的廣播接收者
mBLuetoothStateReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
switch (state){
case BluetoothAdapter.STATE_ON:
mToast.showToast("藍(lán)牙已打開");
break;
case BluetoothAdapter.STATE_OFF:
mToast.showToast("藍(lán)牙已關(guān)閉");
break;
case BluetoothAdapter.STATE_TURNING_ON:
mToast.showToast("藍(lán)牙正在打開");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
mToast.showToast("藍(lán)牙正在關(guān)閉");
break;
}
}
};
//創(chuàng)建設(shè)備掃描廣播接收者
mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG,"onReceive");
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
boolean isAdded = false;//標(biāo)記掃描到的設(shè)備是否已經(jīng)在數(shù)據(jù)列表里了
//獲取掃描到的設(shè)備
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//保存設(shè)備的信息
DeviceInformation deviceInformation = new DeviceInformation(device.getName(),device.getAddress());
for (DeviceInformation data : mDatas) {
//判斷已保存的設(shè)備信息里是否有一樣的
if (data.getDeviceAddress().equals(deviceInformation.getDeviceAddress())) {
isAdded = true;
break;
}
}
if (!isAdded) {
//通知UI更新
mDatas.add(deviceInformation);
mAdapter.notifyDataSetChanged();
}
}
}
};
//注冊廣播接收者
IntentFilter filter1 = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
IntentFilter filter2 = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mBLuetoothStateReceiver,filter1);
registerReceiver(mBluetoothReceiver,filter2);
}
//權(quán)限是否授予,給出提示
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSION_REQUEST_CONSTANT: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mToast.showToast("權(quán)限授權(quán)成功");
}else{
mToast.showToast("權(quán)限授權(quán)失敗");
}
return;
}
}
}
private void initView() {
//安卓6.0開始需要動態(tài)申請權(quán)限
if (Build.VERSION.SDK_INT >= 6.0) {
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.ACCESS_FINE_LOCATION},
MY_PERMISSION_REQUEST_CONSTANT);
}
mOpenBluetoothBtn = findViewById(R.id.open_bluetooth_btn);
mFoundDeviceBtn = findViewById(R.id.fount_device_btn);
mDeviceList = findViewById(R.id.bluetooth_device_list);
mToast = new ToastUtil(this);
mAdapter = new MyArrayAdapter(mDatas,this);
mDeviceList.setAdapter(mAdapter);
}
//初始化監(jiān)聽
private void initListener() {
mOpenBluetoothBtn.setOnClickListener(this);
mFoundDeviceBtn.setOnClickListener(this);
//設(shè)備列表item的點擊事件
mDeviceList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
if (mBluetoothAdapter.isDiscovering()) {
//停止搜索設(shè)備
mBluetoothAdapter.cancelDiscovery();
}
//獲取點擊的item的設(shè)備信息
DeviceInformation deviceInformation = mDatas.get(position);
//跳轉(zhuǎn)到設(shè)備通信頁面
Intent intent = new Intent(MainActivity.this,CommunicationActivity.class);
//將設(shè)備地址傳遞過去
intent.putExtra("name",deviceInformation.getDeviceName());
intent.putExtra("address",deviceInformation.getDeviceAddress());
startActivity(intent);
}
});
}
/**
* 檢測和開啟藍(lán)牙
*/
private void openBluetooth() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null) {
//判斷藍(lán)牙是否打開并可見
if (!mBluetoothAdapter.isEnabled()) {
//請求打開并可見
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent,1);
}
}else{
mToast.showToast("設(shè)備不支持藍(lán)牙功能");
}
}
/**
* 搜索藍(lán)牙設(shè)備
*/
private void discoverBluetooth(){
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
//搜索設(shè)備
mBluetoothAdapter.startDiscovery();
mToast.showToast("正在搜索設(shè)備");
}
/**
點擊事件
*/
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.open_bluetooth_btn:
//開啟藍(lán)牙
openBluetooth();
break;
case R.id.fount_device_btn:
//搜索設(shè)備
discoverBluetooth();
break;
default:
break;
}
}
}
Communication.java
package com.example.bluetoothdemo;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.bluetoothdemo.utils.ToastUtil;
import org.w3c.dom.Text;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
public class CommunicationActivity extends AppCompatActivity {
private EditText mEditText;
private Button mSendBtn;
private String mAddress;
private BluetoothAdapter mBlueToothAdapter;
private BluetoothDevice mDevice;
private BluetoothSocket mBluetoothSocket;
private final UUID mUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");//藍(lán)牙串口服務(wù)的UUID
private ToastUtil mToast;
private TextView mReceiveContent;
private TextView mSendContent;
private TextView mCancelConn;
private String mSendContentStr;
private static OutputStream mOS;
private String TAG = "CommunicationActivity";
private String mName;
private TextView mBtName;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.communication_layout);
Intent intent = getIntent();
//得到傳輸過來的設(shè)備地址
mAddress = intent.getStringExtra("address");
mName = intent.getStringExtra("name");
initView();
initListener();
//開始連接
connectDevice();
}
private void initView() {
mEditText = findViewById(R.id.send_edit_text);
mSendBtn = findViewById(R.id.send_text_btn);
mToast = new ToastUtil(this);
mReceiveContent = findViewById(R.id.received_text_content);
mSendContent = findViewById(R.id.send_text_content);
mCancelConn = findViewById(R.id.cancel_conn_btn);
mBtName = findViewById(R.id.bluetooth_name);
mBtName.setText(mName);
}
private void initListener() {
mSendBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mSendContentStr = mEditText.getText().toString();
//發(fā)送信息
sendMessage(mSendContentStr);
}
});
mCancelConn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
/**
* 發(fā)送數(shù)據(jù)的方法
* @param contentStr
*/
private void sendMessage(String contentStr) {
if (mBluetoothSocket.isConnected()) {
try {
//獲取輸出流
mOS = mBluetoothSocket.getOutputStream();
if (mOS != null) {
//寫數(shù)據(jù)(參數(shù)為byte數(shù)組)
mOS.write(contentStr.getBytes("GBK"));
mEditText.getText().clear();
mSendContent.append(contentStr);
mToast.showToast("發(fā)送成功");
}
} catch (IOException e) {
e.printStackTrace();
}
}else{
mToast.showToast("沒有設(shè)備已連接");
}
}
/**
* 與目標(biāo)設(shè)備建立連接
*/
private void connectDevice() {
//獲取默認(rèn)藍(lán)牙設(shè)配器
mBlueToothAdapter = BluetoothAdapter.getDefaultAdapter();
//通過地址拿到該藍(lán)牙設(shè)備device
mDevice = mBlueToothAdapter.getRemoteDevice(mAddress);
try {
//建立socket通信
mBluetoothSocket = mDevice.createRfcommSocketToServiceRecord(mUUID);
mBluetoothSocket.connect();
if (mBluetoothSocket.isConnected()) {
mToast.showToast("連接成功");
//開啟接收數(shù)據(jù)的線程
ReceiveDataThread thread = new ReceiveDataThread();
thread.start();
}else{
mToast.showToast("連接失敗,結(jié)束重進(jìn)");
}
} catch (IOException e) {
e.printStackTrace();
mToast.showToast("連接出錯! ");
finish();
try {
mBluetoothSocket.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mBluetoothSocket.isConnected()) {
//關(guān)閉socket
mBluetoothSocket.close();
mBlueToothAdapter = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 負(fù)責(zé)接收數(shù)據(jù)的線程
*/
public class ReceiveDataThread extends Thread{
private InputStream inputStream;
public ReceiveDataThread() {
super();
try {
//獲取連接socket的輸入流
inputStream = mBluetoothSocket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
super.run();
int len = 0;
byte[] buffer = new byte[256];
while (true){
try {
inputStream.read(buffer);
for (byte b : buffer) {
Log.d(TAG,"b:" + b);
}
//設(shè)置GBK格式可以獲取到中文信息,不會亂碼
String a = new String(buffer,0,buffer.length - 3,"GBK");//為什么-3 看文章最后注意部分
Log.d(TAG,"a:" + a);
// byte[] gbks = "你好".getBytes("GBK");
// for (byte gbk : gbks) {
// Log.d(TAG,"gbk:" + gbk);
// }
// String[] chars = a.split(" ");
// String str = "";
// for(int i = 0; i<chars.length;i++){
// str += (char)Integer.parseInt(chars[i]);
// }
//Log.d(TAG,"str:" + str);
runOnUiThread(new Runnable() {
@Override
public void run() {
//將收到的數(shù)據(jù)顯示在TextView上
mReceiveContent.append(a);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3、權(quán)限聲明
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
四、注意
我使用的這個友善串口調(diào)試工具進(jìn)行通訊測試時,我發(fā)現(xiàn)用它發(fā)完數(shù)據(jù)后,在手機(jī)端接收到的數(shù)據(jù)的字節(jié)數(shù)組會多出3個字節(jié),不知道是干什么用的。字節(jié)數(shù)組轉(zhuǎn)漢字后會亂碼,所以我就減去了最后3個字節(jié)。
還有編碼格式要用GBK,不然漢字會亂碼。不過在智能小車的數(shù)據(jù)傳輸中應(yīng)該用不到漢字。
五、項目源碼下載
源碼+apk
https://download.csdn.net/download/weixin_44902943/14929360