码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2016-07-13 16:23:35      阅读:193      评论:0      收藏:0      [点我收藏+]

标签:

第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就设计好了。

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

标签:

原文地址:http://blog.csdn.net/anddlecn/article/details/51897504

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!