经测试稳定可用的蓝牙链接通信Demo,记录过程中遇到的问题的思考和解决办法,并整理后给出一个Utils类可以简单调用来实现蓝牙功能

说明:这是本人在蓝牙开发过程中遇到过的问题记录和分析,以及解决办法。

在研究过程中,许多的前人给出的解决方案和思路指导对我相当有帮助,但并非都是可采取的解决方法,

经过本人对这些方法的测试和使用过后,给出自己的理解和解决方案,不一定是正确的,但这些方法的确可以解决问题。

如果有人遇到同样的问题,并且看到我的文章解决,那是我的荣幸。

!!!!!!但特别需要说明的是,看的越多,不明白的越多,我的看法可能是完全错误的,这些方法只是暂时解决了我的问题,

!!!!!!如果有人发现了我的错误,请私信或评论告知在下!不胜感激!

测试设备:小米手机两台 一台 API level 17,另一台 API level 21

蓝牙过程中的主要问题种类:

1.查找蓝牙设备报空指针异常

原因:蓝牙查找的操作,在系统中是异步的,若是直接顺序执行,可能查找操作的线程还没有执行完毕,所以返回值是空的!

解决办法:异步操作,得到返回结果后在继续执行后续操作,例如,通过Handler发送异步信息来执行

2.建立Server端失败,报错的种类有:

3.建立Client端时,connect链接出错,报错的种类有:

以上两种情况参见总结第二点!

4.可以成功建立连接并通信,但容易启动会失败,非常不稳定

原因:

有前辈说是因为服务端的监听端口号是随机生成的,当服务端和客户端的端口生成的号码恰好一致,那么可以稳定运行,如果不一致,则无法链接,这就是不稳定的原因。

并且给出了通过反射来固定端口来建立稳定链接的解决方法:

建立服务端:

BluetoothServerSocket this.Server = (BluetoothServerSocket) adapter.getClass().getMethod("listenUsingRfcommOn",new Class[]{int.class}).invoke(adapter,10);

建立服务端:

BluetoothSocket tmp = (BluetoothSocket) REMOTE_DEVICE.getClass().getMethod("createRfcommSocket",new Class[]{int.class}).invoke(REMOTE_DEVICE,10);

  

方案尝试与分析:经过追踪源码和检测端口号码,端口号的确是随机生成的,那么尝试采用反射的方法固定端口

测试结果:通过反射的方法的确可以建立稳定的连接,但是这样得到的Socket是无法通信的!

5.某次成功传递过数据后,没有关闭连接,照理来说,应该还是能够继续通信的,但若继续传递信息,毫无响应

原因:Log信息给出的是Socket已经关闭或时间超过或读取内容为-1.

分析:

执行链接操作会马上报错,不会是时间超过这个原因,而没有执行到读取的步骤,不会是读取内容出错!所以只能是Socket已关闭!

但问题是,我没有执行关闭Socket的操作!也就是说,这个Socket是系统自动关闭的!有可能是在关闭流的时候自动关闭了!

6.在某种情况下,确定通信成功率已经是可信赖的程度,但为了进一步完善继续调试的过程中出现问题,将代码回到先前成功的状态,也不能正常运行了

原因:

本着从可用的情况下逐步修改,直到不可用的那一步,那么这一步就是出错的原因,这个思想原本是十分有效和科学的!

可是如果这种错误的出现,这种思想毫无意义!

直到相当偶然的一次情况下,我进行了接下来的操作:关闭蓝牙,删掉应用,重启机器,再次安装应用,测试!一切回复正常,完美运行!

而在接下来的调试过程中,遇到了同样的问题,通过这样的操作,也多次验证了这个方法可以解决这个问题。(传说中的重启解决一切问题!!!!!!)

而我自己曾经遇到过一个问题:说我的应用已经注册了20个Service,已经没有可用空间了!

这里特别提醒:

1.蓝牙测试过程中,非常容易崩溃,如果动态注册了广播,而程序直接崩溃就无法执行取消注册的操作,这样就非常容易注册了20个Service,且目前为止,本人没有找到一个方法来取消应用注册的广播

2.初始化服务端,建立UUID监听,也是一个Service,如果程序直接崩溃,没有关闭,也会成为一个无法取消注册的Service

注:这个错误的的LOG信息显示,这是Native层的代码!而这种情况也会直接导致无法创建新的蓝牙设备通信的服务端!

如何解决这个问题?

重启大法:卸载应用,重启开启手机,再次安装应用!

原理:重启系统,等于重新初始化Davik虚拟机,而在初始化的时候,由于已经卸载了应用,没有加载我们写的应用的相关信息!这样一来,注册过的服务信息就取消了!

蓝牙设备在测试使用过程中,会出现多次崩溃,很容易产生问题,而蓝牙是通过JNI调用Native层代码里间接实现的,并非应用能够操作的范围!

崩溃的影响只能通过重新启动来清除!

总结:

原本有很多的问题出现,并且困扰我很久,但现在做出来了,我却已经无法再次故意产生当时的那种错误!

我只能理解为:当时之所以发生问题,是有什么地方我没有正确理解!

如果你看完这篇文章,做了同样的代码或直接用我的Util类,依然没有解决与我相同的问题,那么可能有一句话对你有帮助:

无法实现的原因,其问题不是出在代码运行上,而是处在交互过程上!

1.蓝牙服务端一个UUID,只能创建一个Server,并且用完记得关闭,若是没有关闭会影响接下来的使用。但客户端可以通过同一个UUID连接到一个Server,该功能需要设备的支持(Android版本大于lolipop)。

2.蓝牙需要遵循一用一连的模式,连完要关闭!

服务端Server若是没有关闭,不能再次创建Server,而Server没有处于accpte状态,客户端也是无法实现链接的

(这一点至关重要!这一点至关重要!这一点至关重要!

本人的很多问题都是没有遵循这一模式而出现的错误!)

接下来贴出一个本人写的用于蓝牙操作的工具类,通过对这个类可以简单调用来使用蓝牙功能,相应的解释在注释里相当清楚不再多说:

public class bluetoothUtils {

    //本机为NATIVE_DEVICE,连接的机器为REMOTE_DEVICE
    private BluetoothDevice REMOTE_DEVICE;
    //类似于Socket中的id地址,但是不知为何原因,这里必须使用这个的UUID
    protected static final UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    //蓝牙适配器,一般使用系统默认的
    private BluetoothAdapter adapter;
    //用来发送寻找蓝牙设备的请求码
    private static final int REQUES_CODE = 1234;
    private static final String ServerName = "自定义蓝牙服务器";
    //存放查找到的蓝牙设备的容器
    private Set<BluetoothDevice> ParieDevices;
    //这里的Server是一个服务端线程对象,具体的服务端数据在线程内部操作
    public BluetoothServerSocket Server;     //这里属性设置为公开的,是为了提供通过util类直接操作对应线程的渠道
    //客户端同服务端
    public BluetoothSocket Client;
    //蓝牙的搜索操作是通过广播给出结果的,所以需要在Activity中去进行接受和处理,
    //如果是在Fragment中,由于Activity和Fragment是相互关联的,可以使用getActivity()间接操作
    private Activity activity;

    public bluetoothUtils(Activity activity) {
        this.adapter = BluetoothAdapter.getDefaultAdapter();
        this.activity = activity;
    }

    /*
    * !这是一个异步的操作,若是顺序调用的话,容易产生空指针异常
    * */

    public Set<BluetoothDevice> QueryDevice() {
        if (adapter.isEnabled()) {
            //开启查找周围的蓝牙设备,但是搜索到的结果是通过广播消息类发送的,需要使用广播来接收结果
            adapter.startDiscovery();

            //获取已经绑定过的蓝牙设备
            this.ParieDevices = adapter.getBondedDevices();
            if (this.ParieDevices.size() > 0) {
                return this.ParieDevices;
            } else {
                return null;
            }
        }else{
            Intent i = new Intent(adapter.ACTION_REQUEST_ENABLE);
            this.activity.startActivityForResult(i, REQUES_CODE);
            adapter.enable();
            //打开蓝牙设备后再次调用查找方法
            return QueryDevice();
        }
    }

    /*
    * 当获取到结果后,增加记录并通知更新,但需要注意的是这个容易里存放的是已经匹配过的蓝牙设备!
    * */
    public void addAndNotify(BluetoothDevice device){
        this.ParieDevices.add(device);
        notify();
    }

    /*
    * 这是用于匹配已经匹配过的蓝牙设备的方法,如果一个蓝牙设备未曾匹配过,则无法通过本方法实现匹配
    * */

    public boolean Match(String address) {

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

        this.REMOTE_DEVICE = adapter.getRemoteDevice(address);
        if (this.REMOTE_DEVICE.getAddress() == address) {
            //设定返回值是为了给出是否匹配成功的响应
            return true;
        }
        return false;
    }

    /*
    *  特别需要说明的是,本方法中的accpte方法是线程阻塞运行的
    *  考虑后,选择保持他阻塞的特点,在调用时再使用异步操作,
    *  这样做的好处是,保留了系统原本阻塞的特点,只在最终步骤编写的时候再去异步操作,方便调度和监听
    * */

    public BluetoothSocketManager createServer(){
        this.Server =  new Server(adapter,ServerName,uuid).getServerSocket();
        BluetoothSocket temp = null;
        try {
            temp = this.Server.accept(30000);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new BluetoothSocketManager(temp);
    }

    /*
    * 不要重复调用本方法,一个UUID只能使用一次,作为客户端或服务端连接对方,重复调用会失败!
    * */

    public BluetoothSocketManager createClient(){
        return new BluetoothSocketManager(new Client(REMOTE_DEVICE).getClientSocket());

    }

    /*
    *  Server类
    *  @ServerName : 服务端的名称,默认使用Util类的预定名称:自定义蓝牙服务端
    *  @uuid      : 类似与IP和域名一样,用来匹配链接的编码,原本是可以随机生成和使用的,但是为了能够正确连接客户端和服务端,默认使用Util的预定UUID
    *               特别说明:安卓手机与蓝牙模块连接的话,一定要使用Util类预定义的那个UUID!
    *               而蓝牙链接是一个近距离的临时连接,无需担心安全问题,可以统一使用预定义的UUID:00001101-0000-1000-8000-00805F9B34FB。
    * */

    class Server{
        private BluetoothServerSocket Server;

        public Server(BluetoothAdapter adapter,String ServerName,UUID uuid) {
            try {
                this.Server = adapter.listenUsingInsecureRfcommWithServiceRecord(ServerName,uuid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public BluetoothServerSocket getServerSocket(){
            return this.Server;
        }

    }

    /*
    *   Client类
    *   @UUID: 同Server的UUID一样,用来实现连接的身份地址,统一使用:00001101-0000-1000-8000-00805F9B34FB。
    * */

    class Client{
        private BluetoothSocket Client;

        public Client(BluetoothDevice device) {
            try {
                this.Client = device.createRfcommSocketToServiceRecord(uuid);
            } catch (IOException e) {
                e.printStackTrace();
            }
            run();
        }

        /*
        * 建立了链接的信息不能马上链接,设置了本方法作为缓冲,
        * 只有通过调用本方法才会去链接,然后通过getClientSocket返回本Socket的信息,用于建立Manager对象
        * */

        private void run() {
            try {
                this.Client.connect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public  BluetoothSocket getClientSocket(){
            return this.Client;
        }
    }

    /*
    *   Manager类
    *   说明:
    *   1.通过Util类的createServer和createClient都可以获得Manager对象,这也是预定义的新建方法。
    *   2.由于保持链接和消息交互需要一条独立的线程来完成,所以选择继承线程类,使得Manager对象可以被当作线程来操作
    *
    *   @监听消息:.在新建完成后,调用线程的start方法,即可启动输入消息的监听,而监听到消息后,默认进行当前界面Toast显示,可以自行更改,只是需要注意线程调度问题
    *   @发送消息: 通过write()方法来发送
    *
    *   特别注意:这是一个一直运行的线程对象,一定要记得调用Destory方法后,摧毁该线程,避免泄露。
    * */

    public class BluetoothSocketManager extends Thread{
        private BluetoothSocket socket;
        private InputStream inputStream;
        private OutputStream outputStream;
        private Handler handler;

        public BluetoothSocketManager(BluetoothSocket socket) {
            this.socket = socket;
            this.handler = new Handler(Looper.getMainLooper());
            try {
                this.inputStream = this.socket.getInputStream();
                this.outputStream = this.socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /*
        * Manager对象是通过继承线程类来写的,当调用线程的start方法后,就处于不断监听的状态一直读取接收到的信息
        * */

        @Override
        public void run() {
            final byte[] bytes = new byte[1024];
            int  i = 0;

            while (true) {
                try {
                    while ( (i = this.inputStream.read(bytes)) != -1) {
                        if (i > 0) {
                            handler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(activity.getApplicationContext(),new String(bytes),Toast.LENGTH_SHORT).show();
                                }
                            },0);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        public void write(String s){
            try {
                this.outputStream.write(s.getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /*
        * 如果想要断开该Socket链接,那么千万记得调用本方法来关闭链接
        * 蓝牙是通过Native层间接调用的,如果未曾关闭,很容易出现无法使用的情况
        * 如果出现这种情况,万能的解决办法:卸载应用,关闭蓝牙,重启手机,重新安装
        * */

        public void Destory(){
            try {
                this.inputStream.close();
                this.outputStream.close();
                this.socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}

  

在一个Activity中的客户端和服务端使用示例:

import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import java.util.Set;

/**
 * Created by Andrew on 2016/10/15.
 */

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView Show;
    private com.bluetoothdemo.bluetoothUtils bluetoothUtils;
//    private bluetoothUtils bluetoothUtils;
    private Set<BluetoothDevice> device;
    private Handler handler;
    private String line;
    private int i = 0;
    private com.bluetoothdemo.bluetoothUtils.BluetoothSocketManager ServerManager;
    private com.bluetoothdemo.bluetoothUtils.BluetoothSocketManager ClientManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Show = (TextView) findViewById(R.id.textView);
        bluetoothUtils = new bluetoothUtils(MainActivity.this);

        findViewById(R.id.btn1).setOnClickListener(this);
        findViewById(R.id.btn2).setOnClickListener(this);
        findViewById(R.id.btn3).setOnClickListener(this);
        findViewById(R.id.btn4).setOnClickListener(this);

        handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 10){
                    String line = (String) msg.obj;
                    Show.append(line);
                }
//                if (msg.what == Socket_Message){
//                    String line = (String) msg.obj;
//                    Show.append(line);
//                }
            }
        };

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                device = bluetoothUtils.QueryDevice();
            }
        },1000);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn1:
                bluetoothUtils.Match(device.iterator().next().getAddress());
                ServerManager = bluetoothUtils.createServer();
                Log.e("TAG","after initServer ");
                ServerManager.write("hi!"+ i++);
                ServerManager.start();
                break;
            case R.id.btn2:
                bluetoothUtils.Match(device.iterator().next().getAddress());
                ClientManager = bluetoothUtils.createClient();
                ClientManager.start();
                Log.e("TAG","after initClient");

                break;
            case R.id.btn3:
                ServerManager.write("hi!"+ i++);
                break;
            case R.id.btn4:
                ClientManager.write("hi!"+ i++);
                break;
        }
    }
}

  

时间: 2024-10-05 04:38:34

经测试稳定可用的蓝牙链接通信Demo,记录过程中遇到的问题的思考和解决办法,并整理后给出一个Utils类可以简单调用来实现蓝牙功能的相关文章

链接与加载过程中,几个关键的概念

http://www.cnblogs.com/qiaoconglovelife/p/5870000.html 加载(load) 将程序拷贝到存储器并运行的过程,由加载器(loader)执行. 链接分类 编译时(compile time)链接:也称为传统静态链接.静态链接: 加载时(load time)链接:在程序被加载的时候动态链接共享库: 运行时(run time)链接:在程序运行时根据需要动态链接共享库. 目标文件 可重定位目标文件:可被链接生成可执行目标文件: 可执行目标文件:可被直接拷贝

项目中遇到的超卖问题及解决办法(使用go做测试工具)

超卖问题:在一个很短的时间内,Mysql的数据状态在 取出,比较,提交,或修改中,另外一个进程访问数据导致的超卖问题. 案例: 1.前端没有做限制,如果用户连续点击签到,那么会有多条数据发送到后端,如果数据状态没有来得及完全修改过来,导致用户的签到数据被多次添加. 2.每天签到用户的前3名用户可以获得一张价值100元的优惠券,如果有多名用户在很短的时间内同时签到,那么就会有多发的问题. 解决案例1:使用数据库的行锁和表锁 DROP TABLE IF EXISTS `crm_concurrency

iOS: 删除真机测试的Provisioning Profile后,在Code Singing中出现entitlements.plist文件无效,解决办法如下:

问题主题:method to The entitlements specified in your application’s Code Signing Entitlements file do not mat 问题描述:Error:The entitlements specified in your application’s Code Signing Entitlements file do not match those specified in your provisioning pro

下载的pod链接失效,build diff: /../Podfile.lock: No such file or directory解决办法

build diff: /../Podfile.lock: No such file or directory 1.终端进入文件路径,执行pod install 2.在工程设置中的Build Phases下删除Check Pods Manifest.lock及Copy Pods Resources 3.clean工程,然后编译就ok了 不行再试试下面方法: 工程使用CocoaPods管理第三方库,在新的目录update版本的时候出现如下问题 问题1描述: diff: /../Podfile.lo

Linux 系统使用WordPress开启“固定链接设置”之后部分页面打不开(404)的解决办法

参考 https://blog.51cto.com/wulin0710/1319054 httpd.conf 中 <Directory "/var/www/www"> 中间的AllowOverride none改为AllowOverride all 重启Apache成功解决 原文地址:https://www.cnblogs.com/lnxcode/p/11139155.html

Android借用QQ开放平台,简单实现联系客服功能

公司做的电商APP,现在要加入联系客服功能,起初想的是做一个即时聊天系统,以前没做过,这两天开始恶补,后来需求变了,改成调用QQ开放平台,做一个临时会话就行了,这就省了不少力气,也不需要再去组件服务端了. 步骤: 1.引用QQ SDK源码文件. 创建一个工程,并把open-sdk.jar文件和mta_sdk_x.x.x.jar文件拷贝到libs(或lib)目录下,如下图所示: 选中open-sdk.jar和mta_sdk_x.x.x.ja,右键菜单中选择Build Path, 选择Add to 

BluetoothChat用于蓝牙串口通信的修改方法

本人最近在研究嵌入式的串口通信,任务是要写一个手机端的遥控器用来遥控双轮平衡小车.界面只用了一个小时就写好了,重要的问题是如何与板子所带的SPP-CA蓝牙模块进行通信. SPP-CA模块自带代码,在这里我使用的全部都是SPP-CA的默认模式.其中波特率是9600.读者若要修改其匹配密码,波特率等请使用串口调试工具对SPP-CA使用AT命令进行修改.详情参考其技术手册. 首先介绍Android端,官方的SDK中给了一个BluetoothChat的版本,这个版本稍加修改就可以进行串口通信.由于源代码

Android蓝牙实例(和单片机蓝牙模块通信)

最近做毕设,需要写一个简单的蓝牙APP进行交互,在网上也找了很多资料,终于给搞定了,这里分享一下^_^. 1.Android蓝牙编程 蓝牙3.0及以下版本编程需要使用UUID,UUID是通用唯一识别码(Universally Unique Identifier),这是一个软件构建的标准,也是被开源基金会组织应用在分布式计算环境领域的一部分.在蓝牙3.0及下一版本中,UUID被用于唯一标识一个服务,比如文件传输服务,串口服务.打印机服务等,如下: #蓝牙串口服务 SerialPortService

Android蓝牙串口通信模板

转载请注明出处,谢谢http://blog.csdn.net/metalseed/article/details/7988945 Android蓝牙操作:与蓝牙串口模块通信,或其他蓝牙设备通信. 初涉android的蓝牙操作,按照固定MAC地址连接获取Device时,程序始终是异常终止,查了好多天代码都没查出原因.今天改了一下API版本,突然就成功连接了.总结之后发现果然是个坑爹之极的错误. 为了这种错误拼命查原因浪费大把时间是非常不值得的,但是问题不解决更是揪心.可惜我百度了那么多,都没有给出