[AndroidStudio Development] (4): Bluetooth BLE to send messages

Series Article Directory

[AndroidStudio development] (1): Create a new page switching project

[AndroidStudio development] (2): Add joystick control

[AndroidStudio development] (3): Classic Bluetooth + BLE Bluetooth search

[AndroidStudio Development] (4): Bluetooth BLE to send messages

[AndroidStudio development] (5): Horizontal and vertical screen switching and APP packaging and installation


Table of contents

Series Article Directory

foreword

1. Establish a Bluetooth connection

(1) FirstFragment file

(2) fragment_first.xml file

2. Send messages via Bluetooth

(1) SecondFragment.java file

(2) Create a bt package, as well as BtBase and BtClient classes

Summarize


foreword

        The first article explains how to create a new page switching project and learn to use the simulated mobile phone for debugging. The second article explains how to design your own page and add a joystick module. The third article then explains how to add a Bluetooth module to achieve classic and The search device function of BLE Bluetooth, the first three articles are all prepared for the fourth article. The effect to be achieved in the last article is an APP that remotely controls the car through Bluetooth. The function that is still poor at present is that the APP establishes a connection with the Bluetooth module of the car, and the APP then sends a message to the Bluetooth module of the car through Bluetooth.


1. Establish a Bluetooth connection

        The APP in the first three articles has basically implemented the Bluetooth module that can search for the car. Now you only need to join the Bluetooth connection process to establish the Bluetooth connection between the APP and the car.

(1) FirstFragment file

a. onItemClick function

Process the onItemClick function of each device. Clicking means connecting to this Bluetooth device, and connection processing needs to be added.

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    firstGatt = null;
    conbletype = mfbletype.get(position);
    condev = mLeDeviceListAdapter.getItem(position);

    if (btAdapter.isDiscovering()) {
        btAdapter.cancelDiscovery();
    }

    if (conbletype.equals("old")) {
        //todo 目前放在secondfragment,需要剥离成类
    } else {
        connectToDevice(mLeDeviceListAdapter.getItem(position),position);
    }

    NavHostFragment.findNavController(FirstFragment.this)
            .navigate(R.id.action_FirstFragment_to_SecondFragment);
}
    public void connectToDevice(BluetoothDevice device, int pos) {
        if (firstGatt == null) {
            scanLeDevice(false);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                firstGatt = device.connectGatt(mActivity.getApplicationContext(),
                        true, gattCallback, TRANSPORT_LE);
            } else {
                firstGatt = device.connectGatt(mActivity.getApplicationContext(),
                        true, gattCallback);
            }

            if (firstGatt == null) {
                Log.d(TAG, "firstGatt is null!" );
            }
        }
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.d(TAG, "onConnectionStateChange,Status: " + status);

            if (status==BluetoothGatt.GATT_SUCCESS){
                switch (newState) {
                    case BluetoothProfile.STATE_CONNECTED:
                        Log.i(TAG, "gattCallback,STATE_CONNECTED");
                        gatt.discoverServices();
                        break;
                    case BluetoothProfile.STATE_DISCONNECTED:
                        Log.i(TAG, "gattCallback,STATE_DISCONNECTED");
                        break;
                    default:
                        Log.e(TAG, "gattCallback,STATE_OTHER");
                }
            }else{
                //连接失败
                Log.e(TAG,"onConnectionStateChange fail,Status:" + status);
                gatt.close();
            }
        }
        /**
         * 发现设备(真正建立连接)
         * */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {

            List<BluetoothGattService> services = firstGatt.getServices();
            Log.d(TAG, "onServicesDiscovered:" + services.toString());
            //gatt.readCharacteristic(services.get(0).getCharacteristics().get(0));

            for (BluetoothGattService service : services){
                List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
                Log.d(TAG, "扫描到Service:uuid " +  service.getUuid());

                for (BluetoothGattCharacteristic characteristic : characteristics) {
                    Log.d(TAG, "扫描到Service:characteristic " +  characteristic.getUuid());
                }
            }
        }

        @Override
        public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
            Log.d(TAG, "onCharacteristicRead:" + characteristic.toString());
        }
    };

        This place needs to be optimized later. It should be abstracted into a Bluetooth class and unified entry. Now this processing method is a bit ugly. The Bluetooth connection of BLE is placed in the first, and the classic Bluetooth connection is placed in the second.

b. onCreateView function

A disconnection button is added to the right side of the scan button, so a disconnection button processing needs to be added in the onCreateView function.

        Button dc = view.findViewById(R.id.dc);
        dc.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                if (conbletype.equals("old")) {
                    //todo 目前放在secondfragment,需要剥离成类
                } else {
                    if (firstGatt != null) {
                        firstGatt.disconnect();
                        firstGatt = null;
                        APP.toast("Disconnected From Device", Toast.LENGTH_SHORT);

                    } else {
                        APP.toast("No Device Is Connected", Toast.LENGTH_SHORT);
                    }
                    firstGatt = null;
                }
            }
        });

The reason why firstGatt is set as a static variable is that the first page is responsible for searching, clicking on the corresponding Bluetooth device will jump to the second page, and at the same time destroy the first page, resulting in the connection firstGatt established by clicking on Bluetooth will also be destroyed at the same time, so create If it is static, the second page will not be empty when it is fetched.

Add the Gatt acquisition function, the second page can obtain the Gatt connection through this function.

    public static BluetoothGatt getBluetoothGatt() {
        return firstGatt;
    }

Classic bluetooth acquisition type and bluetooth device ( note that there is no equipment in the classic bluetooth sending information section, which has not been tested yet, please test it yourself )

    public static String getConbletype() {
        return conbletype;
    }

    public static  BluetoothDevice getCondev() {
        return condev;
    }

(2) fragment_first.xml file

Add disconnect (disconnect, DC) button resource

    <Button
        android:id="@+id/dc"
        android:text="Disconnect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="30dp"/>

At the same time, increase the position limit of the button_first button.

<Button
        android:id="@+id/button_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/next"
        android:layout_toStartOf="@+id/scan"
        android:layout_toEndOf="@+id/dc"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/scan"
        app:layout_constraintRight_toLeftOf="@+id/dc"
        android:layout_marginBottom="30dp"/>

2. Send messages via Bluetooth

(1) SecondFragment.java file

(a) Create a new static variable

    private static final String TAG = "SecondFragment";
    
    MainActivity sActivity;
    private BluetoothGatt secondGatt = null;
    private String secondbletype = "";
    private BluetoothDevice secondcondev = null;
    private final BtClient secondClient = new BtClient(this);
    private ScheduledExecutorService scheduledExecutor;

(b) onAttach function

    public void onAttach(@NonNull Activity activity) {
        super.onAttach(activity);
        sActivity = (MainActivity) activity;
        secondGatt = FirstFragment.getBluetoothGatt();
        secondbletype = FirstFragment.getConbletype();
        secondcondev = FirstFragment.getCondev();

        if (secondbletype.equals("old")) {
            Log.d(TAG, "ble type is old!" + secondcondev.getName());
            if (secondClient.isConnected(secondcondev)) {
                APP.toast("已经连接了", 0);
            } else {
                secondClient.connect(secondcondev);
                APP.toast("正在连接...", 0);
            }
        }
    }

Obtain the Gatt connection of the BLE Bluetooth on the first page, that is, this part of the classic Bluetooth sending information has not been tested.

(c) Class SecondFragment declaration adds implements BtBase.Listener

public class SecondFragment extends Fragment implements BtBase.Listener {

(d) Adding a BtBase class must also add a message notification function

public void socketNotify(int state, Object obj) {
        String msg = null;
        Toast toast = makeText(sActivity.getApplicationContext(), "", Toast.LENGTH_SHORT);

        switch (state) {
            case BtBase.Listener.CONNECTED:
                BluetoothDevice dev = (BluetoothDevice) obj;
                msg = String.format("与%s(%s)连接成功", dev.getName(), dev.getAddress());
                //mTips.setText(msg);
                toast.setText(msg);
                toast.setDuration(Toast.LENGTH_SHORT);
                toast.show();

                break;
            case BtBase.Listener.DISCONNECTED:
                msg = "连接断开";
                //mTips.setText(msg);
                toast.setText(msg);
                toast.setDuration(Toast.LENGTH_SHORT);
                toast.show();
                break;
            case BtBase.Listener.MSG:
//                msg = String.format("\n%s", obj);
                Log.d(TAG, String.format("\n%s", obj));
                break;
        }

    }

(e) onMove adds sending message operation

            @Override
            public void onMove(int angle, int strength) {
                mTextViewAngleRight.setText(angle + "°");
                mTextViewStrengthRight.setText(strength + "%");
                mTextViewCoordinateRight.setText(
                        String.format("x%03d:y%03d",
                                joystickRight.getNormalizedX(),
                                joystickRight.getNormalizedY())
                );

                String msgj = "jx" + joystickRight.getNormalizedX() + "y" + joystickRight.getNormalizedY() + "#";
                bluetoothsendmsg(msgj);
            }

(f) Add bluetoothsendmsg function

    private void bluetoothsendmsg(String msg)
    {
        if (secondbletype.equals("old")) {
            if (secondClient.isConnected(secondcondev)) {
                Log.d(TAG, "old Button " + msg);
                secondClient.sendMsg(msg);
            } else {
                Log.i(TAG, "secondClient not connect!");
            }
        } else {
            if (secondGatt != null) {
                //BluetoothGattService service = secondGatt.getService(UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"));
                BluetoothGattService service = secondGatt.getService(UUID.fromString("00001801-0000-1000-8000-00805f9b34fb"));
                if (service != null) {
                    //BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb"));
                    BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString("00002a05-0000-1000-8000-00805f9b34fb"));
                    characteristic.setValue(msg.getBytes());
                    secondGatt.writeCharacteristic(characteristic);
                    Log.d(TAG, "Button " + msg);
                } else {
                    Log.i(TAG, "service is null!");
                }

            } else {
                Log.i(TAG, "BluetoothGatt is null!");
            }
        }
    }

(2) Create a bt package, as well as BtBase and BtClient classes

(a) The document directory is as follows

 (b) BtBase class

package com.example.myapplication.bt;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Environment;
import android.util.Log;

import com.example.myapplication.APP;
import com.example.myapplication.util.Util;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.UUID;

/**
 * 客户端和服务端的基类,用于管理socket长连接
 */
public class BtBase {
    static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
//    static final UUID SPP_UUID = UUID.fromString("00001105-0000-1000-8000-00805f9b34fb");
//    static final UUID SPP_UUID = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
//    static final UUID SPP_UUID = UUID.fromString("00001801-0000-1000-8000-00805f9b34fb");

    private static final String FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/bluetooth/";
    private static final int FLAG_MSG = 0;  //消息标记
    private static final int FLAG_FILE = 1; //文件标记

    private BluetoothSocket mSocket;
    private DataOutputStream mOut;
    private Listener mListener;
    private boolean isRead;
    private boolean isSending;

    BtBase(Listener listener) {
        mListener = listener;
    }

    /**
     * 循环读取对方数据(若没有数据,则阻塞等待)
     */
    void loopRead(BluetoothSocket socket,BluetoothDevice mdev) {
        mSocket = socket;
        try {
            //防止横竖屏页面导致的in.readInt()返回read failed的read ret: -1错误。
            Thread.sleep(300);

            if (!mSocket.isConnected()) {
                mSocket.connect();
            }

            notifyUI(Listener.CONNECTED, mSocket.getRemoteDevice());
            mOut = new DataOutputStream(mSocket.getOutputStream());
            DataInputStream in = new DataInputStream(mSocket.getInputStream());
            isRead = true;
            Log.d("BaseFragment","loopRead!"+isRead);
            while (isRead) { //死循环读取
                int msgflag = in.readInt();
                Log.d("BaseFragment","read:"+msgflag);
                switch (msgflag) {
                    case FLAG_MSG: //读取短消息
                        String msg = in.readUTF();
                        notifyUI(Listener.MSG, "接收短消息:" + msg);
                        break;
                    case FLAG_FILE: //读取文件
                        Util.mkdirs(FILE_PATH);
                        String fileName = in.readUTF(); //文件名
                        long fileLen = in.readLong(); //文件长度
                        // 读取文件内容
                        long len = 0;
                        int r;
                        byte[] b = new byte[4 * 1024];
                        FileOutputStream out = new FileOutputStream(FILE_PATH + fileName);
                        notifyUI(Listener.MSG, "正在接收文件(" + fileName + "),请稍后...");
                        while ((r = in.read(b)) != -1) {
                            out.write(b, 0, r);
                            len += r;
                            if (len >= fileLen)
                                break;
                        }
                        notifyUI(Listener.MSG, "文件接收完成(存放在:" + FILE_PATH + ")");
                        break;
                }
            }
        } catch (Throwable e) {
            Log.d("BaseFragment","error!"+e.getMessage());
            e.printStackTrace();
            close();
        }
    }

    /**
     * 发送短消息
     */
    public void sendMsg(String msg) {
        if (checkSend()) return;
        isSending = true;
        try {
            mOut.writeInt(FLAG_MSG); //消息标记
            mOut.writeChars(msg);
//            mOut.write(msg);
            mOut.flush();
            notifyUI(Listener.MSG, "发送短消息:" + msg);
        } catch (Throwable e) {
            Log.d("BaseFragment","sendMsg error!"+e.getMessage());
//            e.printStackTrace();
            close();
        }
        isSending = false;
    }

    /**
     * 发送文件
     */
    public void sendFile(final String filePath) {
        if (checkSend()) return;
        isSending = true;
        Util.EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    FileInputStream in = new FileInputStream(filePath);
                    File file = new File(filePath);
                    mOut.writeInt(FLAG_FILE); //文件标记
                    mOut.writeUTF(file.getName()); //文件名
                    mOut.writeLong(file.length()); //文件长度
                    int r;
                    byte[] b = new byte[4 * 1024];
                    notifyUI(Listener.MSG, "正在发送文件(" + filePath + "),请稍后...");
                    while ((r = in.read(b)) != -1)
                        mOut.write(b, 0, r);
                    mOut.flush();
                    notifyUI(Listener.MSG, "文件发送完成.");
                } catch (Throwable e) {
                    close();
                }
                isSending = false;
            }
        });
    }

    /**
     * 释放监听引用(例如释放对Activity引用,避免内存泄漏)
     */
    public void unListener() {
        mListener = null;
    }

    /**
     * 关闭Socket连接
     */
    public void close() {
        try {
            isRead = false;
            if (mSocket != null)
            {
                mSocket.close();
                notifyUI(Listener.DISCONNECTED, null);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 当前设备与指定设备是否连接
     */
    public boolean isConnected(BluetoothDevice dev) {
        boolean connected = (mSocket != null && mSocket.isConnected());
        if (dev == null)
            return connected;
        return connected && mSocket.getRemoteDevice().equals(dev);
    }

    // ============================================通知UI===========================================================
    private boolean checkSend() {
        if (isSending) {
            APP.toast("正在发送其它数据,请稍后再发...", 0);
            return true;
        }
        return false;
    }

    private void notifyUI(final int state, final Object obj) {
        APP.runUi(new Runnable() {
            @Override
            public void run() {
                try {
                    if (mListener != null)
                        mListener.socketNotify(state, obj);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public interface Listener {
        int DISCONNECTED = 0;
        int CONNECTED = 1;
        int MSG = 2;

        void socketNotify(int state, Object obj);
    }
}

(c) BtClent class

package com.example.myapplication.bt;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

import com.example.myapplication.util.Util;

/**
 *
 * 客户端,与服务端建立长连接
 */
public class BtClient extends BtBase {
     public BtClient(Listener listener) {
        super(listener);
    }

    /**
     * 与远端设备建立长连接
     *
     * @param dev 远端设备
     */
    public void connect(BluetoothDevice dev) {
        close();
        try {
//             final BluetoothSocket socket = dev.createRfcommSocketToServiceRecord(SPP_UUID); //加密传输,Android系统强制配对,弹窗显示配对码
//            final BluetoothSocket socket =(BluetoothSocket) dev.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(dev,1);
            final BluetoothSocket socket = dev.createInsecureRfcommSocketToServiceRecord(SPP_UUID); //明文传输(不安全),无需配对

            // 开启子线程
            Util.EXECUTOR.execute(new Runnable() {
                @Override
                public void run() {
                    loopRead(socket,dev); //循环读取
                }
            });
        } catch (Throwable e) {
            Log.d("BtFragment","connect error!");
            close();
        }
    }
}

(d) Add the class Util in the util package

package com.example.myapplication.util;

import android.util.Log;

import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class Util {
    private static final String TAG = com.example.myapplication.util.Util.class.getSimpleName();
    public static final Executor EXECUTOR = Executors.newCachedThreadPool();

    public static void mkdirs(String filePath) {
        boolean mk = new File(filePath).mkdirs();
        Log.d(TAG, "mkdirs: " + mk);
    }
}


Summarize

        This part explains how to use the Bluetooth device searched by the previous APP, that is, click to establish a Bluetooth connection with it, and then enter the Second page to send specific information. Only the processing of sending information by the joystick is added here, and four will be added later. A button, to imitate the joystick. There is also the classic Bluetooth sending information part that has not been tested because there is no equipment. If you encounter any problems during the test process, please leave a message.

Guess you like

Origin blog.csdn.net/xanadw/article/details/125629737