Android Studio 开发(四)–蓝牙通信

本次Github代码仓库 --crcr1013/BlueToothDemo

文章目录

    • Android Studio 开发(四)--蓝牙通信
  • 一、成果要求
  • 二、关键步骤
    • 1、工程结构
    • 2、清单文件注册权限
    • 3、准备设计资源
    • 4、设计布局
    • 5.编写蓝牙服务
    • 6.启动项目
  • 三、结果测试

一、成果要求

进行蓝牙通信的简要设计与开发。

二、关键步骤

1、工程结构

新建相应的活动组件、布局文件、准备好相应资源,确定整体架构。
(主活动组件MainActivity被重命名为BluetoothChat,它是蓝牙会话的主Activity组件程序。)

2、清单文件注册权限

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

3、准备设计资源

在文件res/values/strings.xml里,添加程序运行过程中的状态描述文本及配色代码等,其代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">蓝牙Demo</string> 
    <string name="send">发送</string>
    <string name="not_connected">你没有链接一个设备</string>
    <string name="bt_not_enabled_leaving">蓝牙不可用,离开聊天室</string>
    <string name="title_connecting">链接中...</string>
    <string name="title_connected_to">连接到:</string>
    <string name="title_not_connected">无链接</string> 
    <string name="scanning">蓝牙设备搜索中...</string>
    <string name="select_device">选择一个好友链接</string>
    <string name="none_paired">没有配对好友</string>
    <string name="none_found">附近没有发现好友</string>
    <string name="title_paired_devices">已配对好友</string>
    <string name="title_other_devices">其它可连接好友</string>
    <string name="button_scan">搜索好友</string>
    <string name="connect">我的好友</string>
    <string name="discoverable">设置在线</string>
    <string name="back">退出</string>
    <string name="startVideo">开始聊天</string>
    <string name="stopVideo">结束聊天</string>  
</resources>

4、设计布局

在默认的主布局文件activity_main.xml里,添加1个Toolbar控件,其内包含2个水平的TextView控件;在Toolbar控件的下方添加1个ListView控件,用于显示聊天内容;最后在ListView控件的下方添加水平放置的1个EditText控件和一个Button控件。使用垂直线性布局并嵌套水平线性布局实现的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg01">

    <!--新版Android支持的Toolbar,对标题栏布局-->
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/title_left_text"
                style="?android:attr/windowTitleStyle"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_alignParentLeft="true"
                android:layout_weight="1"
                android:gravity="left"
                android:ellipsize="end"
                android:singleLine="true" />
            <TextView
                android:id="@+id/title_right_text"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_alignParentRight="true"
                android:layout_weight="1"
                android:ellipsize="end"
                android:gravity="right"
                android:singleLine="true"
                android:textColor="#fff" />
        </LinearLayout>
    </android.support.v7.widget.Toolbar>

    <ListView android:id="@+id/in"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:stackFromBottom="true"
        android:transcriptMode="alwaysScroll"
        android:layout_weight="1" />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <EditText android:id="@+id/edit_text_out"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="bottom" />
        <Button android:id="@+id/button_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/send"/>
    </LinearLayout>
</LinearLayout>

5.编写蓝牙服务

1、编写用于蓝牙会话的服务组件ChatService,本质上是一个工具类,其文件代码如下:
(仅展示部分代码,完整代码可在github上查看)

/*
    本程序ChatService是蓝牙会话的服务程序
    定义了3个内部类:AcceptThread(接受新连接)、ConnectThread(发出连接)和ConnectedThread (已连接)
*/
public class ChatService {
    //本应用的主Activity组件名称
    private static final String NAME = "BluetoothChat";
    // UUID:通用唯一识别码,是一个128位长的数字,一般用十六进制表示
    //算法的核心思想是结合机器的网卡、当地时间、一个随机数来生成
    //在创建蓝牙连接
    private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
    private final BluetoothAdapter mAdapter;
    private final Handler mHandler;
    private AcceptThread mAcceptThread;
    private ConnectThread mConnectThread;
    private ConnectedThread mConnectedThread;
    private int mState;
    public static final int STATE_NONE = 0;
    public static final int STATE_LISTEN = 1;
    public static final int STATE_CONNECTING = 2;
    public static final int STATE_CONNECTED = 3;

    //构造方法,接收UI主线程传递的对象
    public ChatService(Context context, Handler handler) {
        //构造方法完成蓝牙对象的创建
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = STATE_NONE;
        mHandler = handler;
    }

    private synchronized void setState(int state) {
        mState = state;
        mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
    }

    public synchronized int getState() {
        return mState;
    }

    public synchronized void start() {
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }
        if (mAcceptThread == null) {
            mAcceptThread = new AcceptThread();
            mAcceptThread.start();
        }
        setState(STATE_LISTEN);
    }
...

2、设计供主Activity使用的菜单文件res/menu/optionmenu.xml、选择好友(即已经配对过的蓝牙设备)的界面布局文件devicelist.xml。
**菜单文件option_menu.xml的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android/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>

**选择好友界面的布局文件device_list.xml的代码如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:id="@+id/title_paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_paired_devices"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:paddingLeft="5dp" />
    <ListView android:id="@+id/paired_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1" />
    <TextView android:id="@+id/title_new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/title_other_devices"
        android:visibility="gone"
        android:background="#666"
        android:textColor="#fff"
        android:paddingLeft="5dp" />
    <!--android:visibility="gone"表示不占空间的隐藏,invisible是占空间-->
    <ListView android:id="@+id/new_devices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2" />
    <Button android:id="@+id/button_scan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_scan" />
</LinearLayout>

3、 编写Activity组件DeviceList,实现选取与之会话的蓝牙设备,其文件代码如下:

/*
        本程序供菜单项主界面的选项菜单“我的好友”调用,用于:
        (1)显示已配对的好友列表;
        (2)搜索可配对的好友进行配对
        (3)新选择并配对的蓝牙设备将刷新好友列表
        注意:发现新的蓝牙设备并请求配对时,需要对应接受
        关键技术:动态注册一个广播接收者,处理蓝牙设备扫描的结果
    */
public class DeviceList extends AppCompatActivity {
    private BluetoothAdapter mBtAdapter;
    private ArrayAdapter<String> mPairedDevicesArrayAdapter;
    private ArrayAdapter<String> mNewDevicesArrayAdapter;
    public static String EXTRA_DEVICE_ADDRESS = "device_address";  //Mac地址
    //定义广播接收者,用于处理扫描蓝牙设备后的结果
    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);
                }
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.device_list);
        //在被调用活动里,设置返回结果码
        setResult(Activity.RESULT_CANCELED);
        init();  //活动界面
    }
 ......

4、编写蓝牙会话的主Activity组件程序BluetoothChat:
(不同的SDK API对权限的赋予要求不同,可增加动态赋予权限以及检查权限是否被赋予的函数,具体见github完整代码)

public class BluetoothChat extends AppCompatActivity {

    public static final int MESSAGE_STATE_CHANGE = 1;
    public static final int MESSAGE_READ = 2;
    public static final int MESSAGE_WRITE = 3;
    public static final int MESSAGE_DEVICE_NAME = 4;
    public static final int MESSAGE_TOAST = 5;
    public static final String DEVICE_NAME = "device_name";
    public static final String TOAST = "toast";
    private static final int REQUEST_CONNECT_DEVICE = 1;  //请求连接设备
    private static final int REQUEST_ENABLE_BT = 2;
    private TextView mTitle;
    private ListView mConversationView;
    private EditText mOutEditText;
    private Button mSendButton;
    private String mConnectedDeviceName = null;
    private ArrayAdapter<String> mConversationArrayAdapter;
    private StringBuffer mOutStringBuffer;
    private BluetoothAdapter mBluetoothAdapter = null;
    private ChatService mChatService = null;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportActionBar().hide();  //隐藏标题栏
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
            }
        }
        Toolbar toolbar = findViewById(R.id.toolbar);
        //创建选项菜单
        toolbar.inflateMenu(R.menu.option_menu);
        //选项菜单监听
        toolbar.setOnMenuItemClickListener(new MyMenuItemClickListener());
        mTitle = findViewById(R.id.title_left_text);
        mTitle.setText(R.string.app_name);
        mTitle = findViewById(R.id.title_right_text);
        // 得到本地蓝牙适配器
        checkBlePermission();
        checkBleDevice();
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_LONG).show();
            finish();
            return;
        }
        if (!mBluetoothAdapter.isEnabled()) { //若当前设备蓝牙功能未开启
            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableIntent, REQUEST_ENABLE_BT); //
        } else {
            if (mChatService == null) {
                setupChat();  //创建会话
            }
        }
    }

6.启动项目

三、结果测试

生成apk包,安装到两个真机上进行测试。
1、生成release安装包

2、打开两个真机的蓝牙,进行配对

3、将该程序安装到两个真机上
在两个真机上均点击设置在线,允许蓝牙权限打开。

3、互相发送消息,并确认收到

蓝牙通讯成功! # 四、总结 1、在本次project中,几篇开发文档都给了我莫大的帮助,从中整合出蓝牙通信开发的基本套路,一步步完成了本次项目;另外,如何设计验证环节是检验一个项目是否成功的重要手段。 蓝牙通信的基础开发仅实现发送消息即可,不过如今蓝牙有发送图片、文件等更高级的应用,还等待我在AS开发中探索并实现。依然感谢帮助我的每一篇博客和每一个网站! 2、apk打包参考博客

Android Studio 打包APK(详细版)

更多推荐

## Android Studio 开发(四)--蓝牙通信