Android Studio 经典蓝牙实现

目录

一.具体实现

二.我遇到的问题和解决


蓝牙连接分为经典蓝牙和低功耗蓝牙,此篇为经典蓝牙。Android Studio 官方指导文件对于两种蓝牙连接都有详细的讲解。地址为:蓝牙概览  |  Android 开发者  |  Android Developers (google.cn)

一.具体实现

1.向系统获取蓝牙,并判断设备是否支持蓝牙功能。通过 mBluetoothAdapter 对象实现蓝牙的相关功能。

//对象声明
private BluetoothAdapter mBluetoothAdapter;
//蓝牙获取
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//判断 mBluetoothAdapter 是否获取成功
if (mBluetoothAdapter == null) {
    Log.e(TAG, "onCreate: 此设备不支持蓝牙");
  }

2.判断设备蓝牙是否打开,没有则请求打开蓝牙。

//判断蓝牙是否打开   true为打开
if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
   }

3.查询已配对的设备

此功能会获取所有之前设备配对过且没有删除的设备信息。此功能可以快速查看需要连接的设备是否已经处于了已检测到状态,从而可以直接实现连接。

在此方法中可以看到,我是通过动态添加按钮的方式将已连接过的设备信息进行了输出,并对按钮添加了监听事件,从而实现点击选择指定设备进行连接。

 private void checkpairedDevices() {
        Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
        LinearLayout layout2=(LinearLayout) findViewById(R.id.myLinearLayout2);
        if (pairedDevices.size() > 0) {
            // There are paired devices. Get the name and address of each paired device.
            for (BluetoothDevice device : pairedDevices) {
                String deviceName = device.getName();
                String deviceHardwareAddress = device.getAddress(); // MAC address
                //将连接过的设备信息输出
                Log.i("已连接过的设备:", deviceName+"---"+deviceHardwareAddress);
                //以下是根据自己的需要对设备信息进行的处理
                Button button=new Button(this);
                button.setText(deviceName+"_: "+deviceHardwareAddress);
                button.setOnClickListener(new View.OnClickListener() {
                    public void onClick(View v) {
                        //点击进行连接
                        connectThread=new ConnectThread(device);
                        connectThread.run();
                    }
                });
                layout2.addView(button);
            }
        }
    }

4.蓝牙扫描

需要注意,蓝牙扫描之前必须开启GPS才能成功扫描,且蓝牙扫描需要注册广播,应用必须针对 ACTION_FOUND Intent 注册一个 BroadcastReceiver,以便接收每台发现的设备的相关信息。

蓝牙扫描实现:

 private void Bluetoothscan() {
        //发现设备,实现扫描功能
        boolean f = mBluetoothAdapter.startDiscovery();
        if (f == true) {
            Log.i(TAG, "成功进行扫描以发现设备");
        } else {
            Log.i(TAG, "扫描失败");
        }
    }

申请打开GPS,申请打开GPS的方法有很多,此处列举我使用过的两种方法:

方法1:

protected void onCreate(Bundle savedInstanceState) {
        //打开GPS的权限申请
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (!permissionList.isEmpty()) {
            String[] permissions = permissionList.toArray(new String[permissionList.size()]);
            ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
        }
    ......
}
       

方法2:

public void GPS() {
        LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        boolean isEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        if (!isEnabled) {
            androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this);
            builder.setTitle("蓝牙扫描需要开启GPS,是否进入设置开启?").setNegativeButton("取消", null);
            builder.setPositiveButton("确认",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            Intent myIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                            startActivity(myIntent);
                        }
                    });
            builder.show();
        }
    }

注册广播接收蓝牙扫描到的其他蓝牙设备的信息,并对数据实现自己想要的处理:

//注册广播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);

 创建注册的广播:

我在使用官方给出的广播方法时,发现同一设备会被多次扫描到而被多次输出。因此,在此广播中我加入了一个数组对扫描信息进行了判断,从而是实现扫描到的设备不多次输出展示。

    //创建广播
    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            boolean f=true;
            String action = intent.getAction();
            // Discovery has found a device. Get the BluetoothDevice
            BluetoothDevice device = 
            intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            //判断扫描到的设备是否已经被输出过
            for(int i=0;i<number;i++){
                if(scanresult[i].equals(device.getAddress())) {
                    f=false; break;}
            }
            if (BluetoothDevice.ACTION_FOUND.equals(action)&&f) {
                //进入此if语句中则代表扫描到了一个新的蓝牙设备,以下是对此设备信息的处理
                //将扫描到的蓝牙mac地址存入字符串组,保证唯一输出显示
                scanresult[number]=device.getAddress();
                number++;
                // object and its info from the Intent.
                String deviceName = device.getName();
                String deviceHardwareAddress = device.getAddress(); // MAC address
                //以动态添加按钮的方式输出扫描到的蓝牙设备信息
                LinearLayout layout2=(LinearLayout) findViewById(R.id.myLinearLayout2);
                Button button=new Button(context);
                button.setText(deviceName+"_: "+deviceHardwareAddress);
                button.setOnClickListener(new View.OnClickListener() {
                    public void onClick(View v) {
                        //这里写入子线程需要做的工作
                        Thread conThread = new Thread(new Runnable() {
                            @Override
                            public void run() {
                                //点击进行连接
                                connectThread=new ConnectThread(device);
                                connectThread.run();
                            }
                        });
                        conThread.start(); //启动线程
                    }
                });
                layout2.addView(button);
            }
        }
    };

我对此处广播的理解:此处的广播就相当于一个监听,我们对这个监听函数的监听方法进行了实现并对其进行注册,使其处于监听工作状态,因为我们的蓝牙扫描并没有实现对扫描数据的获取和处理,因此,此处的广播就配合扫描动作,接收蓝牙扫描获取到的数据,并实现对数据的处理。

5.设备连接

蓝牙连接有两种方式:1.作为服务器连接 ,2.作为客户端连接。作为服务器则自己端发送数据,其他设备接收,作为客户端则相反。

1.作为服务器连接:

private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;

    public AcceptThread() {
        // Use a temporary object that is later assigned to mmServerSocket
        // because mmServerSocket is final.
        BluetoothServerSocket tmp = null;
        try {
            // MY_UUID is the app's UUID string, also used by the client code.
            tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) {
            Log.e(TAG, "Socket's listen() method failed", e);
        }
        mmServerSocket = tmp;
    }

    public void run() {
        BluetoothSocket socket = null;
        // Keep listening until exception occurs or a socket is returned.
        while (true) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                Log.e(TAG, "Socket's accept() method failed", e);
                break;
            }

            if (socket != null) {
                // A connection was accepted. Perform work associated with
                // the connection in a separate thread.
                manageMyConnectedSocket(socket);
                mmServerSocket.close();
                break;
            }
        }
    }

    // Closes the connect socket and causes the thread to finish.
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) {
            Log.e(TAG, "Could not close the connect socket", e);
        }
    }
}

2.作为客户端连接:

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;

    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket
        // because mmSocket is final.
        BluetoothSocket tmp = null;
        mmDevice = device;

        try {
            // Get a BluetoothSocket to connect with the given BluetoothDevice.
            // MY_UUID is the app's UUID string, also used in the server code.
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
            Log.e(TAG, "Socket's create() method failed", e);
        }
        mmSocket = tmp;
    }

    public void run() {
        // Cancel discovery because it otherwise slows down the connection.
        bluetoothAdapter.cancelDiscovery();

        try {
            // Connect to the remote device through the socket. This call blocks
            // until it succeeds or throws an exception.
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and return.
            try {
                mmSocket.close();
            } catch (IOException closeException) {
                Log.e(TAG, "Could not close the client socket", closeException);
            }
            return;
        }

        // The connection attempt succeeded. Perform work associated with
        // the connection in a separate thread.
        manageMyConnectedSocket(mmSocket);
    }

    // Closes the client socket and causes the thread to finish.
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) {
            Log.e(TAG, "Could not close the client socket", e);
        }
    }
}

6.管理连接

连接成功后,通过以下方法实现对蓝牙通信的信息数据进行处理:

public class MyBluetoothService {
    private static final String TAG = "MY_APP_DEBUG_TAG";
    private Handler handler; // handler that gets info from Bluetooth service

    // Defines several constants used when transmitting messages between the
    // service and the UI.
    private interface MessageConstants {
        public static final int MESSAGE_READ = 0;
        public static final int MESSAGE_WRITE = 1;
        public static final int MESSAGE_TOAST = 2;

        // ... (Add other message types here as needed.)
    }

    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
        private byte[] mmBuffer; // mmBuffer store for the stream

        public ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // Get the input and output streams; using temp objects because
            // member streams are final.
            try {
                tmpIn = socket.getInputStream();
            } catch (IOException e) {
                Log.e(TAG, "Error occurred when creating input stream", e);
            }
            try {
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "Error occurred when creating output stream", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        public void run() {
            mmBuffer = new byte[1024];
            int numBytes; // bytes returned from read()

            // Keep listening to the InputStream until an exception occurs.
            while (true) {
                try {
                    // Read from the InputStream.
                    numBytes = mmInStream.read(mmBuffer);
                    // Send the obtained bytes to the UI activity.
                    Message readMsg = handler.obtainMessage(
                            MessageConstants.MESSAGE_READ, numBytes, -1,
                            mmBuffer);
                    readMsg.sendToTarget();
                } catch (IOException e) {
                    Log.d(TAG, "Input stream was disconnected", e);
                    break;
                }
            }
        }

        // Call this from the main activity to send data to the remote device.
        public void write(byte[] bytes) {
            try {
                mmOutStream.write(bytes);

                // Share the sent message with the UI activity.
                Message writtenMsg = handler.obtainMessage(
                        MessageConstants.MESSAGE_WRITE, -1, -1, mmBuffer);
                writtenMsg.sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Error occurred when sending data", e);

                // Send a failure message back to the activity.
                Message writeErrorMsg =
                        handler.obtainMessage(MessageConstants.MESSAGE_TOAST);
                Bundle bundle = new Bundle();
                bundle.putString("toast",
                        "Couldn't send data to the other device");
                writeErrorMsg.setData(bundle);
                handler.sendMessage(writeErrorMsg);
            }
        }

        // Call this method from the main activity to shut down the connection.
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "Could not close the connect socket", e);
            }
        }
    }
}

设备连接,管理连接总结: 

以上第5,6点主要实现的是对指定蓝牙设备的连接和实现两设备之间的通信,以下提供我使用的方法,此方法总和了连接和连接管理:

这里是作为客户端进行蓝牙连接,实现对其他蓝牙设备的数据进行接收,代码中有详细注释。

//此方法参数为需要连接的蓝牙设备的MAC地址。
public void connectBT2(String address){
        if (btSocket != null) {return;}
        if(address.isEmpty()){  return;}
        if (mBluetoothAdapter == null) {
            return;
        }
//利用MAC地址生成device对象,用于btSocket创建
        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        try {
            btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
        }
        try {
//蓝牙连接,连接的成功失败会有对应提示:
            btSocket.connect();
//runOnUiThread :不能在主线程以外操作UI,在子线程中对UI进行修改需要调用的方法。
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    connect.setText(""+"设备已连接");//此处为我app中的提示信息,可删除
                }
            });
        } catch (IOException e) {
            btSocket=null;
            Log.e("", "connectBT2: "+"连接失败 1" +e);
            return;
        } 
        try {
            if(btSocket!=null) {
                inStream = btSocket.getInputStream();
                outStream = btSocket.getOutputStream();
                lastBTHB = System.currentTimeMillis();
            }
        } catch (IOException e) {
            try {
                btSocket.close();
            } catch (IOException e2) { }
            btSocket=null;
            inStream=null;//数据输入流,用于接收数据
            outStream=null;//数据输出流,用于发送数据
            return;
        }
//以下开始对蓝牙通信传输的数据进行处理
        readBuffer = new byte[1024];
        btReadThread = new Thread(new Runnable() {
            @Override
            public void run() {
                int bytes = 0;
                while (true&&!readThreadstop) {
                    try {
                        try{
                            Thread.sleep(1);
                        }catch(InterruptedException ie){  }
                        if(inStream.available()>0){
                            Log.e(TAG, "run: 有数据传入" );
                            byte[] temp = new byte[1024];
                            // Read from the InputStream
                            if( (bytes = inStream.read(temp)) > 0 )
                            {
                                Log.e(TAG, "run:接收到的数据 "+bytes );
                                byte[] temp1 = new byte[bytes];
                                for(int i =0;i<bytes;i++)
                                {
                                    temp1[i]=temp[i];
                                }
                                final String recstr=new String(temp1);
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                    //此处实现app需要对接收到的数据进行的处理
                                    }
                                }); } } }
                    catch (IOException e) {} } }
        });
        readThreadstop =false;
        btReadThread.start();
    }

如果需要实现对其他蓝牙设备进行数据发送可以调用下面这个方法:

数据发送:

 //参数为需要发送的数据
public void write(String send) {
        byte[] bytes = send.getBytes();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    outStream.write(bytes);
                } catch (IOException e) {
                    Log.e("TAG", "Error occurred when sending data", e);
                }
            }
        }).start();
    }

至此,蓝牙的基本功能都实现了。但在蓝牙功能的实现中有很多需要注意的细节,这些在Android Studio 的指导文档里都有详细讲解。

二.我遇到的问题和解决

1.蓝牙扫描发现不了设备。

GPS没有打开。

2.蓝牙扫描需要注册广播。

没有理解广播之前,我无法理解扫描和广播的工作逻辑,因此建议有困难的可以专门去看看Android Studio中的广播。

3.线程问题。

1.在蓝牙的扫描,连接,数据的接收,发送对是否建立新的线程都有要求,详细可以看官方文档的说明。

2.在子线程中对UI进行更新需要使用 runOnUiThread 线程。

4.在蓝牙连接时有时会出现 read failed, socket might closed or timeout, read ret: -1 的报错,从而连接失败。

目前我在网上找到了两个方法:

1.修改自己的 UUID

2.设置蓝牙连接时的端口号

我使用的方法:

3.在连接时,将MAC地址作为参数,利用MAC地址生成device对象,用于btSocket创建。

方法总结:第一个方法在我这完全没用,第二个方法解决了第一次出现这种报错。之后在第二个方法上又出现了这种报错,然后使用第三个方法可以实现连接不报错,但是在多次的使用中还是会有几率出现报错。因此目前我也没有100%的解决这个 read ret: -1 de 报错。如果有大神知道这个报错的根本问题,希望得到指点,多谢!

  • 7
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Android Studio是一种非常有用的开发工具,可以用来开发各种不同类型的Android应用程序,包括经典蓝牙应用程序。经典蓝牙是一种非常常见的蓝牙协议,用于在设备之间传输数据。在Android Studio中,经典蓝牙应用程序可以通过蓝牙适配器创建和管理。开发者可以使用Android SDK中提供的蓝牙API,通过Android Studio开发经典蓝牙应用程序。 经典蓝牙应用程序在Android平台上有很多用途,包括与其他设备进行文件传输、远程控制和数据共享等。通过使用Android Studio,开发者可以轻松地实现这些功能,而不必自己编写代码。经典蓝牙是一种传统的蓝牙协议,因此在开发经典蓝牙应用程序时需要考虑其相对较低的速度和较短的范围。 在开发经典蓝牙应用程序时,开发者应该首先了解如何配置和管理蓝牙适配器。他们还应该了解如何使用Android SDK提供的蓝牙API,以便建立连接、发送和接收数据。除了基本的连接和传输功能外,开发者还可以通过使用蓝牙协议栈的高级功能来增强经典蓝牙应用程序的功能。 总之,在Android Studio中开发经典蓝牙应用程序是非常容易的。开发者可以通过使用Android SDK中提供的蓝牙API来快速构建应用程序,并且可以根据自己的需要使用高级蓝牙协议栈功能。虽然经典蓝牙可能不如最新的蓝牙标准那么快速和高级,但它仍然是一种非常实用的协议,可以用于许多不同类型的应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值