从源码角度分析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

时间: 2024-10-05 17:17:43

从源码角度分析android蓝牙设备如何互联?的相关文章

从源码角度分析Android中的Binder机制的前因后果

前面我也讲述过一篇文章<带你从零学习linux下的socket编程>,主要是从进程通信的角度开篇然后延伸到linux中的socket的开发.本篇文章依然是从进程通信的角度去分析下Android中的进程通信机制. 为什么在Android中使用binder通信机制? 众所周知linux中的进程通信有很多种方式,比如说管道.消息队列.socket机制等.socket我们再熟悉不过了,然而其作为一款通用的接口,通信开销大,数据传输效率低,主要用在跨网络间的进程间通信以及在本地的低速通信.消息队列和管道

从源码角度分析Android View的绘制机制(一)

在Android的学习道路上,每一个人员都免不了去翻阅Android的源码,因为只有从源码的角度分析问题,我们才能真正的玩转Android开发.最近由于工作比较闲,总想着想写点什么东西,正好自己也可以整理一下.考虑到view的显示机制是自定义view的基础,也是面试中经常被问到的问题,所以记录此文,和大家共享,因水平有限,望大家踊跃拍砖,不胜感激. 有过自定义view的同行们都应该知道,view的显示依托于activity的setContentView方法依附到PhoneWindow窗体上的,在

【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. Looper在Android的消息机制中就是用来进行消息循环的.它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻处理该消息,否则就一直等待. Looper中有一个属性: static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 这也就解释了,前面我们所说的我们可以通过ThreadLocal实现Looper

【原创】源码角度分析Android的消息机制系列(三)——ThreadLocal的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Android源码(API24)中对ThreadLocal的定义: public class ThreadLocal<T> 即ThreadLoca是一个泛型类,再看对该类的注释: /** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread th

【原创】源码角度分析Android的消息机制系列(六)——Handler的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Handler的定义: /** * A Handler allows you to send and process {@link Message} and Runnable * objects associated with a thread's {@link MessageQueue}. Each Handler * instance is associated with a single thread and that thre

【原创】源码角度分析Android的消息机制系列(四)——MessageQueue的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. MessageQueue,主要包含2个操作:插入和读取.读取操作会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除.虽然MessageQueue叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势. 先看M

【原创】源码角度分析Android的消息机制系列(二)——ThreadLocal的工作过程

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一篇文章中,我们已经提到了ThreadLocal,它并非线程,而是在线程中存储数据用的.数据存储以后,只能在指定的线程中获取到数据,对于其他线程来说是无法获取到数据的,也就是说ThreadLocal可以在多个线程中互不干扰地存储和修改数据.基于ThreadLocal的这一特点,那么当我们在开发中,需要将某些数据以线程作为作用域,并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal了. Android的消息机制中也

【原创】源码角度分析Android的消息机制系列(一)——Android消息机制概述

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.为什么需要Android的消息机制 因为Android系统不允许在子线程中去访问UI,即Android系统不允许在子线程中更新UI. 为什么不允许在子线程中更新UI呢?因为Android的控件不是线程安全的.既然是非线程安全的,那么若在多个子线程中并发访问,UI控制可能会处于一种不可预期的状态.有的读者可能会说,为什么不对UI控件加锁呢?加锁会降低UI访问的效率,因为加锁之后,若想要运行这段synchronized的代码,线程要先拿到

从源码角度分析linearLayout测量过程以及weight机制

???上文从源码角度分析了view和viewGroup的measure机制,如果还没有阅读的同志们,可以前往从源码角度分析Android View的绘制机制(一)阅读.下面我再结合linearLayout的measure过程解释以下两个问题的缘由. 问题一: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent