Android蓝牙开发

蓝牙这个功能,搞了我3-4天的时间,网上的资料到时蛮多,但就是没有很全的。今天终于是把它搞定了,记录下。

蓝牙功能需要两个服务:
1、服务端
2、客户端
这个两个服务,与通常的说的C/S的意义是一致的,而且也是用的socket通信。

下面的代码以最简开发蓝牙为实例,来说明蓝牙的数据收发,所以有些代码不是很严谨和合理的。


服务端
1、开启蓝牙
在使用蓝牙前,需要先打开蓝牙设备

private BluetoothAdapter mBluetoothAdapter;
private boolean isEnableBluetooth = false;
public void openBl(View view) {
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if(mBluetoothAdapter == null) {
        isEnableBluetooth = false;
        Toast.makeText(this, "前蓝牙设备驱动有问题", Toast.LENGTH_LONG).show();
        log("当前蓝牙设备驱动有问题");
        return;
    }
    if(!mBluetoothAdapter.isEnabled()) {
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(intent, EnableBluetoothRequestCode);
        return;
    }
    isEnableBluetooth = true;
}
mBluetoothAdapter.isEnabled()
获取蓝牙的开启状态,如果蓝牙已经开启,返回true,否则返回false
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, EnableBluetoothRequestCode);
异步提示用户打开蓝牙,即,弹出允许打开蓝牙框,
因为是异步等待用户打开蓝牙,所以这里重写 onActivityResult 去获取用户操作的结果,
EnableBluetoothRequestCode 这个参数为用户自定义,但必须要大于0,主要用于在异步接收操作结果时的判断

重写 onActivityResult ,目的是接收用户打开蓝牙的操作结果

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == EnableBluetoothRequestCode) {
        if(Activity.RESULT_OK == resultCode) {
            Toast.makeText(this, "蓝牙成功打开", Toast.LENGTH_LONG).show();
            isEnableBluetooth = true;
        } else {
            Toast.makeText(this, "蓝牙打开失败", Toast.LENGTH_LONG).show();
            isEnableBluetooth = false;
        }
    }
}

2、开启Server

因为 server 端是阻塞式的等待用户连接,即,如果用户没有连接,则我什么都不做,一直等待用户来连接。
所以,这个服务就不能与主线程的 Activity 在同一个进程里面了,所以要新起一个 server thread:

private class ServerThread extends Thread {
    private BluetoothServerSocket serverSocket;

    @Override
    public void run() {
        try {
            //serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
            serverSocket = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
            if(serverSocket == null) {
                log("server socket创建失败");
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log("server socket创建失败, 异常发生");
        }

        log("等待客户端连接...");
        while (true) {
            try {
                log("开始 accept");
                BluetoothSocket socket = serverSocket.accept();
                log("接受到了客户端");

                BluetoothDevice device = socket.getRemoteDevice();
                log("接受客户连接, name:"+device.getName());

                if(socket.isConnected()) {
                    log("已建立连接");
                    InputStream tmpIn = socket.getInputStream();
                    byte[] buffer = new byte[1024];
                    int bytes = tmpIn.read(buffer);
                    log("收到数据:");
                    log(new String(buffer));

                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
                log("accept 发生异常");
                break;
            }

        }
    }
}
private BluetoothServerSocket serverSocket;
定义服务端 socket
serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
serverSocket = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
获取服务端 socket

这里有几个东西要讲下,当时是费了很多时间去理解和查资料的
(1) listenUsingRfcommWithServiceRecord 与 listenUsingInsecureRfcommWithServiceRecord 经测试,这两者的主要区别是:

  • listenUsingRfcommWithServiceRecord 在客户端与服务器端连接时需要双方验证匹配, 即,所谓的安全连接

  • listenUsingInsecureRfcommWithServiceRecord 在客户端与服务器端连接时不需要双方验证匹配, 即,所谓的不安全连接。客户端 connect 之后,两者就可以互发数据了,这个应该应用在无线设备上比较多,比如蓝牙耳机等

  • 在测试过程中发现,服务器端和客户端的连接方式要一致,即,要么都是“安全连接”,要么都是“非安全连接”, 如果有一方为非安全连接的话,程序会出异常,具体的异常原因,目前还没有去研究

 (2)listenUsingRfcommWithServiceRecord 参数的意义

 public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid)
定义服务端 socket
  • @param name service name for SDP record  这个参数最好与当前设备的蓝牙名称一致(还没研究透这个字段的具体意义)

  • @param uuid uuid for SDP record  一开始在网上搜索到蓝牙相关的代码,里面有这个字段,但是没说这个字段是怎么来的,还一直以为是读取蓝牙设备的值,一致出错;后来单独搜索了UUID类型的含义,原来是标识设备/服务的唯一标识,具体的可以参考这几篇文章:
    http://blog.csdn.net/buaaroid/article/details/39372187
    http://blog.csdn.net/wletv/article/details/8957612

 (3) 接下来就是死循环的接受客户端的连接了。
在这里只是为了说明原理,这个 while 循环只会运行一次就跳出

BluetoothSocket socket = serverSocket.accept();
服务器端在这里阻塞地等待客户端连连接,
socket 为客户端连接后,服务器端与客户端绑定的 socket

如果程序能继续往下运行,则说明已经有客户端来连接了

if(socket.isConnected()) {
    log("已建立连接");
    InputStream tmpIn = socket.getInputStream(); // 获取输入流 准备读取数据
    byte[] buffer = new byte[1024];
    int bytes = tmpIn.read(buffer);  // 读取数据
    log("收到数据:");
    log(new String(buffer));  // 打印数据

    break;
}

最后就是使用这个 server thread 进行开启 server 了

public void openService(View view) {
    if(!isEnableBluetooth) {
        log("蓝牙未开启");
        return;
    }

    new Thread(new ServerThread()).start();
}


客户端
1、开启蓝牙
(代码同上)

2、扫描服务端蓝牙设备

private BluetoothAdapter mBluetoothAdapter;
public void scanBl(View view) {
    if(!isEnableBluetooth) {
        Toast.makeText(this, "请先打开蓝牙", Toast.LENGTH_LONG).show();
        return;
    }

    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(mReceiver, filter);

    mBluetoothAdapter.startDiscovery();
    log("启动蓝牙扫描");

}

这里调用 mBluetoothAdapter.startDiscovery(); 进行搜索
如果发现了蓝牙设备,则进行广播,所以这里注册了一个 BroadcastReceiver:

private BroadcastReceiver mReceiver = new 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);
            String name = device.getName();
            log("广播收到消息 name:" + name);
            if(name != null && name.equals(blName)) { // 判断是否是目标设备 private final String blName = "Honor 6X";
                log("发现目标蓝牙服务,开始连接");
                mBluetoothAdapter.cancelDiscovery(); // 停止搜索  否则耗资源

                new Thread(new ClientThread(device)).start(); // 启动连接线程
            }
        }
    }
};

3、连接服务端
如果已经扫描到了目标设备,则新启动一个线程去连接

private class ClientThread extends Thread {
    private BluetoothDevice device;
    private BluetoothSocket socket;

    public ClientThread(BluetoothDevice device) {
        this.device = device;
    }

    @Override
    public void run() {
        try {
            //socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
            socket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
            log("连接服务器端");
            socket.connect();  // 去连接服务器端 阻塞
            log("建立连接");
            OutputStream outputStream = socket.getOutputStream();
            String sndMsg = "hello Bluetooth";
            byte[] buffer = sndMsg.getBytes();
            log("发送数据");
            outputStream.write(buffer); // 向服务端发送数据
            log("发送完数据");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建 socket

createRfcommSocketToServiceRecord
createInsecureRfcommSocketToServiceRecord

 这两个方法与服务器端的 listenUsingRfcommWithServiceRecord 和 listenUsingInsecureRfcommWithServiceRecord 类似。
 要注意的是这里的 MY_UUID 与服务器端的 MY_UUID 一定要一致

以上就是客户端和服务器端的核心代码了,完整代码如下:

服务器端:

package com.example.xxh.bluetoothserver;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.io.InputStream;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    private TextView log_view;

    private BluetoothAdapter mBluetoothAdapter;
    private boolean isEnableBluetooth = false;

    private int EnableBluetoothRequestCode = 9;

    private final String blName = "Honor 6X";
    private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";

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

        log_view = (TextView)findViewById(R.id.log_view);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == EnableBluetoothRequestCode) {
            if(Activity.RESULT_OK == resultCode) {
                Toast.makeText(this, "蓝牙成功打开", Toast.LENGTH_LONG).show();
                isEnableBluetooth = true;
            } else {
                Toast.makeText(this, "蓝牙打开失败", Toast.LENGTH_LONG).show();
                isEnableBluetooth = false;
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBluetoothAdapter.disable();
    }

    private void log(String msg) {
        Log.i("BLUETOOTH", msg);
        //log_view.append(msg + "\n");
    }

    public void openBl(View view) {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if(mBluetoothAdapter == null) {
            isEnableBluetooth = false;
            Toast.makeText(this, "前蓝牙设备驱动有问题", Toast.LENGTH_LONG).show();
            log("当前蓝牙设备驱动有问题");
            return;
        }
        if(!mBluetoothAdapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, EnableBluetoothRequestCode);
            return;
        }
        isEnableBluetooth = true;
    }

    public void openService(View view) {
        if(!isEnableBluetooth) {
            log("蓝牙未开启");
            return;
        }

        new Thread(new ServerThread()).start();
    }

    private class ServerThread extends Thread {
        private BluetoothServerSocket serverSocket;

        @Override
        public void run() {
            try {
                //serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
                serverSocket = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
                if(serverSocket == null) {
                    log("server socket创建失败");
                    return;
                }
            } catch (Exception e) {
                e.printStackTrace();
                log("server socket创建失败, 异常发生");
            }

            log("等待客户端连接...");
            while (true) {
                try {
                    log("开始 accept");
                    BluetoothSocket socket = serverSocket.accept();
                    log("接受到了客户端");

                    BluetoothDevice device = socket.getRemoteDevice();
                    log("接受客户连接, name:"+device.getName());

                    if(socket.isConnected()) {
                        log("已建立连接");
                        InputStream tmpIn = socket.getInputStream();
                        byte[] buffer = new byte[1024];
                        int bytes = tmpIn.read(buffer);
                        log("收到数据:");
                        log(new String(buffer));

                        break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    log("accept 发生异常");
                    break;
                }

            }
        }
    }
}

客户端

package com.example.xxh.bluetoothclient;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.io.OutputStream;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private boolean isEnableBluetooth = false;

    private int EnableBluetoothRequestCode = 9;

    private final String blName = "Honor 6X";
    private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";


    private TextView log_view;

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

        log_view = (TextView)findViewById(R.id.log_view);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == EnableBluetoothRequestCode) {
            if(Activity.RESULT_OK == resultCode) {
                Toast.makeText(this, "蓝牙成功打开", Toast.LENGTH_LONG).show();
                isEnableBluetooth = true;
            } else {
                Toast.makeText(this, "蓝牙打开失败", Toast.LENGTH_LONG).show();
                isEnableBluetooth = false;
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBluetoothAdapter.disable();
        unregisterReceiver(mReceiver);
    }

    private void log(String msg) {
        Log.i("BLUETOOTH", msg);
        //log_view.append(msg + "\n");
    }

    public void openBl(View view) {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if(mBluetoothAdapter == null) {
            Toast.makeText(this, "当前蓝牙设备驱动有问题", Toast.LENGTH_LONG).show();
            isEnableBluetooth = false;
            return;
        }
        if(mBluetoothAdapter.isEnabled()) {
            isEnableBluetooth = true;
            return;
        }
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(intent, EnableBluetoothRequestCode);
    }

    public void scanBl(View view) {
        if(!isEnableBluetooth) {
            Toast.makeText(this, "请先打开蓝牙", Toast.LENGTH_LONG).show();
            return;
        }

        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(mReceiver, filter);

        mBluetoothAdapter.startDiscovery();
        log("启动蓝牙扫描");

    }

    private BroadcastReceiver mReceiver = new 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);
                String name = device.getName();
                log("广播收到消息 name:" + name);
                if(name != null && name.equals(blName)) {
                    log("发现目标蓝牙服务,开始连接");
                    mBluetoothAdapter.cancelDiscovery(); // 停止搜索

                    new Thread(new ClientThread(device)).start();
                }
            }
        }
    };

    private class ClientThread extends Thread {
        private BluetoothDevice device;
        private BluetoothSocket socket;

        public ClientThread(BluetoothDevice device) {
            this.device = device;
        }

        @Override
        public void run() {
            try {
                //socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
                socket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
                log("连接服务器端");
                socket.connect();
                log("建立连接");
                OutputStream outputStream = socket.getOutputStream();
                String sndMsg = "hello Bluetooth";
                byte[] buffer = sndMsg.getBytes();
                log("发送数据");
                outputStream.write(buffer);
                log("发送完数据");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

【参考】
UUID:
http://blog.csdn.net/buaaroid/article/details/39372187
http://blog.csdn.net/wletv/article/details/8957612

 文章:
http://blog.csdn.net/zhangphil/article/details/50554415
http://www.cnblogs.com/CharlesGrant/p/4924169.html
http://www.jianshu.com/p/fc46c154eb77

发表评论