[Android Studio]蓝牙通信(Fragment中实现)

[Android Studio]蓝牙通信(Fragment中实现)

  • UUID
  • 蓝牙RFCOMM数据通信
  • 蓝牙相关权限
  • 布局
    • Toolbar Menu
    • TextView ArrayAdapter
  • 蓝牙搜索链接
  • Hander传递消息

[Android Studio]蓝牙通信(Fragment中实现)_第1张图片 [Android Studio]蓝牙通信(Fragment中实现)_第2张图片

UUID

UUID(Universally Unique Identifier):通用唯一识别码,软件建构的标准。其保证对同一时空中的所有机器都唯一。其目的是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。

蓝牙RFCOMM数据通信

一个蓝牙设备可提供多种服务,每种服务对应一个UUID。
服务有:蓝牙音频传输 A2DP,免提 HEADFREE,电话本 PBAP,串口通信 SPP

可通过BluetoothSocket类建立有关蓝牙通信的套接字socket,通过MAC地址来识别远程设备,建立完通信连接RFCOMM通道后以输入、输出流方式通信。

public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid)

其中的UUID参数指定socket的端口。

蓝牙通信分server服务器端和client客户端,它们用BluetoothSocket类的不同方法来获取数据。

server端:

	private class AcceptThread extends Thread {
        private final BluetoothServerSocket mmServerSocket;

        public AcceptThread() {
            BluetoothServerSocket tmp = null;
            try {
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
            } catch (IOException e) {
                e.printStackTrace();
            }
            mmServerSocket = tmp;
        }

        @Override
        public void run() {
            setName("AcceptThread");
            BluetoothSocket socket = null;
            while (mState != STATE_CONNECTED) {
                try {
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (socket != null) {
                    synchronized (BluetoothChatService.this) {
                        switch (mState) {
                            case STATE_LISTEN:
                            case STATE_CONNECTING:
                                connected(socket, socket.getRemoteDevice());
                                break;
                            case STATE_NONE:
                            case STATE_CONNECTED:
                                try {
                                    socket.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                break;
                        }
                    }
                }
            }
        }

        public void cancel() {
            try {
                mmServerSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

client端:

	private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;
            try {
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) {
                e.printStackTrace();
            }
            mmSocket = tmp;
        }

        @Override
        public void run() {
            setName("ConnectThread");
            mAdapter.cancelDiscovery();
            try {
                mmSocket.connect();
            } catch (IOException e) {
                connectionFailed();
                try {
                    mmSocket.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
                BluetoothChatService.this.start();
                return;
            }
            synchronized (BluetoothChatService.this) {
                mConnectThread = null;
            }
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private class ConnectedThread  extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                e.printStackTrace();
            }
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }

        @Override
        public void run() {
            byte[] buffer = new byte[1024];
            int bytes;
            while (true) {
                try {
                    bytes = mmInStream.read(buffer);
                  mHandler.obtainMessage(homeFragment.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
                } catch (IOException e) {
                    connectionLost();
                    break;
                }
            }
        }

        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);
                mHandler.obtainMessage(homeFragment.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

synchronized:同步方法实现排队调用。

蓝牙相关权限

AndroidManifest.xml中添加:



在Fragment中添加请求权限操作。

布局

[Android Studio]蓝牙通信(Fragment中实现)_第3张图片[Android Studio]蓝牙通信(Fragment中实现)_第4张图片

Toolbar Menu

我使用的Android Studio的版本是3.5.3,对于toolbar还是直接使用AS自带的比较好。若是写出v7版本的toolbar,则会出现一些版本不支持的问题。

Toolbar:点击如图右上角图标显示下拉列表,下拉列表为option_menu.xml布局。

[Android Studio]蓝牙通信(Fragment中实现)_第5张图片[Android Studio]蓝牙通信(Fragment中实现)_第6张图片
option_menu.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/scan"
        android:icon="@android:drawable/ic_menu_myplaces"
        android:title="@string/connect" />
    <item android:id="@+id/discoverable"
        android:icon="@android:drawable/ic_menu_view"
        android:title="@string/discoverable" />
    <item android:id="@+id/back"
        android:icon="@android:drawable/ic_menu_close_clear_cancel"
        android:title="@string/back" />
</menu>

在Fragment中使用Toolbar和在Activity使用Toolbar有所不同。toolbar可作为独立控件, 在Fragment中无需加上
((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
但需要加上setHasOptionsMenu(true) ,表示有menu待使用。

一开始这里没写正确,应用根本打不开,只有闪退。

homeFragment.java:

	Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
	setHasOptionsMenu(true);
//	((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
	toolbar.inflateMenu(R.menu.option_menu);

TextView ArrayAdapter

发送消息和蓝牙设备搜索显示这两部分有用到了ArrayAdapter,如:

mPairedDevicesArrayAdapter = new ArrayAdapter(this, R.layout.device_name);

这种ArrayAdapter的定义使用到layout,其布局的根节点必须为TextView(如下代码),否则报错:ArrayAdapter requires the resource ID to be a TextView

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp">
</TextView>

蓝牙搜索链接

在点击了option_menu.xml中的“我的好友”之后,随后打开新的界面进行查看已链接过的设备和搜索发现的设备。以广播Broadcast的形式来实现接收扫描到的蓝牙设备。

DeviceList.java

	private final 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);
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = getResources().getText(R.string.none_found).toString();
                    mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };

	private void init() {
        Button scanButton = findViewById(R.id.button_scan);
        scanButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Toast.makeText(DeviceList.this, R.string.scanning, Toast.LENGTH_LONG).show();
                doDiscovery();  //搜索蓝牙设备
            }
        });
        mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
        mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
        //已配对蓝牙设备列表
        ListView pairedListView = findViewById(R.id.paired_devices);
        pairedListView.setAdapter(mPairedDevicesArrayAdapter);
        pairedListView.setOnItemClickListener(mPaireDeviceClickListener);
        //未配对蓝牙设备列表
        ListView newDevicesListView = findViewById(R.id.new_devices);
        newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
        newDevicesListView.setOnItemClickListener(mNewDeviceClickListener);
        //动态注册广播接收者
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(mReceiver, filter);
        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        registerReceiver(mReceiver, filter);
        mBtAdapter = BluetoothAdapter.getDefaultAdapter();
        Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
            for (BluetoothDevice device : pairedDevices) {
                mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        } else {
            String noDevices = getResources().getText(R.string.none_paired).toString();
            mPairedDevicesArrayAdapter.add(noDevices);
        }
    }

Hander传递消息

使用Hander对象实现主线程与子线程之间传递消息,同时在“微信”tab界面显示当前链接状态。
homeFragment.java:

	mChatService = new BluetoothChatService(view.getContext(), mHandler);
	
	private final Handler mHandler = new Handler() {   //消息处理
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_STATE_CHANGE:
                    switch (msg.arg1) {
                        case BluetoothChatService.STATE_CONNECTED:
                            mTitle.setText(R.string.title_connected_to);
                            mTitle.append(mConnectedDeviceName);
                            mConversationArrayAdapter.clear();
                            break;
                        case BluetoothChatService.STATE_CONNECTING:
                            mTitle.setText(R.string.title_connecting);
                            break;
                        case BluetoothChatService.STATE_LISTEN:
                        case BluetoothChatService.STATE_NONE:
                            mTitle.setText(R.string.title_not_connected);
                            break;
                    }
                    break;
                case MESSAGE_WRITE:
                    byte[] writeBuf = (byte[]) msg.obj;
                    String writeMessage = new String(writeBuf);
                    mConversationArrayAdapter.add("我:  " + writeMessage);
                    break;
                case MESSAGE_READ:
                    byte[] readBuf = (byte[]) msg.obj;
                    String readMessage = new String(readBuf, 0, msg.arg1);
                    mConversationArrayAdapter.add(mConnectedDeviceName + ":  "
                            + readMessage);
                    break;
                case MESSAGE_DEVICE_NAME:
                    mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
                    Toast.makeText(getActivity().getApplicationContext(),"链接到 " + mConnectedDeviceName, Toast.LENGTH_SHORT).show();
                    break;
                case MESSAGE_TOAST:
                    Toast.makeText(getActivity().getApplicationContext(), msg.getData().getString(TOAST), Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

参阅:

  • 对Android蓝牙UUID的理解
  • 关于蓝牙通信文档 Android建立蓝牙RFCOMM通信
  • fragment中toolbar的一些坑
  • ArrayAdapter requires the resource ID to be a TextView问题
  • 掌握手机Bluetooth的使用和Android对Bluetooth的支持
  • Android Developers | Bluetooth

源码已上传至 GitHub。

你可能感兴趣的:(Android)