【AndroidStudio开发】(四):蓝牙BLE发送消息

系列文章目录

【AndroidStudio开发】(一):新建页面切换项目

【AndroidStudio开发】(二):加入摇杆控制

【AndroidStudio开发】(三):经典蓝牙+BLE蓝牙搜索

【AndroidStudio开发】(四):蓝牙BLE发送消息

【AndroidStudio开发】(五):横竖屏切换及APP打包安装


目录

系列文章目录

前言

一、建立蓝牙连接

(1)FirstFragment文件

(2)fragment_first.xml文件

二、蓝牙发送消息

(1)SecondFragment.java文件

(2)新建一个bt的package,以及BtBase、BtClient类

总结


前言

        第一篇文章讲解了如何新建一个页面切换项目并且学会使用模拟手机进行调试功能,第二篇则是讲解如何设计自己的页面并添加摇杆模块,第三篇接着讲解添加蓝牙模块,实现经典和BLE蓝牙的搜索设备功能,前面三篇都是为了第四篇准备的。最后一篇文章要实现的效果是一个通过蓝牙遥控小车的APP。目前还差的功能是APP与小车的蓝牙模块建立连接,APP再通过蓝牙发送消息给小车的蓝牙模块。


一、建立蓝牙连接

        前三篇文章的APP已经基本实现了可以搜索到小车的蓝牙模块,现在只需要加入蓝牙的连接过程,就可以建立APP与小车的蓝牙连接。

(1)FirstFragment文件

a. onItemClick函数

对每一个设备的onItemClick函数进行处理,点击表示连接此蓝牙设备,需要增加连接处理。

@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());
        }
    };

        这个地方后面需要优化,应该单独抽象成一个蓝牙类,统一入口,现在这种处理方式有点丑陋,BLE的蓝牙连接放在first里面,经典蓝牙的连接放在了second里面。

b. onCreateView函数

scan按钮的右侧增加一个断连按钮,所以onCreateView函数里面需要增加一个对断连按钮的处理。

        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;
                }
            }
        });

firstGatt设置成静态变量的原因是,first页面负责搜索,点击对应的蓝牙设备会跳转到second页面,同时会销毁掉第一个页面,导致点击蓝牙建立的连接firstGatt同时也会被销毁,所以建立成静态,second页面获取的时候就不会是空值了。

增加Gatt的获取函数,第二个页面可以通过这个函数获取Gatt连接。

    public static BluetoothGatt getBluetoothGatt() {
        return firstGatt;
    }

经典蓝牙获取类型和蓝牙设备(注意经典蓝牙发送信息这部分没有设备暂未测试过,请自行测试

    public static String getConbletype() {
        return conbletype;
    }

    public static  BluetoothDevice getCondev() {
        return condev;
    }

(2)fragment_first.xml文件

增加断连(disconnect,dc)按钮资源

    <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"/>

同时增加对button_first按钮的位置限制。

<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"/>

二、蓝牙发送消息

(1)SecondFragment.java文件

(a) 新建静态变量

    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函数

    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);
            }
        }
    }

获取first页面BLE蓝牙的Gatt连接,就是这部分经典蓝牙发送信息没有测试过。

(c)类SecondFragment声明增加implements BtBase.Listener

public class SecondFragment extends Fragment implements BtBase.Listener {

(d)增加BtBase类必须同时增加一个消息通知函数

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增加发送消息操作

            @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)增加bluetoothsendmsg函数

    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)新建一个bt的package,以及BtBase、BtClient类

(a)文档目录如下

 (b)BtBase类

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类

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)util包里面增加类Util

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);
    }
}


总结

        这部分讲解了如何利用起前面APP搜索到的蓝牙设备,即通过点击与之建立蓝牙连接,再进入Second页面进行发送具体的信息,这里面只加入了摇杆发送信息的处理,后面会加入四个按键,来模仿手柄。还有就是经典蓝牙发送信息这部分因为没有设备所以暂未测试过,如果测试过程遇到问题欢迎留言。

  • 4
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是使用Android Studio开发BLE向手机发送消息的代码示例: 1. 添加权限到AndroidManifest.xml文件中: ```xml <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> ``` 2. 在布局文件中添加一个按钮和一个文本框: ```xml <Button android:id="@+id/send_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Send"/> <EditText android:id="@+id/message_edittext" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter message"/> ``` 3. 在Activity或Fragment中添加以下代码: ```java private BluetoothAdapter bluetoothAdapter; private BluetoothGatt bluetoothGatt; private BluetoothGattCharacteristic characteristic; private Button sendButton; private EditText messageEditText; private final String SERVICE_UUID = "0000fff0-0000-1000-8000-00805f9b34fb"; private final String CHARACTERISTIC_UUID = "0000fff1-0000-1000-8000-00805f9b34fb"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); sendButton = findViewById(R.id.send_btn); messageEditText = findViewById(R.id.message_edittext); sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String message = messageEditText.getText().toString(); if (characteristic != null && message != null) { characteristic.setValue(message.getBytes()); bluetoothGatt.writeCharacteristic(characteristic); } } }); } private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { bluetoothGatt.discoverServices(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { BluetoothGattService service = gatt.getService(UUID.fromString(SERVICE_UUID)); if (service != null) { characteristic = service.getCharacteristic(UUID.fromString(CHARACTERISTIC_UUID)); if (characteristic != null) { gatt.setCharacteristicNotification(characteristic, true); } } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // Handle incoming data } }; private void connectToDevice(BluetoothDevice device) { if (bluetoothGatt != null) { bluetoothGatt.close(); } bluetoothGatt = device.connectGatt(this, false, gattCallback); } private void scanForDevices() { BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner(); if (scanner != null) { scanner.startScan(new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { BluetoothDevice device = result.getDevice(); if (device.getName() != null && device.getName().equals("MyDeviceName")) { connectToDevice(device); } } }); } } @Override protected void onResume() { super.onResume(); if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } else { scanForDevices(); } } @Override protected void onPause() { super.onPause(); if (bluetoothGatt != null) { bluetoothGatt.close(); bluetoothGatt = null; } } ``` 4. 注意替换SERVICE_UUID和CHARACTERISTIC_UUID为你自己设备的UUID。 5. 运行应用程序,输入消息并单击Send按钮。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xanadw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值