手把手教你做蓝牙聊天应用(四)-蓝牙连接模块

第4节 蓝牙连接模块

蓝牙连接的管理模块需要为ChatActivity提供于连接相关的所有功能,要设计的方便使用,并尽量隐藏连接的细节。

4.1 对外接口

我们首先来看看ConnectionManager需要向Chat Activity提供哪些接口。

  1. 监听。当应用运行起来后,聊天应用需要启动对其它蓝牙设备的监听,迎接随时可能到来的连接请求。所以ConnectionManager需要提供启动监听-startListen()停止监听-stopListen()的两个接口;
  2. 主动连接。应用搜索到可连接到设备后,可以主动的连接对方设备。假如正在连接,用户可能会取消连接;假如已经连接上,用户可能会想断开这个连接。所以ConnectionManager需要提供连接-connect()断开连接-disconnect()(包含取消连接的功能)两个接口;
  3. 主动查询当前连接状态。应用在采取某些操作的时候,可能需要知道当前的连接状态。所以需要提供主动查询当前连接状态的接口。

    连接的状态,我们可以把它分成两类,一个是监听的状态,一个是连接的状态。监听的状态表示当前是否在监听;连接的状态应该包括没有连接,正在连接和已经连接。

    所以接口应该有getCurrentListenState()getCurrentConnectState()

  4. 获取连接状态变化通知。应用界面需要根据连接的状态做出相应的变化,需要ConnectionManager能在连接状态发生变化的时候,主动把信息通知给ChatActivity。所以还要提供一个通知状态变化的接口。
  5. 接收数据。ConnectionManager收到数据以后,需要一个机制将数据通知给ChatActivity,这样ChatActivity才能显示接收到的内容。
  6. 发送数据。用户要发送数据给被连接的设备,所以ConnectionManager要提供一个发送数据的接口-sendData()

45都需要ConnectionManager主动向ChatActivity传递信息(收到的数据、状态改变),可以通过设计监听器来实现。因此需要设计安装监听器的方法:定义一个回调的接口-ConnectionListener;把监听器设置到ConnectioManager中。

综上所述,ConnectionManager大概应该是这个样子,

public class ConnectionManager {

    //定义监听器
    public interface ConnectionListener {

        //当ConnectionManager的连接状态发生变化,
        //通过onConnectStateChange()将变化通知到ChatActivity
        public void onConnectStateChange(int oldState, int State);
        //当ConnectionManager的监听状态发生变化,
        //通过onListenStateChange()将变化通知到ChatActivity
        public void onListenStateChange(int oldState, int State);
        //当ConnectionManager的数据发送完成后,
        //通过onSendData()将发送的内容通知到ChatActivity
        public void onSendData(boolean suc, byte[] data);
        //当ConnectionManager的接收到对方连接设备传来的数据,
        //通过onReadData()将传来的数据通知到ChatActivity
        public void onReadData(byte [] data);
    }

    private ConnectionListener mConnectionListener;

    //构造函数中设置监听器
    public ConnectionManager(ConnectionListener cl) {
        mConnectionListener = cl;
    }

    public void startListen() {
    }

    public void stopListen() {
    }

    public void connect() {
    }

    public void disconnect() {
    }

    public int getCurrentListenState() {
    }

    public int getCurrentConnectState() {
    }

    public void sendData(byte[] data) {
    }

}


/*******************************************************************/

* 版权声明

* 本教程只在CSDN安豆网发布,其他网站出现本教程均属侵权。

/*******************************************************************/


4.2 ConnectionManager的结构

我们已经明确了ConnectionManager对外的接口。接下来就需要来实现功能了。

由Android SDK提供的蓝牙连接接口函数,很多都是阻塞的(wifi连接也是如此),例如accept()connect()等等,所以就需要将这些操作放到单独的工作线程中进行。

因此,我们将会在ConnectionManager中设计两个工作线程,

  1. AcceptThread:负责监听别的蓝牙设备发起的连接;
  2. ConnectThread:用来维持与其它设备的连接。

主线程首先创建一个监听线程,用来接收其它设备可能发出的连接请求,当监听线程监听到了连接请求,就会得到一个Socket;然后我们再创建一个连接线程,将Socket交给连接线程,两个设备就可以通过这个Socket,在连接线程中进行消息的发送和接收了。

4.2.1 ConnectionManager

  • AcceptThread线程的工作状态,反应的就是监听状态。

    它包括LISTEN_STATE_IDLELISTEN_STATE_LISTENING。这个状态保存在mListenState变量中;

    当监听状态发生改变的时候,通过setListenState()设置状态的改变,并利用回调的方式,将改变的状态通知给关注者。

    public class ConnectionManager {
        ......
        //监听的两种状态
        public static final int LISTEN_STATE_IDLE = 3;
        public static final int LISTEN_STATE_LISTENING = 4;
    
        //记录当前监听的状态
        private int mListenState = LISTEN_STATE_IDLE;
    
        //修改当前监听的状态
        private void setListenState(int state) {
    
            //状态没有发生变化,不用通知
            if(mListenState == state) {
                return;
            }
    
            int oldState = mListenState;
            mListenState = state;
    
            //状态发生变化,发起通知
            if(mConnectionListener != null) {
                mConnectionListener.onListenStateChange(oldState, mListenState);
            }
        }
        ......
    }
  • ConnectedThread线程的工作状态,反应的就是连接状态。

    它包括CONNECT_STATE_IDLE CONNECT_STATE_CONNECTINGCONNECT_STATE_CONNECTED;

    这个状态保存在mConnectState变量中;

    当连接状态发生改变的时候,通过setConnectState()设置状态的改变,并利用回调的方式,将改变的状态通知给关注者。

    public class ConnectionManager {
        ......
        //连接的三种状态
        public static final int CONNECT_STATE_IDLE = 0;
        public static final int CONNECT_STATE_CONNECTING = 1;
        public static final int CONNECT_STATE_CONNECTED = 2;
    
        //记录当前连接的状态
        private int mConnectState = CONNECT_STATE_IDLE;
    
        //修改当前连接的状态
        private void setConnectState(int state) {
    
            ///状态没有发生变化,不用通知
            if(mConnectState == state) {
                return;
            }
    
            int oldState = mConnectState;
            mConnectState = state;
    
            //状态发生变化,发起通知
            if(mConnectionListener != null) {
                mConnectionListener.onConnectStateChange(oldState, mConnectState);
            }
        }
        ......
    }
  • ConnectionManager对外提供的接口,实际上就是对这两个工作线程的控制;
public class ConnectionManager {
    ......
    private ConnectionListener mConnectionListener;

    private AcceptThread mAcceptThread;
    private ConnectedThread mConnectedThread;
    private final BluetoothAdapter mBluetoothAdapter;

    //构造函数中设置监听器
    public ConnectionManager(ConnectionListener cl) {
        mConnectionListener = cl;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    public void startListen() {
        //创建监听线程
        if(mAcceptThread != null) {
            mAcceptThread.cancel();
        }

        mAcceptThread = new AcceptThread();
        mAcceptThread.start();
    }

    public void stopListen() {
        //停止监听线程
        if(mAcceptThread != null) {
            mAcceptThread.cancel();
        }
    }

    public void connect() {
        //发起连接
        if(mConnectedThread != null) {
            mConnectedThread.cancel();
        }

        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(deviceAddr);

        try {
            //创建发起主动连接使用的Socket
            BluetoothSocket socket = device.createRfcommSocketToServiceRecord(BT_UUID);
            //启动连接线程
            mConnectedThread = new ConnectedThread(socket, true);
            mConnectedThread.start();
        } catch (IOException e) {

        }
    }

    public void disconnect() {
        //停止连接线程
        if(mConnectedThread != null) {
            mConnectedThread.cancel();
        }
    }

    public int getCurrentListenState() {
        //查询当前监听线程的状态
        return mListenState;
    }

    public int getCurrentConnectState() {
        //查询当前连接线程的状态
        return mConnectState;
    }

    public boolean sendData(byte[] data) {
        //发送数据
        if(mConnectedThread != null
            && mConnectState == CONNECT_STATE_CONNECTED) {
            mConnectedThread.sendData(data);

            return true;
        }
        return false;
    }
    ......
}

通过这里,我们可以看出,为了能够取消正在运行的工作线程,在设计AcceptThreadConnectedThread的时候,我们需要给它们添加上取消的方法-cancel();为了在连接后能够发送数据,需要给ConnectedThread添加发送数据的方法-sendData()

4.2.2 监听线程

监听线程进行的工作有,

  1. 等待其它蓝牙设备发起的连接;
  2. 如果接收到连接的请求,就创建出一个Socket
  3. 之后继续等待其它设备可能发起的连接;
  4. 假如已经处于正在连接或者已经连接的状态,就断开最新收到的连接,因为我们假定了每次只能连接一个设备;
  5. 监听线程可以被取消,退出运行;
public class ConnectionManager {
    ......
    private class AcceptThread extends Thread {

        private BluetoothServerSocket mServerSocket;
        private boolean mUserCancel;

        public AcceptThread() {
            BluetoothServerSocket tmp = null;
            mUserCancel = false;

            //创建监听用的ServerSocket
            try {
                tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
                        BT_NAME, BT_UUID);
            } catch (IOException e) {

            }
            mServerSocket = tmp;
        }

        //监听线程开始运行
        @Override
        public void run() {

            setName("AcceptThread");

            //将ConnectionManger监听的状态设置成“正在监听”
            setListenState(LISTEN_STATE_LISTENING);

            BluetoothSocket socket = null;

            while(!mUserCancel) {
                try {
                    //阻塞在这里,等待别的设备连接
                    socket = mServerSocket.accept();

                } catch (IOException e) {
                    //阻塞过程中,如果其它地方调用了mServerSocket.close(),
                    //将会进入到这个异常当中
                    mServerSocket = null;
                    break;
                }

                if(mConnectState == CONNECT_STATE_CONNECTED
                || mConnectState == CONNECT_STATE_CONNECTING) {
                    //如果当前正在连接别的设备,
                    //或者已经和别的设备连接上了,就放弃这个连接,
                    //因为每次只能和一个设备连接
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                else if(mConnectState == CONNECT_STATE_IDLE) {
                    //如果当前没有和别的设备连接上,
                    //启动连接线程
                    mConnectedThread = new ConnectedThread(socket, false);
                    mConnectedThread.start();
                }
            }

            if(mServerSocket != null) {
                try {
                    mServerSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mServerSocket = null;
            }
            setListenState(LISTEN_STATE_IDLE);
            mAcceptThread = null;
        }

        //让监听线程退出运行
        public void cancel() {
            try {
                mUserCancel = true;
                //ServerSocket此时阻塞在accept()方法中,
                //关闭之后,会让accept()方法抛出异常,实现监听线程的退出
                if(mServerSocket != null) {
                    mServerSocket.close();
                }
            } catch (IOException e) {
            }
        }
    }
    ......

}

4.2.3 连接线程

连接线程的工作是,

  1. 假如是主动连接,连接线程要主动调用connect()方法;该方法是一个阻塞调用,在调用前,应该把ConnectionManager的连接状态,设置成CONNECT_STATE_CONNECTING;如果连接成功,就把状态设置成CONNECT_STATE_CONNECTED
  2. 假如是被动连接,那么从监听线程获取的Socket就已经是被连接上了的Socket,不需要进行connect()的操作了;
  3. 从已经连接的Socket当中,获取输入、输出的流接口,以后这个连接上的数据读取和写入,就是通过它们对应的流接口进行的;
  4. 连接线程进入循环,不断的尝试通过InputStreamread()方法,读取数据;
  5. 连接线程可以被取消,退出连接;
public class ConnectionManager {

    ......

    private class ConnectedThread extends Thread {

        private final int MAX_BUFFER_SIZE = 1024;

        private BluetoothSocket mSocket;
        private InputStream mInStream;
        private OutputStream mOutStream;
        private boolean mUserCancel;
        private boolean mNeedConnect;

        //needConnect参数告诉连接线程是否需要主动发起连接,
        //因为通过监听线程启动的连接线程是不需要发起主动连接的,
        //所以需要一个标志位来控制这种情况
        public ConnectedThread(BluetoothSocket socket, boolean needConnect) {

            //保存下工作线程中要用到的参数
            setName("ConnectedThread");
            mNeedConnect = needConnect;
            mSocket = socket;
            mUserCancel = false;
        }

        @Override
        public void run() {

            //将ConnectionManager的连接状态修改成CONNECT_STATE_CONNECTING
            setConnectState(CONNECT_STATE_CONNECTING);

            //如果这是一个主动连接,说明Socket还没有和对方相连接,就需要发起主动连接
            if(mNeedConnect && !mUserCancel) {
                try {
                    mSocket.connect();
                } catch (IOException e) {
                    //主动连接发生异常,回到未连接的状态
                    setConnectState(CONNECT_STATE_IDLE);
                    mSocket = null;
                    mConnectedThread = null;

                    return;
                }
            }

            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            //从连接的Socket中获取读数据和写数据的流接口
            try {
                tmpIn = mSocket.getInputStream();
                tmpOut = mSocket.getOutputStream();
            } catch (IOException e) {
                setConnectState(CONNECT_STATE_IDLE);
                mSocket = null;
                mConnectedThread = null;

                return;
            }

            mInStream = tmpIn;
            mOutStream = tmpOut;

            //将ConnectionManager的连接状态修改成CONNECT_STATE_CONNECTED
            setConnectState(CONNECT_STATE_CONNECTED);

            byte[] buffer = new byte[MAX_BUFFER_SIZE];
            int bytes;

            while (!mUserCancel) {
                try {
                    //阻塞在这里,用流接口等待读取数据
                    bytes = mInStream.read(buffer);
                    //将读取到的数据传递给关注它的组件
                    if(mConnectionListener != null && bytes > 0) {

                        byte [] data = new byte[bytes];
                        System.arraycopy(buffer, 0, data, 0, bytes);
                        mConnectionListener.onReadData(data);
                    }
                } catch (IOException e) {
                    //阻塞过程中,如果其它地方调用了mSocket.close(),
                    //或者对方的连接关闭
                    //将会进入到这个异常当中
                    break;
                }
            }

            setConnectState(CONNECT_STATE_IDLE);
            mSocket = null;
            mConnectedThread = null;
        }

        //让连接线程退出运行
        public void cancel() {
            try {
                mUserCancel = true;
                //Socket此时阻塞在InputStream的read()方法中,
                //关闭之后,会让read()方法抛出异常
                if(mSocket != null) {
                    mSocket.close();
                }

            } catch (IOException e) {
            }
        }

        //向对方发送数据
        public void sendData(byte[] data) {
            try {
                //用流接口发送数据
                mOutStream.write(data);
                //向关心的组件通知发送成功
                if(mConnectionListener != null) {
                    mConnectionListener.onSendData(true, data);
                }
            } catch (IOException e) {
                //向关心的组件通知发送失败
                if(mConnectionListener != null) {
                    mConnectionListener.onSendData(false, data);
                }
            }
        }
    }
    ......
}

至此,ConnectionManager就设计好了。

时间: 2024-08-12 14:32:45

手把手教你做蓝牙聊天应用(四)-蓝牙连接模块的相关文章

手把手教你做Android聊天机器人

我相信大家应该知道有款应用叫小黄鸡吧! 如果不知道,那你见过那种可以秒回复的聊天应用么? 如果仍然没看到!那你总见过可以迅速回复你的微信公共账吧! 如果仍然....亲出门左拐 好,不多说. 首先大家都应该了解程序,程序就是由人为的设定搭建起来的一套系统, 这里的机器人也是,简单的原理就是当你输入关键字后,通过一套算法, 在数据库中找到与之最为匹配的内容在返回给你.这个已经有人实现,我们这节教程就用别人 已经实现好的东西去做一??个应用来玩耍~~ (当然如果你非要想知道如何去做,给我留言.看情况我

手把手教你做视频播放器(四)

第5节 刷新与停止刷新列表 虽然经过我们的假设,忽略了很多不需要关注的视频文件,但设备上依然有可能有很多的满足了我们假设条件的视频存在,这时就需要一个"取消刷新"的功能. 如果视频还没有刷新完,就被取消了,然后又希望继续刷新,那么还需要一个手动开始"刷新"的功能. 因此,准备在ActionBar的右上角,设置一个菜单项,让用户可以"刷新",也能"停止刷新". 5.1 添加刷新菜单项 在制作"计算器"的文档里

手把手教你做关键词匹配项目(搜索引擎)---- 第九天

第九天 回顾: 8. 手把手教你做关键词匹配项目(搜索引擎)---- 第八天 7. 手把手教你做关键词匹配项目(搜索引擎)---- 第七天 6. 手把手教你做关键词匹配项目(搜索引擎)---- 第六天 5. 手把手教你做关键词匹配项目(搜索引擎)---- 第五天 4. 手把手教你做关键词匹配项目(搜索引擎)---- 第四天 3. 手把手教你做关键词匹配项目(搜索引擎)---- 第三天 2. 手把手教你做关键词匹配项目(搜索引擎)---- 第二天 1. 手把手教你做关键词匹配项目(搜索引擎)---

微信测试工程师手把手教你做弱网络模拟测试

微信测试工程师手把手教你做弱网络模拟测试 Posted by 腾讯优测 | 3,152 views 小优有话说: app研发不同于实验室里做研究,哪里有"理想环境". 理想里,用户用着性能卓越的手机,连着畅通无阻的wifi网络. "哇塞!这个app好用到飞起!" 现实是,他们可能正用着你闻所未闻的机型,穿梭于地铁.公交.火车.乡间.大山-.. 信号"若隐若现,扑朔迷离" "我去!又crash了!" "唉,怎么又连不上

手把手教你做关键词匹配项目(搜索引擎)---- 第三天

第三天 小王(运营总监)看到小丁丁整天都在淘宝.百度.魔方.拍拍上面淘关键词,每天花费的时间好长,工作效率又低,拿着这个借口来找到我. 说到:小帅帅,你看小丁丁每天都在淘宝.百度.魔方.拍拍上面淘关键词花费的时间好长,你能不能帮帮忙,看看能不能让系统自己做啦,这样可以节省好多人力,带来的效益多高.(0 其实就是为了掩饰他们懒惰 0) 小帅帅一听到可以带来的效益好高,王总还求着我呢 ,马上 两眼冒着星光,是该好好体现, 解决这个问题就可以体现出我的价值. 小帅帅拍着胸膛保证到:王总,这个小KS啦,

UWP Jenkins + NuGet + MSBuild 手把手教你做自动UWP Build 和 App store包

背景 项目上需要做UWP的自动安装包,在以前的公司接触的是TFS来做自动build. 公司要求用Jenkins来做,别笑话我,之前还真不晓得这个东西. 会的同学请看一下支持错误,不会的同学请先自行脑补,我们一步一步的来. 首先我们准备2个安装包,Jenkins,NuGet 都下载最新的好了. 1. 安装Jenkins,下一步下一步.安装好了会自动浏览器跳转到http://localhost:8080/ 如下图 按照提示去C:\Program Files (x86)\Jenkins\secrets

手把手教你做关键词匹配项目(搜索引擎)---- 第六天

第六天 小帅帅周五休息后,精神估计太旺盛了,周末两天就狂欢去了,酒喝高了,把一件重要的事儿给忘记了. 周一重新整装 刺骨上战场. 一来公司,小帅帅终于记得他要做的事情,就迫不及待的整理会议报告(工作总结). 1.上周工作任务: 1) 页面提交关键词到关键词词库 2) 文件导入到关键词词库 3) 自动抓取关键此到关键词词库 2.能力的提升 1) 学会了如何读csv文件 2)  学会了curl 3)  学会了Html Dom parse 3.下周工作任务: 1) 了解下关键词词库的应用 刚写到这儿,

手把手教你做关键词匹配项目(搜索引擎)---- 第七天

第七天 小帅帅拿回去仔细研究了一个晚上. 发现代码其实都是自己写的,就多了一些类,于老大还不是抄的我的代码,心里又鄙视了于老大一番. 其实每个人都有通病,写过程的总是会鄙视写面向对象的,因为他们没体会到面向对象是啥玩意,要让他们写好可得花上好几年的工夫. 小帅帅学编程的时候,明明知道有函数这一概念,知道函数的写法,但是实际上就算一个函数里面几百行代码,也不知道去提前多个函数出来,美其名约:你看我多厉害,几百行代码耶. 小帅帅心里虽然鄙视于老大,但是看到于老大的代码怎么感觉很清爽,一切都那么自然.

手把手教你做关键词匹配项目(搜索引擎)---- 第一天

第一天 收到需求,需求如下: 1. 收集关键词,构建关键词词库. 收到这个任务,第一想法,这还不简单吗? 马上动手创建一个关键词录入界面,保存到数据库. 第一步完成了,哈哈大笑了一天,没想到事情原来如此的简单. $keywords = $_POST["keywords"]; foreach($keywords as $keyword) { #save $keyword to database .............. } 手把手教你做关键词匹配项目(搜索引擎)---- 第一天

手把手教你做关键词匹配项目(搜索引擎)---- 第二十一天

客串:屌丝的坑人表单神器.数据库那点事儿 面向对象升华:面向对象的认识----新生的初识.面向对象的番外----思想的梦游篇(1).面向对象的认识---如何找出类 负载均衡:负载均衡----概念认识篇.负载均衡----实现配置篇(Nginx) 吐槽:现在欠的文章有面向对象的认识----类的转化.面向对象的番外---思想的梦游篇(2).负载均衡 ---- 文件服务策略.手把手教你做关键词匹配项目(搜索引擎).真心太多了,能不能让我休息一会儿. 第二十一天 起点:手把手教你做关键词匹配项目(搜索引擎