指引网

当前位置: 主页 > 编程开发 > Android >

Android蓝牙开发-站在前辈的肩膀上唠嗑

来源:网络 作者:佚名 点击: 时间:2017-07-09 07:41
[摘要] Android蓝牙开发---站在前辈的肩膀上唠嗑描述一段背景:前年我找工作时,总碰到一个问题。 面试官问:“你会蓝牙开发吗?”。 我说:“不会”。 面试官答:“那,很抱歉。我们商量了一下,觉得你不适合这个岗位。” 于是我就走了,心里想:“就应
Android蓝牙开发---站在前辈的肩膀上唠嗑

描述一段背景:前年我找工作时,总碰到一个问题。

面试官问:“你会蓝牙开发吗?”。

我说:“不会”。

面试官答:“那,很抱歉。我们商量了一下,觉得你不适合这个岗位。”

于是我就走了,心里想:“就应为一个蓝牙通讯技术不会,就把我给cut了,这面试官好有想象力。”

我一个同学,都没做过编程,我半年时间都带到android开发道上了。我仅仅蓝牙没做过,研究蓝牙无非就是三两天的时间,难吗?

于是,我周末窝在家里,查阅了大量资料,实践和总结,研究透了蓝牙技术。

有些同学可能会说,蓝牙简单,无非就是扫描设备,配对,和socket通讯。

没错,是这些,但是还有很多坑你不知道,还有很多奇葩代码。废话少说,一起分享吧。



想使用蓝牙呢,首先得看手机是否支持,有些低配手机,可能就没有内置蓝牙模块。当然,一般都会有,我们可以得到唯一的蓝牙适配器,进行其他操作。

bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

然后,我们可做n多的事情。不要着急,后面会附上我的项目源码,大牛见笑了。

/**
     * 开启蓝牙
     *
     * @param activity 上下文
     * @return 是否开启成功
     */
    public static boolean openBluetooth(Activity activity) {
        //确认开启蓝牙
        if (!getInstance().isEnabled()) {
            //=默认120秒==============================================================
            //使蓝牙设备可见,方便配对
            //Intent in = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            //in.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
            //activity.startActivityForResult(in,Activity.RESULT_OK);
            //=1=============================================================
            //请求用户开启,需要提示
            //Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            //startActivityForResult(intent, RESULT_FIRST_USER);
            //=2=============================================================
            //程序直接开启,不经过提示
            getInstance().enable();
        }
        //T.showLong(context, "已经开启蓝牙");
        return getInstance().isEnabled();
    }

    //关闭蓝牙
    public static boolean closeBluetooth() {
        return getInstance().disable();
    }

    /**
     * 扫描已经配对的设备
     *
     * @return
     */
    public static ArrayList<BluetoothDevice> scanPairs() {
        ArrayList<BluetoothDevice> list = null;
        Set<BluetoothDevice> deviceSet = getInstance().getBondedDevices();
        if (deviceSet.size() > 0) {
            //存在已经配对过的蓝牙设备
            list = new ArrayList<>();
            list.addAll(deviceSet);
        }
        return list;
    }

    //开始扫描
    public static void scan() {
        getInstance().startDiscovery();
    }

    //取消扫描
    public static void cancelScan() {
        if (getInstance().isDiscovering())
            getInstance().cancelDiscovery();

    }

    //蓝牙配对
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static boolean createBond(BluetoothDevice device) {
        return bond(device, "createBond");
        /*if (device.createBond()) {
            return device.setPairingConfirmation(true);
        }
        return false;*/
    }

    //解除配对
    public static boolean removeBond(BluetoothDevice device) {
        return bond(device, "removeBond");
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private static boolean bond(BluetoothDevice device, String methodName) {
        Boolean returnValue = false;
        if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
            try {
                device.setPairingConfirmation(false);
                cancelPairingUserInput(device);
                Method removeBondMethod = BluetoothDevice.class.getMethod(methodName);
                returnValue = (Boolean) removeBondMethod.invoke(device);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return returnValue;
    }

    //取消配对
    public static boolean cancelBondProcess(BluetoothDevice device) {
        try {
            Method cancelBondMethod = BluetoothDevice.class.getMethod("cancelBondProcess");
            Boolean returnValue = (Boolean) cancelBondMethod.invoke(device);
            return returnValue.booleanValue();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    //取消用户输入
    public static boolean cancelPairingUserInput(BluetoothDevice device) {
        try {
            Method cancelPairingUserInputMethod = BluetoothDevice.class.getMethod("cancelPairingUserInput");
            Boolean returnValue = (Boolean) cancelPairingUserInputMethod.invoke(device);
            return returnValue.booleanValue();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

通过上面这段代码,应道知道了吧,获取到适配器后,可以得到当前手机已经配对的设备。同时可以开启扫描(这个时间大概是12秒,异步的),扫描到设备和扫描完成系统会发广播。故广播接收代码:

//注册蓝牙接收广播
        if (!hasRegister) {
            hasRegister = true;
            //扫描结束广播
            IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            //找到设备广播
            filter.addAction(BluetoothDevice.ACTION_FOUND);
            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
            filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
            filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
            registerReceiver(mMyReceiver, filter);
        }

private class MyReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                //搜索到新设备
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                //搜索没有配过对的蓝牙设备
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    mListData.add(device);
                    dataAdapter.refreshData(mListData);
                } else {
                    T.showLong(TwoActivity.this, device.getName() + '\n' + device.getAddress() + " > 已发现");
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {   //搜索结束
                if (mListData.size() == 0) {
                    T.showLong(TwoActivity.this, "没有发现任何蓝牙设备");
                }
                progressDialog.dismiss();
                scan.setText("重新扫描");
            } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) {
                if (TwoActivity.this.position != -1) {
                    final BluetoothDevice device = mListData.get(TwoActivity.this.position);
                    com.dk.bluetooth.tools.T.showLong(TwoActivity.this, device + "  配对成功");
                    EventBus.getDefault().post(new com.dk.bluetooth.tools.MyEvent());
                }
            }
        }
    }

以上都不是重点,核心在线程通讯。首先得了解socket,java有这个,蓝牙里面也有,是BluetoothServerSocket和BluetoothSocket两个,一个是服务器端的,一个是客户端的。不了解的朋友建议先去百度java socket用法,超级简单。

通讯需要建立信道,BluetoothServerSocket需要先启动,监听当前设备上的某UUID位置上的设备(阻塞到在此处),就跟windows的端口意思是一样的。然后BluetoothSocket再启动,根据对方的mac地址和对方监听的UUID位置,启动连接(也阻塞了),直到连上服务器了,就返回。服务器也一样,直到有人来连接了,就返回。都会返回一个BluetoothSocket,然后从这个socket里面获取input和output流。服务器端了input流是客户端的output流,另外一半也一样。剩下的收发消息就是流的读写了,简单吧。

下面贴出我的代码,应为服务器端和客户端启动的方法不一样,我分成了两个线程,由于读写的功能一样,我就共用了一套读写线程。根据这个思路看我的代码。

/**
     * 初始化及启动蓝牙socket
     *
     * @param handler         UI消息传递对象
     * @param securityType    连接的安全模式
     * @param serverOrClient  客户端或服务端
     * @param bluetoothDevice 服务器端设备
     */
    public BluetoothChatService(Handler handler, SecurityType securityType, ServerOrClient serverOrClient,
                                BluetoothDevice bluetoothDevice) {
        if (securityType != null)
            this.mSecurityType = securityType;
        if (serverOrClient != null)
            this.mServerOrClient = serverOrClient;
        if (bluetoothDevice != null)
            this.mBluetoothDevice = bluetoothDevice;
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mHandler = handler;
        start();
    }

    /**
     * 多线程同步修改状态标识
     *
     * @param state
     */
    private synchronized void setState(int state) {
        mState = state;
        mHandler.obtainMessage(MESSAGE_TOAST_STATE_CHANGE, state, -1, null).sendToTarget();
    }

    /**
     * 多线程同步读取状态标识
     */
    public synchronized int getState() {
        return mState;
    }

    /**
     * 启动服务
     */
    public void start() {
        start(null, null, null);
    }

    /**
     * 启动服务
     *
     * @param securityType    连接的安全模式
     * @param serverOrClient  客户端或服务端
     * @param bluetoothDevice 服务器端设备
     */
    public void start(SecurityType securityType, ServerOrClient serverOrClient, BluetoothDevice bluetoothDevice) {
        if (securityType != null)
            this.mSecurityType = securityType;
        if (this.mSecurityType == null) {
            if (DEBUG)
                Log.e(TAG, "mSecurityType cannot be null");
            mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "mSecurityType cannot be null").sendToTarget();
            return;
        }
        if (serverOrClient != null)
            this.mServerOrClient = serverOrClient;
        if (this.mServerOrClient == null) {
            if (DEBUG)
                Log.e(TAG, "mServerOrClient cannot be null");
            mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "mServerOrClient cannot be null").sendToTarget();
            return;
        }
        if (bluetoothDevice != null)
            this.mBluetoothDevice = bluetoothDevice;
        if (this.mBluetoothDevice == null) {
            if (DEBUG)
                Log.e(TAG, "mBluetoothDevice cannot be null");
            mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "mBluetoothDevice cannot be null").sendToTarget();
            return;
        }
        if (mState == STATE_NONE) {
            stop();
            if (this.mServerOrClient == ServerOrClient.SERVER) {
                if (mServerConnectThread == null) {
                    mServerConnectThread = new ServerConnectThread(this.mSecurityType);
                    mServerConnectThread.start();
                }
            } else if (this.mServerOrClient == ServerOrClient.CLIENT) {
                if (mClientConnectThread == null) {
                    mClientConnectThread = new ClientConnectThread(this.mSecurityType);
                    mClientConnectThread.start();
                }
            }
            setState(STATE_LISTEN);
        }
    }

    /**
     * 停止服务
     */
    public synchronized void stop() {
        try {
            if (mReadWriteThread != null) {
                mReadWriteThread.cancel();
                mReadWriteThread = null;
            }
            if (mServerConnectThread != null) {
                mServerConnectThread.cancel();
                mServerConnectThread = null;
            }
            if (mClientConnectThread != null) {
                mClientConnectThread.cancel();
                mClientConnectThread = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (DEBUG)
                Log.e(TAG, "BluetoothChatService -> stop() -> :failed " + e.getMessage());
            mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "BluetoothChatService -> stop() -> :failed").sendToTarget();
            mReadWriteThread = null;
            mServerConnectThread = null;
            mClientConnectThread = null;
        } finally {
            setState(STATE_NONE);
            System.gc();
        }
    }

    /**
     * 发送消息
     *
     * @param out 数据参数
     */
    public void write(String out) {
        if (TextUtils.isEmpty(out)) {
            if (DEBUG)
                Log.e(TAG, "please write something now");
            mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "BluetoothChatService -> write() -> :failed").sendToTarget();
            return;
        }
        ReadWriteThread r;
        synchronized (this) {
            if (mState != STATE_CONNECTED)
                return;
            r = mReadWriteThread;
        }
        r.write(out);
    }

    /**
     * 服务器端连接线程
     */
    @SuppressLint("NewApi")
    private class ServerConnectThread extends Thread {
        private BluetoothServerSocket mmServerSocket;
        private BluetoothSocket mmSocket = null;

        public ServerConnectThread(SecurityType securityType) {
            setName("ServerConnectionThread:" + securityType.getValue());
            BluetoothServerSocket tmp = null;
            try {
                if (securityType == SecurityType.SECURE) {
                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(SecurityType.SECURE.getValue(), MY_UUID_SECURE);
                } else if (securityType == SecurityType.INSECURE) {
                    tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SecurityType.INSECURE.getValue(),
                            MY_UUID_INSECURE);
                }
                if (tmp != null)
                    mmServerSocket = tmp;
            } catch (IOException e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ServerConnectThread -> ServerConnectThread() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ServerConnectThread -> ServerConnectThread() -> :failed").sendToTarget();
                mmServerSocket = null;
                BluetoothChatService.this.stop();
            }
        }

        public void run() {
            try {
                // 正在连接
                setState(STATE_CONNECTING);
                //accept() 阻塞式的方法,群聊时,需要循环accept接收客户端
                mmSocket = mmServerSocket.accept();
                connected(mmSocket);
            } catch (Exception e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ServerConnectThread -> run() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ServerConnectThread -> run() -> :failed").sendToTarget();
                BluetoothChatService.this.stop();
            }
        }

        public void cancel() {
            try {
                if (mmSocket != null) {
                    mmSocket.close();
                    mmSocket = null;
                }
                if (mmServerSocket != null) {
                    mmServerSocket.close();
                    mmServerSocket = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ServerConnectThread -> cancel() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ServerConnectThread -> cancel() -> :failed").sendToTarget();
                mmSocket = null;
                mmServerSocket = null;
                BluetoothChatService.this.stop();
            }
        }
    }

    // 客户端连接线程
    private class ClientConnectThread extends Thread {
        private BluetoothSocket mmSocket;

        public ClientConnectThread(SecurityType securityType) {
            setName("ClientConnectThread:" + securityType.getValue());
            BluetoothSocket tmp = null;
            try {
                if (securityType == SecurityType.SECURE) {
                    tmp = mBluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
                    //Method m = mBluetoothDevice.getClass().getMethod("createRfcommSocket", int.class);
                    //tmp = (BluetoothSocket) m.invoke(mBluetoothDevice, 1);
                } else if (securityType == SecurityType.INSECURE) {
                    tmp = mBluetoothDevice.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
                    //Method m = mBluetoothDevice.getClass().getMethod("createRfcommSocket", int.class);
                    //tmp = (BluetoothSocket) m.invoke(mBluetoothDevice, 1);
                }
                if (tmp != null)
                    mmSocket = tmp;
            } catch (Exception e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ClientConnectThread -> ClientConnectThread() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ClientConnectThread -> ClientConnectThread() -> :failed").sendToTarget();
                mmSocket = null;
                BluetoothChatService.this.stop();
            }
        }

        public void run() {
            try {
                setState(STATE_CONNECTING);
                mmSocket.connect();
                connected(mmSocket);
            } catch (IOException e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ClientConnectThread -> run() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ClientConnectThread -> run() -> :failed").sendToTarget();
                BluetoothChatService.this.stop();
            }
        }

        public void cancel() {
            try {
                if (mmSocket != null && mmSocket.isConnected()) {
                    mmSocket.close();
                }
                mmSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ClientConnectThread -> cancel() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ClientConnectThread -> cancel() -> :failed").sendToTarget();
                mmSocket = null;
                BluetoothChatService.this.stop();
            }
        }
    }

    /**
     * 以获取socket,建立数据流线程
     *
     * @param socket
     */
    private synchronized void connected(BluetoothSocket socket) {
        if (mReadWriteThread != null) {
            mReadWriteThread.cancel();
            mReadWriteThread = null;
        }
        mReadWriteThread = new ReadWriteThread(socket);
        mReadWriteThread.start();
    }

    /**
     * 连接成功线程,可进行读写操作
     */
    private class ReadWriteThread extends Thread {
        private BluetoothSocket mmSocket;
        private DataInputStream mmInStream;
        private DataOutputStream mmOutStream;
        private boolean isRunning = true;

        public ReadWriteThread(BluetoothSocket socket) {
            mmSocket = socket;
            try {
                mmInStream = new DataInputStream(mmSocket.getInputStream());
                mmOutStream = new DataOutputStream(mmSocket.getOutputStream());
                // 连接建立成功
                setState(STATE_CONNECTED);
            } catch (IOException e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ReadWriteThread -> ReadWriteThread() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> ReadWriteThread() -> :failed").sendToTarget();
                mmOutStream = null;
                mmInStream = null;
                BluetoothChatService.this.stop();
            }
        }

        public void run() {
            byte[] buffer = new byte[1024];
            int len;
            while (isRunning) {
                try {
                    //readUTF(),read(buffer) 都是阻塞式的方法
                    //如果这儿用readUTF,那么写的地方得用writeUTF。对应
                    String receive_str = mmInStream.readUTF();
                    if (!TextUtils.isEmpty(receive_str))
                        mHandler.obtainMessage(MESSAGE_RECEIVE, -1, -1, receive_str).sendToTarget();
//                    len = mmInStream.read(buffer);
//                    if(len > 0){
//                        String receive_str = new String(buffer,0,len);
//                        if (!TextUtils.isEmpty(receive_str))
//                            mHandler.obtainMessage(MESSAGE_RECEIVE, -1, -1, receive_str).sendToTarget();
//                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    if (DEBUG)
                        Log.e(TAG, "ReadWriteThread -> run() -> :failed " + e.getMessage());
                    mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> run() -> :failed").sendToTarget();
                    BluetoothChatService.this.stop();
                }
            }
        }

        public void write(String str) {
            try {
                mmOutStream.writeUTF(str);
                mmOutStream.flush();
                mHandler.obtainMessage(MESSAGE_TOAST_SEND, -1, -1, str).sendToTarget();
            } catch (IOException e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ReadWriteThread -> write() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> write() -> :failed").sendToTarget();
                BluetoothChatService.this.stop();
            }
        }

        public void cancel() {
            try {
                isRunning = false;
                if (mmInStream != null) {
                    mmInStream.close();
                    mmInStream = null;
                }
                if (mmOutStream != null) {
                    mmOutStream.close();
                    mmOutStream = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
                if (DEBUG)
                    Log.e(TAG, "ReadWriteThread -> cancel() -> :failed " + e.getMessage());
                mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> cancel() -> :failed").sendToTarget();
                mmInStream = null;
                mmOutStream = null;
                BluetoothChatService.this.stop();
            }
        }
    }

sorry,不要蛋疼,不要骂娘,代码确实有这么多。以上代码使我在前辈的基础上优化改进了的。可以作为一个公共的蓝牙通讯工具类使用。

下面贴出我的项目功能,是一个聊天程序,只能单聊。我不明太网上有很多demo声称能群聊怎么实现的,据目前分析,服务器端和客户端的管道流是一一对应的,不是广播模式。如果能群聊,会在服务器端创建一个输入输出流管理的集合吧,服务器端没收到一条消息,在=再循环输出流集合,往各个客户端都发送消息。这样一来,我上面的这段代码不够用了。懒得改,故没有做群聊。

先贴图:







程序中,socket的连接方式有安全连接和不安全连接,我一直没有搞懂区别

在两个手机都连接了wifi的情况加,再使用我这种蓝牙通讯方式通讯时,io流连接上后会自动断开,很奇怪。查资料说蓝牙通讯的波段频率与路由器的冲突了。没辙,故在启动程序的时候关闭了wifi,下下策,望大家提供思路。

最后附上源码。有遗漏和错误,望大家指点。


源码下载

4楼u0122304454天前 10:02
文章写的还可以,想说下面试官这种做法也没有什么错,也许项目是针对蓝牙的深入项目,可能很着急蓝牙模块,你无经验还要研究个两三天,而且蓝牙开发有那么多坑,时间和稳定上你都不合适,不录用你正常。
3楼HU_CHENGDU5天前 12:53
需要用户输入配对码吗?
2楼HU_CHENGDU5天前 11:58
“但是还有很多坑你不知道,还有很多奇葩代码”没有细说呢?
1楼fesdgasdgasdg5天前 11:56
不需要输入配对码!n奇葩代码在:nmBluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID);nmmBluetoothDevice.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);n两种连接方式的区别。n一坑为:n两个手机都连接了wifi时,蓝牙连接脸上后又会很快断开。
------分隔线----------------------------