最近调试需求,针对性对某个无屏幕的设备编写了个蓝牙日志传输应用,也很简单,即使把log和log文件通过蓝牙传输到另一台设备查看,不多说,讲下蓝牙配对连接。直接上代码
public class BleLogMonitorAty extends FragmentActivity implements AdapterView.OnItemClickListener, View.OnClickListener { private static final boolean DEBUG = true; public static BleLogMonitorAty logMonitorAty; private static final String TAG = "BleLogMonitorAty"; private ArrayAdapter<String> devAdapter; private List<BluetoothDevice> devices = new ArrayList<>(); private List<BluetoothDevice> bondeDevices = new ArrayList<>(); private List<String> devNames = new ArrayList<>(); // private List<String> bondeDevNames = new ArrayList<>(); private BluetoothAdapter mBluetoothAdapter; private ProgressBar progressBar; // private ArrayAdapter<String> bonDevAdapter; private BluetoothDevice curDevice; private BleLogFragment logFragment; private DevAdapter bonDevAdapter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ble_layout); logMonitorAty = this; ActivityMger.addActivity(this); initView(); initBluetooth(); updateBondDevlist(); Intent intent = new Intent(this, LogService.class); startService(intent); bindService(intent, conn, Context.BIND_AUTO_CREATE); } private void updateBondDevlist() { bondeDevices.clear(); bondeDevices.addAll(mBluetoothAdapter.getBondedDevices()); bonDevAdapter.notifyDataSetChanged(); } private void initBluetooth() { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter.enable(); //每搜索到一个设备就会发送一个该广播 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); //配对请求广播 registerReceiver(receiver, filter); } private void discovery() { if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); } //开启搜索 mBluetoothAdapter.startDiscovery(); progressBar.setVisibility(View.VISIBLE); devNames.clear(); devices.clear(); } private void initView() { progressBar = findViewById(R.id.progressbar); findViewById(R.id.bt_search).setOnClickListener(this); ListView listView2 = findViewById(R.id.bondDev_list); ListView listView = findViewById(R.id.devices_list); devAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, devNames); listView.setAdapter(devAdapter); listView.setOnItemClickListener(this); bonDevAdapter = new DevAdapter(); // bonDevAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, bondeDevNames); listView2.setAdapter(bonDevAdapter); listView2.setOnItemClickListener(this); listView2.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { BluetoothDevice device = bondeDevices.get(position); if (device != null && device.getBondState() == 12 || device.getBondState() == 11) { showCancelBondeDialog(device); } return true; } }); } private void showCancelBondeDialog(final BluetoothDevice device) { AlertDialog.Builder builder = new AlertDialog.Builder(this).setMessage("取消配对?" + device.getName()) .setNegativeButton("NO", null).setPositiveButton("YES", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { try { boolean removeBond = ClsUtils.removeBond(device.getClass(), device); if (removeBond) { Toast.makeText(logService, "取消配对成功!", Toast.LENGTH_SHORT).show(); // discovery(); } } catch (Exception e) { e.printStackTrace(); } } }); builder.create().show(); } class DevAdapter extends BaseAdapter { @Override public int getCount() { return bondeDevices == null ? 0 : bondeDevices.size(); } @Override public BluetoothDevice getItem(int position) { return bondeDevices.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { DevViewHolder dh = null; if (convertView == null) { convertView = getLayoutInflater().inflate(R.layout.dev_item, null); dh = new DevViewHolder(); dh.tv_name = convertView.findViewById(R.id.tv_name); dh.bt_cnnt = convertView.findViewById(R.id.btn_cnnt); convertView.setTag(dh); } else { dh = (DevViewHolder) convertView.getTag(); } BluetoothDevice device = getItem(position); dh.tv_name.setText(device.getName() + " " + device.getAddress() + " state:" + device.getBondState()); if (LogService.hasCnntAddr != null && LogService.hasCnntAddr.equals(device.getAddress())) { dh.bt_cnnt.setVisibility(View.VISIBLE); dh.bt_cnnt.setText("已连接"); dh.bt_cnnt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { disConnectDev(); Toast.makeText(logService, "断开连接!", Toast.LENGTH_SHORT).show(); v.setVisibility(View.GONE); } }); } return convertView; } } static class DevViewHolder { TextView tv_name; Button bt_cnnt; } private final BroadcastReceiver receiver = new BroadcastReceiver() { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.v(TAG, "ACTION:" + intent.getAction()); if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Log.v(TAG, device.getName() + ":" + device.getAddress() + ",state:" + device.getBondState() + "," + device.getType()); if (device.getBondState() != BluetoothDevice.BOND_BONDED || device.getBondState() != BluetoothDevice.BOND_BONDING) { devNames.add(device.getName() + ":" + device.getAddress()); devices.add(device); devAdapter.notifyDataSetChanged(); } updateBondDevlist(); } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) { //开始搜索 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { //已搜素完成 progressBar.setVisibility(View.GONE); } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { Toast.makeText(context, "" + intent.getAction(), Toast.LENGTH_SHORT).show(); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); Log.v(TAG, "PAIRING type=" + type); //3 if (type == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION || type == 4 || type == 5) { int pairingKey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); Log.v(TAG, "pairingKey=" + pairingKey); } try { abortBroadcast(); // if (true) return; boolean pair = ClsUtils.setPin(device.getClass(), device, "1234"); //1.确认配对 ClsUtils.setPairingConfirmation(device.getClass(), device, true); if (DEBUG) Log.v(TAG, "pair=" + pair + ",state:" + device.getBondState()); } catch (Exception e) { e.printStackTrace(); } } else if (BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { int connectState = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1); if (DEBUG) Log.v(TAG, "++++connectState=" + connectState); } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { if (DEBUG) Log.v(TAG, "++++ACTION_BOND_STATE_CHANGED"); final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); int state = device.getBondState(); if (DEBUG) Log.v(TAG, device.getName() + "+++++bond state:" + state); if (state == BluetoothDevice.BOND_BONDED) { if (Build.VERSION.SDK_INT < 23) { int deviceClass = device.getBluetoothClass().getDeviceClass(); int majorDeviceClass = device.getBluetoothClass().getMajorDeviceClass(); if (DEBUG) Log.v(TAG, "deviceClass=" + deviceClass); if (DEBUG)Log.v(TAG, "MajorDeviceClass=" + majorDeviceClass); if(deviceClass==1344&&majorDeviceClass==1280) // PERIPHERAL_KEYBOARD&&PERIPHERAL 外围设备 PROFILE_HID类型 3 cnntToInputDevice(device); } if (!containsThisDev(bondeDevices, device)) { bondeDevices.add(device); bonDevAdapter.notifyDataSetChanged(); } if (containsThisDev(devices, device)) { int i = devices.indexOf(device); devices.remove(device); devNames.remove(i); devAdapter.notifyDataSetChanged(); } mBluetoothAdapter.cancelDiscovery(); } else if (state == BluetoothDevice.BOND_NONE) { if (containsThisDev(bondeDevices, device)) { bondeDevices.remove(device); bonDevAdapter.notifyDataSetChanged(); } if (!containsThisDev(devices, device)) { devices.add(device); devNames.add(device.getName() + ":" + device.getAddress()); devAdapter.notifyDataSetChanged(); } } } } }; private void cnntToInputDevice(final BluetoothDevice device) { if (DEBUG) Log.v(TAG, "===cnntToInputDevice=="); final int INPUT_DEVICE = 4; // this is hidden memeber in BluetoothDevice // BluetoothAdapter.getDefaultAdapter().closeProfileProxy(); 调用此方法关闭代理服务 BluetoothAdapter.getDefaultAdapter().getProfileProxy(BleLogMonitorAty.this, new BluetoothProfile.ServiceListener() { @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { Class<?> clazz = null; try { clazz = Class.forName("android.bluetooth.BluetoothInputDevice"); Object obj = clazz.cast(proxy); Method connectMethod = clazz.getDeclaredMethod("connect", BluetoothDevice.class); boolean resultCode = (boolean) connectMethod.invoke(obj, device); Method setPriority = clazz.getDeclaredMethod("setPriority", BluetoothDevice.class, int.class); setPriority.invoke(obj, device, 1000); if (DEBUG) Log.v(TAG, "cnntToInputDevice resultCode=" + resultCode); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(int profile) { if (DEBUG) Log.d("wtf", "onservice disconnected " + profile); } }, INPUT_DEVICE); } private boolean containsThisDev(List<BluetoothDevice> deviceList, BluetoothDevice device) { Log.v(TAG, "containsThisDev deviceList=" + deviceList.size()); if (deviceList == null || deviceList.size() < 1) return false; for (int i = 0; i < deviceList.size(); i++) { if (deviceList.get(i).getAddress().equals(device.getAddress())) return true; } return false; } @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { switch (parent.getId()) { case R.id.devices_list: if (mBluetoothAdapter.isDiscovering()) mBluetoothAdapter.cancelDiscovery(); curDevice = devices.get(position); try { boolean bond = ClsUtils.createBond(curDevice.getClass(), curDevice); if (bond) { Toast.makeText(logMonitorAty, "配对成功!", Toast.LENGTH_SHORT).show(); updateBondDevlist(); } else { Toast.makeText(logMonitorAty, "配对失败!", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); } break; case R.id.bondDev_list: BluetoothDevice bondeDev = bondeDevices.get(position); if (logService != null) { if (LogService.hasCnntAddr != null && !bondeDev.getAddress().equals(LogService.hasCnntAddr)) { Toast.makeText(BleLogMonitorAty.this, "请先断开上一个设备", Toast.LENGTH_SHORT).show(); return; } logService.connectDev(bondeDev); } updateBondDevlist(); // logFragment = new BleLogFragment(); // getSupportFragmentManager().beginTransaction().add(R.id.framecontent, logFragment, "LOG_FRAGMENT").addToBackStack("LF").commit(); break; } } @Override protected void onDestroy() { super.onDestroy(); logMonitorAty = null; ActivityMger.removeActivity(this); if (DEBUG) Log.v(TAG, "==onDestroy"); unregisterReceiver(receiver); unbindService(conn); } private LogService logService; public ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { logService = ((LogService.MyBinder) service).getService(); logService.setDeviceCallback(new LogService.DeviceCallback() { @Override public void connect(BluetoothDevice remote) { if (DEBUG) Log.v(TAG, "Dev connect ..." + LogService.hasCnntAddr); recLog("Dev connect ..."); runOnUiThread(new Runnable() { @Override public void run() { updateBondDevlist(); logFragment = new BleLogFragment(); getSupportFragmentManager().beginTransaction().add(R.id.framecontent, logFragment, "LOG_FRAGMENT").addToBackStack("LF").commit(); } }); } @Override public void disConnect(BluetoothDevice remote) { if (DEBUG) Log.v(TAG, "Dev disConnect ..."); recLog("Dev disConnect ..."); } @Override public void recLog(final String log) { runOnUiThread(new Runnable() { @Override public void run() { if (logFragment != null) logFragment.recLog(log); } }); } }); } @Override public void onServiceDisconnected(ComponentName name) { } }; public void disConnectDev() { if (logService != null) logService.disConnectDev(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_search: if (mBluetoothAdapter.isDiscovering()) { mBluetoothAdapter.cancelDiscovery(); ((Button) v).setText("搜索设备"); } else { discovery(); ((Button) v).setText("停止搜索"); } break; default: break; } }
蓝牙配对流程
1.搜索蓝牙 使用api startDiscovery;
2.绑定使用工具类 ClsUtils的createBond,这个在网上都能找到
boolean bond = ClsUtils.createBond(curDevice.getClass(), curDevice); if (bond) { Toast.makeText(logMonitorAty, "配对成功!", Toast.LENGTH_SHORT).show(); updateBondDevlist(); } else { Toast.makeText(logMonitorAty, "配对失败!", Toast.LENGTH_SHORT).show(); }
我测试用的是一个蓝牙手柄,用于拍照的,类型属于 profile HID,一般输入类型的都是使用这种接口协议,类似蓝牙键盘,游戏手柄,参考博客https://blog.csdn.net/ZBJDSBJ/article/details/47123595
但是我遇到一个情况,就是直接使用绑定createBond的反射方法执行绑定,在MTK6.0上(其他品牌设备6.0没测)可以直接配对并且连接上蓝牙手柄,而5.1上就只能绑定蓝牙手柄而已,没有连接上,就不能对设备进行输入操作,必须还得使用蓝牙Profile ,通过BluetoothAdapter.getDefaultAdapter().getProfileProxy进行连接,但是这种类似蓝牙手柄属于输入蓝牙类型,就是INPUT_DEVICE = 4 ,从BluetoothProfile可以找到,发现是hide类型的,而且还需要用的一个BluetoothInputDevice类,也是个系统hide类型,感谢国外溢出论坛的大佬们,https://stackoverflow.com/questions/27504900/android-bluetooth-paring-input-device ,通过反射得到BluetoothInputDevice并进行连接
private void cnntToInputDevice(final BluetoothDevice device) { if (DEBUG) Log.v(TAG, "===cnntToInputDevice=="); final int INPUT_DEVICE = 4; // this is hidden memeber in BluetoothDevice // BluetoothAdapter.getDefaultAdapter().closeProfileProxy(); 调用此方法关闭代理服务 BluetoothAdapter.getDefaultAdapter().getProfileProxy(BleLogMonitorAty.this, new BluetoothProfile.ServiceListener() { @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { Class<?> clazz = null; try { clazz = Class.forName("android.bluetooth.BluetoothInputDevice"); Object obj = clazz.cast(proxy); Method connectMethod = clazz.getDeclaredMethod("connect", BluetoothDevice.class); boolean resultCode = (boolean) connectMethod.invoke(obj, device); Method setPriority = clazz.getDeclaredMethod("setPriority", BluetoothDevice.class, int.class); setPriority.invoke(obj, device, 1000); if (DEBUG) Log.v(TAG, "cnntToInputDevice resultCode=" + resultCode); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } }
还有值得一提的是,当设备收到蓝牙手柄的主动配对请求是,会收到广播BluetoothDevice.ACTION_PAIRING_REQUEST
此时我们可以监听此广播,想要实现自动配对的话就调用以下两句代码就可以了
boolean pair = ClsUtils.setPin(device.getClass(), device, "1234"); //1.确认配对 ClsUtils.setPairingConfirmation(device.getClass(), device, true); if (DEBUG) Log.v(TAG, "pair=" + pair + ",state:" + device.getBondState());
以上博客简单描述,只为了以后记住这些知识点,同时帮助那些还在为经典蓝牙配对有疑问的童鞋,附上ClsUtils代码
public class ClsUtils { /** * 与设备配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean createBond(Class btClass, BluetoothDevice btDevice) throws Exception { Method createBondMethod = btClass.getMethod("createBond"); Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice); return returnValue.booleanValue(); } /** * 与设备解除配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ static public boolean removeBond(Class<?> btClass, BluetoothDevice btDevice) throws Exception { Method removeBondMethod = btClass.getMethod("removeBond"); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice); return returnValue.booleanValue(); } static public boolean setPin(Class<? extends BluetoothDevice> btClass, BluetoothDevice btDevice, String str) throws Exception { try { Method removeBondMethod = btClass.getDeclaredMethod("setPin", new Class[]{byte[].class}); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice, new Object[] {str.getBytes()}); Log.e("returnValue", "" + returnValue); } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return true; } // 取消用户输入 static public boolean cancelPairingUserInput(Class<?> btClass, BluetoothDevice device) throws Exception { Method createBondMethod = btClass.getMethod("cancelPairingUserInput"); // cancelBondProcess(btClass, device); Boolean returnValue = (Boolean) createBondMethod.invoke(device); return returnValue.booleanValue(); } // 取消配对 static public boolean cancelBondProcess(Class<?> btClass, BluetoothDevice device) throws Exception { Method createBondMethod = btClass.getMethod("cancelBondProcess"); Boolean returnValue = (Boolean) createBondMethod.invoke(device); return returnValue.booleanValue(); } //确认配对 static public void setPairingConfirmation(Class<?> btClass, BluetoothDevice device, boolean isConfirm) throws Exception { Method setPairingConfirmation = btClass.getDeclaredMethod("setPairingConfirmation", boolean.class); setPairingConfirmation.invoke(device, isConfirm); } /** * * @param clsShow */ static public void printAllInform(Class clsShow) { try { // 取得所有方法 Method[] hideMethod = clsShow.getMethods(); int i = 0; for (; i < hideMethod.length; i++) { Log.e("method name", hideMethod[i].getName() + ";and the i is:"+ i); } // 取得所有常量 Field[] allFields = clsShow.getFields(); for (i = 0; i < allFields.length; i++) { Log.e("Field name", allFields[i].getName()); } } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
忘记还有个蓝牙连接,以上针对蓝牙手柄,配对就自动连接了,对应其他的蓝牙设备模块,还得主动连接
1.先定义好UUID
2.BluetoothSocket socket=remote.createRfcommSocketToServiceRecord(定义的uuid);
3.socket.connect(), socket.getInputstream()获取流来读取对端设备发来的数据
4.当然对端设备需要mBluetoothAdapter.listenUsingRfcommWithServiceRecord (uuid)来监听连接接入,使用同一个UUID即可