Android 藍(lán)牙串口通信Demo

關(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)到通訊頁面


image.png

image.png

image.png

源碼(帶注釋)

建議先熟悉一下藍(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

GitHub:
https://github.com/gitHub-lgh/BlueToothSerialPort

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容