从源码角度分析android蓝牙设备如何互联?

转载需说明出处:http://blog.csdn.net/andywuchuanlong/article/details/51509229

最近公司需要用到专门的蓝牙设备去连接机器人,由于之前也没有接触过蓝牙,所以就在网上搜寻大把的资料,到最后还是没有什么所获,基本上所有的代码都是用不了的,蓝牙始终是连接不成功。但幸好的是android系统中的setting就附带了蓝牙连接的功能,所以研究下setting还是阔以的。

从android3.0开始,蓝牙的api就提供了对蓝牙profile的支持,比如a2dp profile用来在蓝牙设备之间实现高质量的声音传输,inputDevice profile实现蓝牙输入设备功能,pan profile实现蓝牙个人局域网功能,health profile用来与支持蓝牙健康设备进行通信等。这些profile在原生的setting源代码中已经定义好了。

// android.bluetooth.BluetoothProfile.java
public interface BluetoothProfile {
    .....
    /** * Headset and Handsfree profile */
    public static final int HEADSET = 1;

    /** * A2DP profile. */
    public static final int A2DP = 2;

    /** * Health Profile */
    public static final int HEALTH = 3;

    /** * Input Device Profile * @hide */
    public static final int INPUT_DEVICE = 4;

    /** * PAN Profile * @hide */
    public static final int PAN = 5;

    /** * PBAP * @hide */
    public static final int PBAP = 6;

    /** * GATT */
    static public final int GATT = 7;

    /** * GATT_SERVER */
    static public final int GATT_SERVER = 8;

    /** * MAP Profile * @hide */
    .....
}

网上的大多数资料都是说利用BluetoothServerSocket 来连接的,但是我发现我们的设备压根就不起作用,总是出现socket连接超时。所以只能从setting源码去入手,我们的这个蓝牙设备是充当着一个输入设备角色,所以就必须要去看inputDevice profile是如何实现连接的功能了。下面就具体介绍一个输入类型的蓝牙设备如何连接指定的设备。BluetoothInputDevice 的核心代码如下,

// android.bluetooth.BluetoothInputDevice.java 
public final class BluetoothInputDevice implements BluetoothProfile {

    // 用来接收蓝牙连状态的广播action,连接中、连接成功、断开连接等状态
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_CONNECTION_STATE_CHANGED =
        "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";

    // 获取蓝牙输入设备服务,用来操作连接、断开、发送数据等操作
    private final ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            if (DBG) Log.d(TAG, "Proxy object connected");
            mService = IBluetoothInputDevice.Stub.asInterface(service);

            if (mServiceListener != null) {
                mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this);
            }
        }
        public void onServiceDisconnected(ComponentName className) {
            if (DBG) Log.d(TAG, "Proxy object disconnected");
            mService = null;
            if (mServiceListener != null) {
                mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
            }
        }
    };

    // 连接设备
    public boolean connect(BluetoothDevice device) {
        if (DBG) log("connect(" + device + ")");
        if (mService != null && isEnabled() && isValidDevice(device)) {
            // 如果service不为空,并且蓝牙是打开的,并且要连接的设备是有效的设备
            try {
                return mService.connect(device);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return false;
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return false;
    }

    //断开设备 
    public boolean disconnect(BluetoothDevice device) {
        if (DBG) log("disconnect(" + device + ")");
        if (mService != null && isEnabled() && isValidDevice(device)) {
            try {
                return mService.disconnect(device);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return false;
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return false;
    }

    // 获取已经连接成功的设备列表
    public List<BluetoothDevice> getConnectedDevices() {
        if (VDBG) log("getConnectedDevices()");
        if (mService != null && isEnabled()) {
            try {
                return mService.getConnectedDevices();
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return new ArrayList<BluetoothDevice>();
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return new ArrayList<BluetoothDevice>();
    }

    // 获取某个蓝牙设备的连接状态
     public int getConnectionState(BluetoothDevice device) {
        if (VDBG) log("getState(" + device + ")");
        if (mService != null && isEnabled() && isValidDevice(device)) {
            try {
                return mService.getConnectionState(device);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return BluetoothProfile.STATE_DISCONNECTED;
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return BluetoothProfile.STATE_DISCONNECTED;
    }

    // 发送指定的数据到指定的蓝牙设备
    public boolean sendData(BluetoothDevice device, String report) {
        if (DBG) log("sendData(" + device + "), report=" + report);
        if (mService != null && isEnabled() && isValidDevice(device)) {
            try {
                return mService.sendData(device, report);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return false;
            }
        }
        if (mService == null) Log.w(TAG, "Proxy not attached to service");
        return false;
    }
}

蓝牙连接前如何判断某个设备是否是有效的呢?那肯定是判断mac地址啦。如下:

    private boolean isValidDevice(BluetoothDevice device) {
       if (device == null) return false;
       // 这里便是检查mac地址的有效性 
       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
       return false;
    }
    // 校验蓝牙设备的地址, 例如 00:43:A8:23:10:F0
    public static boolean checkBluetoothAddress(String address) {
        if (address == null || address.length() != ADDRESS_LENGTH) {
            return false;
        }
        for (int i = 0; i < ADDRESS_LENGTH; i++) {
            char c = address.charAt(i);
            switch (i % 3) {
            case 0:
            case 1:
                if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) {
                    // hex character, OK
                    break;
                }
                return false;
            case 2:
                if (c == ':') {
                    break;  // OK
                }
                return false;
            }
        }
        return true;
    }

蓝牙的连接、断开、发送数据等操作均要依赖这个mService来进行操作,那么mService是什么东东?
这个mService是在ServiceConnection 的onServiceConnected回调方法中获取的:

mService = IBluetoothInputDevice.Stub.asInterface(service);

看到这个转换方式,熟悉aidl的同学们肯定知道这个是啥意思啦。所以只要mConnection连接成功,就会返回一个IBluetoothInputDevice代理对象。在BluetoothInputDevice构造方法中:

    BluetoothInputDevice(Context context, ServiceListener l) {
        mContext = context;
        mServiceListener = l;
        mAdapter = BluetoothAdapter.getDefaultAdapter();

        IBluetoothManager mgr = mAdapter.getBluetoothManager();
        if (mgr != null) {
            try {
                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
            } catch (RemoteException e) {
                Log.e(TAG,"",e);
            }
        }
        // dobind,顾名思义肯定是bind某个东西
        doBind();
    }
    boolean doBind() {
        Intent intent = new Intent(IBluetoothInputDevice.class.getName());
        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
        intent.setComponent(comp);
        if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
            Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
            return false;
        }
        return true;
    }

doBind方法中绑定了service,那么这个service绑定之后,就会回调mConnection的onServiceConnected,至此我们已经知道这个操纵蓝牙连接和断开的mService是如何获得的了。
不过还别慌,还有个东西没有介绍,那就是这个onServiceConnected方法中的mServiceListener 是啥东东?这个mServiceListener是通过构造方法中传入的,它通过自身的onServiceConnected方法将
BluetoothInputDevice.this传递给了他的构建者。

if (mServiceListener != null) {
     mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this);
}

下面就看下是谁实例化了这个BluetoothInputDevice。莫非是 BluetoothAdapter ? BluetoothAdapter 是所有蓝牙对象交互的入口,其本身提供了蓝牙相关一系列的api,如提供profile的调用、蓝牙设备的扫描等。其中有个重要的方法:

    public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,int profile) {
        if (context == null || listener == null) return false;
        if (profile == BluetoothProfile.HEADSET) {
            BluetoothHeadset headset = new BluetoothHeadset(context, listener);
            return true;
        } else if (profile == BluetoothProfile.A2DP) {
            BluetoothA2dp a2dp = new BluetoothA2dp(context, listener);
            return true;
        } else if (profile == BluetoothProfile.INPUT_DEVICE) {
            BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener);
            return true;
        } else if (profile == BluetoothProfile.PAN) {
            BluetoothPan pan = new BluetoothPan(context, listener);
            return true;
        } else if (profile == BluetoothProfile.HEALTH) {
            BluetoothHealth health = new BluetoothHealth(context, listener);
            return true;
        } else if (profile == BluetoothProfile.MAP) {
            BluetoothMap map = new BluetoothMap(context, listener);
            return true;
        } else {
            return false;
        }
    }

原来BluetoothInputDevice是在这里实例化的,所以我们就可以通过这个api来实例化一个BluetoothInputDevice,然后实例化一个ServiceListener 获取BluetoothInputDevice的代理对象,下面是实现代码:

    private void initProfile(){
        try {
            mLocalAdapter.getProfileProxy(mContext, new android.bluetooth.BluetoothProfile.ServiceListener() {
                @Override
                public void onServiceDisconnected(int profile) {
                    Log.e(TAG, "onServiceDisconnected : "+profile);
                    removeBound();
                }
                @Override
                public void onServiceConnected(int profile, BluetoothProfile proxy) {
                    Log.e(TAG, "onServiceConnected : "+profile);
                    hidInputService = proxy;
                }
            },PROFILE_HID);
        } catch (Exception e) {
            Log.e(TAG, "createDevice connect 1: "+e.getLocalizedMessage());
        }
    }

获得到BluetoothInputDevice代理对象后就好办啦,可以开始连接等操作了。但是首先还是得进行蓝牙设备的扫描、发现设备、再配对。

下面是相关的代码,同学可以自行研究:

    private void createBond() {
        try {
            if (mBluetoothDevice != null && deviceName.equals(mBluetoothDevice.getName())) {
                boolean createBond = mBluetoothDevice.createBond();
                Log.e(TAG, "createBond result " + createBond);
            }
        } catch (Exception e) {
        }
    }

    private void connect() {
        setPriority();
        try {
            Method connect = hidInputService.getClass().getDeclaredMethod("connect",
                    BluetoothDevice.class);
            connect.setAccessible(true);
            Boolean invoke = (Boolean) connect.invoke(hidInputService, mBluetoothDevice);
            Log.e(TAG, "connect : " + invoke);
        } catch (Exception e) {
            Log.e(TAG, "connect : " + e.getLocalizedMessage());
        }
    }

    public void disconnectDevice(final String deviceName){
        List<BluetoothDevice> connectedDevices = hidInputService.getConnectedDevices();
        for(BluetoothDevice bluetoothDevice : connectedDevices){
        if(bluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED){
              mBluetoothDevice = bluetoothDevice;
              Log.e(TAG, "disconnecting");
              removeBound();
               disConnect();
               closeProfile();
             }
          }
    }

    private void disConnect() {
        try {
            Method connect = hidInputService.getClass().getDeclaredMethod("disconnect",
                    BluetoothDevice.class);
            connect.setAccessible(true);
            Boolean invoke = (Boolean) connect.invoke(hidInputService, mBluetoothDevice);
            Log.e(TAG, " disconnect : " + invoke);
        } catch (Exception e) {
            Log.e(TAG, " disconnect : " + e.getLocalizedMessage());
        }
    }

同学如果研究到这里,说明你对这个蓝牙连接有一定的了解啦,下片文章继续带你研究蓝牙相关的源代码以及其中用到的设计模式!

蓝牙相关连接代码下载地址:http://download.csdn.net/detail/andywuchuanlong/9639446

转载需说明出处:http://blog.csdn.net/andywuchuanlong/article/details/51509229

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/andywuchuanlong/article/details/51509229
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞