蓝牙音箱android

2020-03-08 09:20:11 Atlas12345 阅读数 501

经过一段时间的折腾,我的Android Studio终于可以正常工作了,期间遇到的坑记录在了文章《创建Android Studio 3.5第一个工程遇到的坑》。

我们在《Android蓝牙开发系列文章-策划篇》中对蓝牙专题的内容进行了大概的描述,现在开始a2dp的第一篇:a2dp设备的配对和连接。

目录

1.设备扫描

2.设备配对:

3.设备连接:

4.总结 


首先介绍一下我的小伙伴,一个不知道牌子的蓝牙音响、华为荣耀7手机还有一个花了我9000大洋的thinkPadT480。

蓝牙音箱连接过程概述:首先,手机端需要发起扫描,扫描到设备后需要将目标设备也就是我的蓝牙音箱甄别出来,然后对蓝牙音箱发起配对,在配对成功后发起对设备的连接。

即大体需要三个流畅:(1)扫描,(2)配对,(3)连接。

一般来说,配对和连接流程在用户场景下是连贯性的动作,也就是配对成功后会自动发送对音箱的连接(音箱也只有一个提示音:蓝牙配对成功)。

1.设备扫描

现在AndroidManifest.xml中申请如下两个权限:


    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

我创建了5个Button,用于手动的将相关的流程串接起来,其中PLAY_PCM和PLAY_MUSIC用户后面的文章使用。

<Button
        android:id="@+id/bt_scan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="scan device" />

    <Button
        android:id="@+id/bt_createbond"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="createbond" />

    <Button
        android:id="@+id/bt_connect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="connect" />

    <Button
        android:id="@+id/bt_playpcm"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="play_pcm" />

    <Button
        android:id="@+id/bt_playmusic"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="play_music" />

在应用启动后,首先执行的逻辑如下:

 初始化广播接收器,目的是将流程串接起来,下面会一一进行描述。

初始化蓝牙,拿到BluetoothAdater对象。

获取到BluetoothA2dp service的代理对象。

扫描设备

         //初始化View
        initView();

        //初始化广播接收器
        mBtReceiver = new BtReceiver();
        IntentFilter intent = new IntentFilter();
        intent.addAction(BluetoothDevice.ACTION_FOUND);
        intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        intent.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        registerReceiver(mBtReceiver, intent);
        //初始化蓝牙
        initBt();

        //初始化profileProxy
        initProfileProxy();
        //开始扫描
        scanDevice();

 首先我们看一下initBt():

尝试获取BluetoothAdpter对象,如果获取不到,说明不支持蓝牙。当前,更正式的方式是看设备是否支持蓝牙feature,通过调用PackageManager的接口来判断,但是效果是一样的。

    private void initBt() {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        Log.d(TAG, "mBluetothAdapter = " + mBluetoothAdapter);
        if (mBluetoothAdapter == null) {
            Log.d(TAG, "do't support bt,return");
        }
    }

 在看一下initProfileProxy():

目的是获取调用BluetoothA2dp的connect()方法实现对音箱的连接。我看到很多例子,甚至是原生设置中是在设备配对成功后再去获取,这样会有个问题:如果下面onServiceConnected()返回的慢,会影响后面的连接逻辑,即需要延迟等待一段时间才能发起对设备的连接。

其实该接口的调用可以在蓝牙打开后就去调用,可以省去上面说的等待时间。

private int initProfileProxy() {
        mBluetoothAdapter.getProfileProxy(this,mProfileListener, BluetoothProfile.A2DP);
        return 0;
}

private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            Log.d(TAG, "onServiceConnected, profile = " + profile);
            if(profile == BluetoothProfile.A2DP) {
                mBluetoothA2dpProfile = (BluetoothA2dp)proxy;
            }
        }

        @Override
        public  void  onServiceDisconnected(int profile) {
            Log.d(TAG, "onServiceDisconnected, profile = " + profile);
        }
    };

然后,可以发起蓝牙扫描:

扫描的前提是手机的蓝牙是打开的,所以,我们这里先调用了BluetoothAdapter::isEnable()方法判断蓝牙是否已经打开了,如果打开了就调用BluetoothAdapter::startDiscovery()方法发起设备扫描。

private void scanDevice() {
        if(opentBt()) {
            if (mBluetoothAdapter.isDiscovering()) {
                Log.d(TAG, "scanDevice is already run");
                return;
            }
            boolean ret = mBluetoothAdapter.startDiscovery();
            Log.d(TAG, "scanDevice ret = " + ret);
        } else {
            Log.d(TAG, "bt is not open,wait");
        }

    }
private boolean opentBt() {
        if(mBluetoothAdapter.isEnabled()) {
            Log.d(TAG, "bt is aleady on,return");
            return true;
        } else {
            mBluetoothAdapter.enable();
            mHandler.sendEmptyMessageAtTime(MSG_SCAN, DELAYT_TIMES);
            return false;
        }
    }

 

如果蓝牙没有打开,我们就先去打开蓝牙,然后延迟发送一个消息出去,该消息到了之后再次触发扫描的逻辑。

mHandler = new Handler(){
            @Override
            public void dispatchMessage(Message msg) {
                    Log.d(TAG, "dispatchMessage, msg.what = " + msg.what);
                    switch (msg.what) {
                        case MSG_SCAN:
                            scanDevice();
                            break;
                        case MSG_PAIR:
                            pairDevice();
                            break;
                        case MSG_CONNECT:
                            connectDevice();
                            break;
                        default:
                            break;
                    }
            }
        };

这里有2个问题需要回答一下:

第一个问题:为什么调用BluetoothAdapter::startDiscovery()发起扫描,而不是调用其他接口?

因为我们音箱设备是经典蓝牙设备,也就是BR/EDR类型设备,还有一种蓝牙设备类型叫低功耗蓝牙设备,即BLE设备。

startDiscovery()接口能扫描到这两种类型的设备,而其他接口只能扫描BLE类型设备。

关于这些设备接口的区别以及如何快速搜索到目标设备,我会单独写一个文章进行总结。

第二个问题:为什么要延迟一段时间来再次调用扫描,而不是调用了打开蓝牙接着就去调用?

原因是因为,蓝牙的打开需要一段时间(当前正常情况下也是1s以内),再者蓝牙协议栈一个时间内只能处理一个指令,如果连续调用两个接口,会导致蓝牙底层出现问题。

那对于我们应用层来说,怎么知道扫描到的设备呢? 

蓝牙协议栈通过回调的方式上报扫描到的蓝牙设备到framework层,framework层会发送BluetoothDeive.ACTION_FOUND广播出来。应用层注册接收该广播,接收到后从intent中获取到设备的信息。

            //onReceive()方法
            final String action = intent.getAction();
            Log.d(TAG, "onReceive intent = " + action);
            if(action.equals(BluetoothDevice.ACTION_FOUND)) {
                BluetoothDevice btdevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                final String address = btdevice.getAddress();
                final String deviceName = btdevice.getName();
                Log.d(TAG, "onReceive found device, deivce address = " + address + ",deviceName = " + deviceName);
                if(isTargetDevice(btdevice)) {
                    stopScan();
                    mHandler.sendEmptyMessageDelayed(MSG_PAIR, DELAYT_TIMES);
                }
            }

 接收到广播后,我们需要对搜索到的设备进行判断,看是否是我们的目标设备。判断目标设备的方法就是就是进行一系列的判读,将干扰设备剔除出来。在这里,我进用设备的名词进行判断。

    //可以根据多个限制条件来设定目标设备,例如,信号强度,设备类型,设备名称等。
    //此处我们只用了设备名称来判断
    private boolean isTargetDevice(BluetoothDevice device) {
        if(device.getName() != null && device.getName().equals("S7")) {
            Log.d(TAG, "deivce :" + device.getName() + "is target device");
            mTargetDevice = device;
            return true;
        }
        Log.d(TAG, "deivce :" + device.getName() + "is not target device");
        return false;
    }

2.设备配对:

蓝牙设备的配对都是调用:BluetoothDevice::createBond()方法,

    private void pairDevice() {
        Log.d(TAG,"start pair device = " + mTargetDevice);
        if(mTargetDevice.getBondState() != BluetoothDevice.BOND_NONE){
            Log.d(TAG, "targetdevice is already bonded,return");
            return;
        }
        mTargetDevice.createBond();
    }

应用层注册接收BluetoothDevice.ACTION_BOND_STATE_CHANGED广播,接收到之后进行配对状态变化的判断。

我们从intent中获取到BluetoothDevice对象,也就是是哪个设备的配对状态发生了改变。

preBondState是前一个配对状态,newBondState是新状态,一个成功的配对流程是:

BluetoothDevice.BOND_NONE(10)-->BluetoothDevice.BOND_BONDING(11)-->BluetoothDevice.BOND_BONDED(12),如果是其他状态变化,则说明配对失败了~

在收到11--->12的配对状态变化时,即可认为设备配对成功了。我们需要再判断一下这个配对成功的设备是否是目标设备,只有是目标设备(也就是我们发起配对的设备)才能进行下一步的流程:连接。

if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                BluetoothDevice btdevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int preBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR);
                int newBondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
                Log.d(TAG, "btdeivice = " + btdevice.getName() + "bond state change, preBondState = " + preBondState
                        + ", newBondState = " + newBondState);
                if(preBondState == BluetoothDevice.BOND_BONDING && newBondState == BluetoothDevice.BOND_BONDED) {
                    //判断一下是否是目标设备
                    if(isTargetDevice(btdevice)) {
                        connectDevice();
                    }

                }

3.设备连接:

设备的连接是通过不同的profile,A2DP设备需要通过a2dp profile来连接,hid设备(例如鼠标)需要通过input profile来连接。

我会单独写一篇文章讲解如何区分设备类型。

    private void connectDevice() {
        if(mBluetoothA2dpProfile == null) {
            Log.d(TAG, "don't get a2dp profile,can not run connect");
        } else {
            try {
                //通过反射获取BluetoothA2dp中connect方法
                Method connectMethod = BluetoothA2dp.class.getMethod("connect",
                        BluetoothDevice.class);
                Log.d(TAG, "connectMethod = " + connectMethod);
                connectMethod.invoke(mBluetoothA2dpProfile, mTargetDevice);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

因为BluetoothA2dp的connect()方法是hide的,需要通过反射的方式来获取调用,当然你可以自己在源码中编译,这样就不需要用反射了。

/**
209     * Initiate connection to a profile of the remote bluetooth device.
210     *
211     * <p> Currently, the system supports only 1 connection to the
212     * A2DP profile. The API will automatically disconnect connected
213     * devices before connecting.
214     *
215     * <p> This API returns false in scenarios like the profile on the
216     * device is already connected or Bluetooth is not turned on.
217     * When this API returns true, it is guaranteed that
218     * connection state intent for the profile will be broadcasted with
219     * the state. Users can get the connection state of the profile
220     * from this intent.
221     *
222     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
223     * permission.
224     *
225     * @param device Remote Bluetooth Device
226     * @return false on immediate error,
227     *               true otherwise
228     * @hide
229     */
230    public boolean connect(BluetoothDevice device) {
231        if (DBG) log("connect(" + device + ")");
232        if (mService != null && isEnabled() &&
233            isValidDevice(device)) {
234            try {
235                return mService.connect(device);
236            } catch (RemoteException e) {
237                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
238                return false;
239            }
240        }
241        if (mService == null) Log.w(TAG, "Proxy not attached to service");
242        return false;
243    }

连接状态的变化可以通过监听BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED来实现,preConnectionState表示前一个连接状态,newConnectionState表示后一个连接状态。

一个正常的连接状态流程为:BluetoothProfile.STATE_DISCONNECTED(0)-->BluetoothProfile.STATE_CONNECTING(1)-->

BluetoothProfile.STATE_CONNCTED(2)。

如果出现0-->1-->0的状态变化,则说明连接失败了,需要根据蓝牙log进行分析了。

一个正常的断开流程为:BluetoothProfile.STATE_CONNECTED(2)-->BluetoothProfile.STATE_DISCONNECTING(3)-->BluetoothProfile.STATE_DISCONNECTED(0)。

 if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
                BluetoothDevice btdevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int preConnectionState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
                int newConnectionState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
                Log.d(TAG, "btdevice = " + btdevice.getName() + ", preConnectionState = "
                        + preConnectionState + ", newConnectionState" + newConnectionState);
                if(newConnectionState == BluetoothProfile.STATE_CONNECTED && preConnectionState == BluetoothProfile.STATE_CONNECTING) {
                    Log.d(TAG, "target device connect success");
                }
            }

4.总结 

本文主要从应用层的角度分析了经典蓝牙设备的配对、连接流程。大致为:扫描设备--监听DEVICE_FOUND广播-->直到找到目标设备-->对目标设备发起配对-->监听到设备配对成功-->发起设备连接-->监听连接状态的广播,连接成功。

在后面的文章中,会对如下内容进行分析:

(1)如何进行设备区分,即设备分类;

(2)如何快速扫描设备;

如果想持续关注本博客内容,请扫描关注个人微信公众号,或者微信扫描:万物互联技术。

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2020-04-27 10:11:57 peachs885090 阅读数 145

1.首先设置获取各种代理

		defaultAdapter = BluetoothAdapter.getDefaultAdapter();
        //获取A2DP代理对象
        defaultAdapter.getProfileProxy(mContext, mListener, BluetoothProfile.A2DP);
        //获取HEADSET代理对象
        defaultAdapter.getProfileProxy(mContext, mListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mListener = new BluetoothProfile.ServiceListener() {
        @Override
        public void onServiceDisconnected(int profile) {
            if(profile == BluetoothProfile.A2DP){
                mBluetoothA2dp = null;
            }else if(profile == BluetoothProfile.HEADSET){
                bluetoothHeadset = null;
            }
        }
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            if(profile == BluetoothProfile.A2DP){
                mBluetoothA2dp = (BluetoothA2dp) proxy; //转换
                connectedBluetooth();//得到连接设备
            }else if(profile == BluetoothProfile.HEADSET){
                bluetoothHeadset = (BluetoothHeadset) proxy;
            }
        }
    };

2.连接一个蓝牙的时候需要断开其他的蓝牙

List<BluetoothDevice> connectedDevices = mBluetoothA2dp.getConnectedDevices();
if (connectedDevices != null && connectedDevices.size() > 0) {
    for (BluetoothDevice bluetoothDevice2 : connectedDevices) {
          BluetoothUtils.disConnectA2dp(bluetoothDevice2, mBluetoothA2dp);
          BluetoothUtils.disConnectHeadset(bluetoothDevice2, bluetoothHeadset);
    }
}
BluetoothUtils.connectA2dp(device,mBluetoothA2dp);

连接跟断开连接的方法

 /**
     * 连接蓝牙设备
     *
     * @param device
     * @param mA2dp
     */
    public static boolean connectA2dp(BluetoothDevice device, BluetoothA2dp mA2dp) {
        boolean state = false;
        if (mA2dp == null) {
            return false;
        }
        setPriority(device, 100, mA2dp); //设置priority
        try {
            //通过反射获取BluetoothA2dp中connect方法(hide的),进行连接。
            Method connectMethod = BluetoothA2dp.class.getMethod("connect",
                    BluetoothDevice.class);
            state = (boolean) connectMethod.invoke(mA2dp, device);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return state;
    }

    /**
     * 断开 A2dp 连接
     *
     * @param device 设备
     * @param mA2dp
     */
    public static boolean disConnectA2dp(BluetoothDevice device, BluetoothA2dp mA2dp) {
        if (mA2dp == null) {
            return false;
        }
        setPriority(device, 0, mA2dp);
        boolean connect = false;
        try {
            //通过反射获取BluetoothA2dp中connect方法(hide的),断开连接。
            Method connectMethod = BluetoothA2dp.class.getMethod("disconnect", BluetoothDevice.class);
            connect = (boolean) connectMethod.invoke(mA2dp, device);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return connect;

    }

    /**
     * 断开 Headset 连接
     * @param bluetoothDevice
     * @param bluetoothHeadset
     * @return
     */
    public static boolean disConnectHeadset(BluetoothDevice bluetoothDevice, BluetoothHeadset bluetoothHeadset) {
        if (bluetoothHeadset == null) {
            return false;
        }
        boolean result = false;
        setHeadsetPriority(bluetoothDevice, 0, bluetoothHeadset);
        try {
            result = ((Boolean) BluetoothHeadset.class.getMethod("disconnect", new Class[]{BluetoothDevice.class}).invoke(bluetoothHeadset, new Object[]{bluetoothDevice})).booleanValue();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }

    public static void setHeadsetPriority(BluetoothDevice bluetoothDevice, int i, BluetoothHeadset bluetoothHeadset) {
        try {
            BluetoothHeadset.class.getMethod("setPriority", new Class[]{BluetoothDevice.class, Integer.TYPE}).invoke(bluetoothHeadset, new Object[]{bluetoothDevice, Integer.valueOf(i)});
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    /**
     * 设置优先级
     *
     * @param device   设备
     * @param priority 优先级
     * @param mA2dp
     */
    public static void setPriority(BluetoothDevice device, int priority, BluetoothA2dp mA2dp) {
        try {//通过反射获取BluetoothA2dp中setPriority方法(hide的),设置优先级
            Method connectMethod = BluetoothA2dp.class.getMethod("setPriority",
                    BluetoothDevice.class, int.class);
            connectMethod.invoke(mA2dp, device, priority);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

(注:发现如果只是代理了BluetoothA2dp,没有代理BluetoothHeadset ,那么也可以断开蓝牙,但是你再连接其他蓝牙音箱后,可能会出现两个音箱都是连接状态,只不过一个在播放音乐一个没有播放,重启设备后自动连接上的蓝牙音箱也并不一定是关机前正在播放的)

2019-02-15 15:48:14 chailongger 阅读数 1043

Android 蓝牙音箱开发

项目下载地址github:

1.打开蓝牙:

      mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

      /**如果本地蓝牙没有开启,则开启*/
      if (!mBluetoothAdapter.isEnabled()) {
        // 我们通过startActivityForResult()方法发起的Intent将会在onActivityResult()回调方法中获取用户的选择,比如用户单击了Yes开启,
        // 那么将会收到RESULT_OK的结果,
        // 如果RESULT_CANCELED则代表用户不愿意开启蓝牙
        Intent mIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(mIntent, ENABLE_BLUE);
       } else {
           Toast.makeText(this, "蓝牙已开启", Toast.LENGTH_SHORT).show();
       }
1
2
3
4
5
6
7
8
9
10
11
12
监听打开的结果:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == ENABLE_BLUE) {
            if (resultCode == RESULT_OK) {
                Toast.makeText(this, "蓝牙开启成功", Toast.LENGTH_SHORT).show();
                getBondedDevices();
            } else if (resultCode == RESULT_CANCELED) {
                Toast.makeText(this, "蓝牙开始失败", Toast.LENGTH_SHORT).show();
            }
        } else {

        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2.关闭蓝牙:

     /**关闭蓝牙*/
     if (mBluetoothAdapter.isEnabled()) {
           mBluetoothAdapter.disable();
     }
1
2
3
4
3.设计蓝牙为可见:

     Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
     intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 180);//180可见时间
     startActivity(intent);
1
2
3
4.收索蓝牙:

注册广播监听搜索的结果:

     /**注册搜索蓝牙receiver*/
     mFilter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
     mFilter.addAction(BluetoothDevice.ACTION_FOUND);
     mFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
     registerReceiver(mReceiver, mFilter);
1
2
3
4
5
开始的搜索:

     // 如果正在搜索,就先取消搜索
     if (mBluetoothAdapter.isDiscovering()) {
         mBluetoothAdapter.cancelDiscovery();
     }
     // 开始搜索蓝牙设备,搜索到的蓝牙设备通过广播返回
     mBluetoothAdapter.startDiscovery();
1
2
3
4
5
6
监听搜索的结果:

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            /** 搜索到的蓝牙设备*/
            if (action.equals(BluetoothDevice.ACTION_FOUND)) {
                BluetoothDevice device = intent
                        .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                // 搜索到的不是已经配对的蓝牙设备
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    BlueDevice blueDevice = new BlueDevice();
                    blueDevice.setName(device.getName());
                    blueDevice.setAddress(device.getAddress());
                    blueDevice.setDevice(device);
                    setDevices.add(blueDevice);
                    blueAdapter.setSetDevices(setDevices);
                    blueAdapter.notifyDataSetChanged();
                    Log.d(MAINACTIVITY, "搜索结果......"+device.getName());
                }

                /**当绑定的状态改变时*/
            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {


                /**搜索完成*/
            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                setProgressBarIndeterminateVisibility(false);
                Log.d(MAINACTIVITY, "搜索完成......");
                hideProgressDailog();
            }
        }
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
5.配对蓝牙:

配对工具类

    public class BlueUtils {

        public BlueUtils(BlueDevice blueDevice) {
            this.blueDevice = blueDevice;
        }

        /**
         * 配对
         */
        public void doPair() {
                if(null == mOthHandler){
                    HandlerThread handlerThread = new HandlerThread("other_thread");
                    handlerThread.start();
                    mOthHandler = new Handler(handlerThread.getLooper());
                }
                mOthHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        initSocket();   //取得socket
                        try {
                            socket.connect();   //请求配对
        //                      mAdapterManager.updateDeviceAdapter();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
        }


        /**
         * 取消蓝牙配对
         * @param device
         */
        public static void unpairDevice(BluetoothDevice device) {
            try {
                Method m = device.getClass()
                        .getMethod("removeBond", (Class[]) null);
                m.invoke(device, (Object[]) null);
            } catch (Exception e) {
                Log.d("BlueUtils", e.getMessage());
            }
        }


        /**
         * 取得BluetoothSocket
         */
       private void initSocket() {
            BluetoothSocket temp = null;
            try {
                Method m = blueDevice.getDevice().getClass().getMethod("createRfcommSocket", new Class[] {int.class});
                temp = (BluetoothSocket) m.invoke(blueDevice.getDevice(), 1);
                //怪异错误: 直接赋值给socket,对socket操作可能出现异常,  要通过中间变量temp赋值给socket
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            socket = temp;
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
注册监听配对结果的广播(使用同上面的注册代码)

     /**注册搜索蓝牙receiver*/
     mFilter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
     mFilter.addAction(BluetoothDevice.ACTION_FOUND);
     mFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
     registerReceiver(mReceiver, mFilter);
1
2
3
4
5
开始配对

    /**
     * 开始配对蓝牙设备
     *
     * @param blueDevice
     */
    private void startPariBlue(BlueDevice blueDevice) {
        BlueUtils blueUtils = new BlueUtils(blueDevice);
        blueUtils.doPair();
    }
1
2
3
4
5
6
7
8
9
监听配对结果:(使用同上面的广播接收者)

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            /** 搜索到的蓝牙设备*/
            if (action.equals(BluetoothDevice.ACTION_FOUND)) {

                .....
                /**当绑定的状态改变时*/
            } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                switch (device.getBondState()) {
                    case BluetoothDevice.BOND_BONDING:
                        Log.d(MAINACTIVITY, "正在配对......");

                        break;
                    case BluetoothDevice.BOND_BONDED:
                        Log.d(MAINACTIVITY, "完成配对");
                        hideProgressDailog();
                        /**开始连接*/
                        contectBuleDevices();
                        break;
                    case BluetoothDevice.BOND_NONE:
                        Log.d(MAINACTIVITY, "取消配对");
                        Toast.makeText(MainActivity.this,"成功取消配对",Toast.LENGTH_SHORT).show();
                        getBondedDevices();
                        break;
                    default:
                        break;
                }

                /**搜索完成*/
            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                ....
            }
        }
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
6.使用A2DP协议连接蓝牙设备:

连接设备


    /**
     * 开始连接蓝牙设备
     */
    private void contectBuleDevices() {
        /**使用A2DP协议连接设备*/
        mBluetoothAdapter.getProfileProxy(this, mProfileServiceListener, BluetoothProfile.A2DP);
    }
1
2
3
4
5
6
7
8
监听连接的回调

    /**
     * 连接蓝牙设备(通过监听蓝牙协议的服务,在连接服务的时候使用BluetoothA2dp协议)
     */
    private BluetoothProfile.ServiceListener mProfileServiceListener = new BluetoothProfile.ServiceListener() {

        @Override
        public void onServiceDisconnected(int profile) {

        }

        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            try {
                if (profile == BluetoothProfile.HEADSET) {
                    ....

                } else if (profile == BluetoothProfile.A2DP) {
                    /**使用A2DP的协议连接蓝牙设备(使用了反射技术调用连接的方法)*/
                    a2dp = (BluetoothA2dp) proxy;
                    if (a2dp.getConnectionState(currentBluetoothDevice) != BluetoothProfile.STATE_CONNECTED) {
                        a2dp.getClass()
                                .getMethod("connect", BluetoothDevice.class)
                                .invoke(a2dp, currentBluetoothDevice);
                        Toast.makeText(MainActivity.this,"请播放音乐",Toast.LENGTH_SHORT).show();
                        getBondedDevices();
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
7.添加权限

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
1
2
8.打开乐库播放音乐

9.Android 6.0的系统需要动态添加权限才能搜索出蓝牙设备

Android 6.0的系统需要动态添加权限

    /**判断手机系统的版本*/
    if (Build.VERSION.SDK_INT >= 6.0) {//Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
            if(ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)!=PackageManager.PERMISSION_GRANTED){
                /**动态添加权限:ACCESS_FINE_LOCATION*/
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSION_REQUEST_CONSTANT);
            }
        }
1
2
3
4
5
6
7
8
请求权限的回调

    /**请求权限的回调:这里判断权限是否添加成功*/
     /**请求权限的回调:这里判断权限是否添加成功*/
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSION_REQUEST_CONSTANT: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.i("main","添加权限成功");
                }
                return;
            }
        }
    }
 

2018-08-22 08:59:39 csdn_xiaozhe 阅读数 2164

Android7.0 应用连接蓝牙音箱(亲测可用)

前言

之前操作android蓝牙,都是直接进行数据流通讯,配对成功后接收方监听socket,发送方连接即可。
最近需要连接蓝牙音箱,和系统比较发现,蓝牙音箱存在已连接的状态,而部分android手机之间是不存在连接状态的。
因此将亲测代码贴出,本人萌新,欢迎探讨。

文章中用到的IBluetoothA2dp可在该路径下下载:
https://download.csdn.net/download/csdn_xiaozhe/10618842
至于aidl的使用方式,将在下一篇中记录。

正文

直接贴代码(蓝牙开启,搜索,配对过程省略)

 private IBluetoothA2dp iBluetoothA2dp = null;
 private BluetoothDevice mRemoteBluetoothDevice = null;
 
 @Override
 public void onCreate() {
 //蓝牙开启,搜索,配对过程已省略,默认已经配对成功
	 if (mRemoteBluetoothDevice != null && mRemoteBluetoothDevice.getBondState()== BluetoothDevice.BONDED) {
	             bindA2dpService();
            while(true) {
                if (processMili < 30000) { //等待服务绑定超时时间
                    if (iBluetoothA2dp != null) {
                        try {
                            iBluetoothA2dp.connect(mRemoteBluetoothDevice);
                        } catch (Exception     e) {
                            e.printStackTrace();
                        }
                        break;
                    } else {
                        processMili = System.currentTimeMillis() - startMili;
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                Log.e("Altman","超时,此次连接失败");
                break;
                }
            }
	 }
 }
 
private void bindA2dpService(){
	Intent intent = getExplicitIntent(this,new Intent(IBluetoothA2dp.class.getName()));
	boolean success = this.bindService(intent , mServiceConnection, Context.BIND_AUTO_CREATE);
	if (success) {
		Log.e("Altman","只能证明,bindService执行成功,结果在回调函数中");
	} else {
		Log.e("Altman","只能证明,bindService执行失败");
	}
}
    
public ServiceConnection mServiceConnection= new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                iBluetoothA2dp = IBluetoothA2dp.Stub.asInterface(service);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e("Altman","获取服务失败");
        }
    };

    public Intent getExplicitIntent(Context context, Intent implicitIntent) {
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }
        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);
        Intent explicitIntent = new Intent(implicitIntent);
        explicitIntent.setComponent(component);
        return explicitIntent;
    }
2017-11-06 11:26:29 wangzh92429 阅读数 1259
public class BluetoothConnect {
    private DeviceReceiver devicecReceiver;
    List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
    private BluetoothA2dp mBluetoothA2dp;
    private BluetoothAdapter bluetoothAdapter;   //蓝牙适配器

    public void setDevices(List<BluetoothDevice> devices) {
        this.devices = devices;
    }

    public void setDevicecReceiver(DeviceReceiver DeviceReceiver)
    {
        this.devicecReceiver = DeviceReceiver;
    }
    //打开蓝牙
    public void openBt()
    {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        if(!bluetoothAdapter.isEnabled())
        {
            bluetoothAdapter.enable();
        }
        else
        {}
    }

    //搜索蓝牙设备
    public void searchBt()
    {
        devices.clear();
        bluetoothAdapter.startDiscovery();
        getBluetoothA2DP();
        devicecReceiver.deviceRecv();
        while(bluetoothAdapter.isDiscovering());

        bluetoothAdapter.cancelDiscovery();
    }
    //连接蓝牙音响
    public void connect(BluetoothDevice btDev)
    {
        if(mBluetoothA2dp == null){
            return;
        }
        if(bluetoothAdapter == null){
            return;
        }

        if(btDev.getBondState() == BluetoothDevice.BOND_NONE)
        {
            //btDevice.createBond();
            try {
                Method createBondMethod = BluetoothDevice.class
                        .getMethod("createBond");
                createBondMethod.invoke(btDev);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        while(btDev.getBondState() != BluetoothDevice.BOND_BONDED);

        try {
            Method connect = mBluetoothA2dp.getClass().getDeclaredMethod("connect", BluetoothDevice.class);
            connect.setAccessible(true);
            connect.invoke(mBluetoothA2dp,btDev);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private void getBluetoothA2DP() {
        if (bluetoothAdapter == null) {
            return;
        }

        if (mBluetoothA2dp != null) {
            return;
        }

        bluetoothAdapter.getProfileProxy(MainFragmentActivity.instance, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                if (profile == BluetoothProfile.A2DP) {
                    //Service连接成功,获得BluetoothA2DP
                    mBluetoothA2dp = (BluetoothA2dp) proxy;
                }
            }

            @Override
            public void onServiceDisconnected(int profile) {

            }
        }, BluetoothProfile.A2DP);
    }

    public interface DeviceReceiver
    {
        public void deviceRecv();
    }
}

蓝牙音箱无声音

阅读数 1283