فهرست منبع

1.添加数据转换类;
2.添加蓝牙连接服务类;
3.完善主活动页面和搜索页面。

Yumin 6 سال پیش
والد
کامیت
dd9fdc0ec9

+ 3 - 0
app/src/main/AndroidManifest.xml

@@ -10,6 +10,7 @@
     <!-- 如果 Android6.0 蓝牙搜索不到设备,需要补充下面两个权限 -->
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
 
     <application
         android:name=".ctrl.App"
@@ -43,7 +44,9 @@
             android:exported="true">
             <intent-filter>
                 <action android:name="android.bluetooth.device.action.FOUND" />
+                <action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
                 <action android:name="android.bluetooth.device.action.BOND_STATE_CHANGED" />
+                <action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED" />
                 <action android:name="android.bluetooth.adapter.action.SCAN_MODE_CHANGED" />
                 <action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
             </intent-filter>

+ 12 - 4
app/src/main/java/cn/minbb/producttester/adapter/DeviceListAdapter.java

@@ -15,6 +15,8 @@ import java.util.List;
 import butterknife.BindView;
 import butterknife.ButterKnife;
 import cn.minbb.producttester.R;
+import lombok.Getter;
+import lombok.Setter;
 
 public class DeviceListAdapter extends BaseAdapter {
 
@@ -85,11 +87,17 @@ public class DeviceListAdapter extends BaseAdapter {
 
     public static class ItemBean {
 
+        @Getter
+        @Setter
         public BluetoothDevice device;
-        String deviceName;
-        String deviceAddress;
-        String deviceStatus;
-        String deviceTip;
+        @Getter
+        @Setter
+        public String deviceName;
+        @Getter
+        @Setter
+        public String deviceAddress;
+        private String deviceStatus;
+        private String deviceTip;
 
         public ItemBean(BluetoothDevice device) {
             this.device = device;

+ 0 - 6
app/src/main/java/cn/minbb/producttester/ctrl/App.java

@@ -37,10 +37,6 @@ public class App extends Application {
     // 时间格式
     private static SimpleDateFormat simpleDateFormat = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance();
 
-    // 蓝牙适配器
-    @Getter
-    @Setter
-    private BluetoothAdapter bluetoothAdapter;
     @Getter
     @Setter
     private BluetoothDevice bluetoothDevice = null;
@@ -63,8 +59,6 @@ public class App extends Application {
 
         // 初始化字体图标模块
         Iconify.with(new FontAwesomeModule());
-
-        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     }
 
     @Override

+ 442 - 0
app/src/main/java/cn/minbb/producttester/ctrl/BluetoothChatService.java

@@ -0,0 +1,442 @@
+package cn.minbb.producttester.ctrl;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.UUID;
+
+/**
+ * 实现所有设置和管理与其它蓝牙设备连接的工作。
+ * 一个线程监听传入连接,一个线程与设备进行连接,一个线程负责连接后的数据传输。
+ */
+public class BluetoothChatService {
+
+    // Debugging
+    private static final String TAG = "BluetoothChatService";
+    private static final boolean D = true;
+    // 创建服务器套接字时SDP记录的名称
+    private static final String NAME = "BluetoothChat";
+    // 该应用的唯一UUID
+    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
+    // 成员变量
+    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;       // we're doing nothing
+    public static final int STATE_LISTEN = 1;     // 现在正在侦听传入连接
+    public static final int STATE_CONNECTING = 2; // 现在启动传出连接
+    public static final int STATE_CONNECTED = 3;  // 现在连接到远程设备
+
+    // BluetoothChatService Handler 的消息类型
+    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 int delayTime;
+
+    public void setDelayTime(int delayTime) {
+        this.delayTime = delayTime;
+    }
+
+    /**
+     * 构造函数。准备新的BluetoothChat会话。
+     *
+     * @param context The UI Activity Context
+     * @param handler A Handler to send messages back to the UI Activity
+     */
+    public BluetoothChatService(Context context, Handler handler) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mState = STATE_NONE;
+        mHandler = handler;
+        setDelayTime(100);
+    }
+
+    /**
+     * 设置聊天连接的当前状态
+     *
+     * @param state 定义当前连接状态的整数
+     */
+    private synchronized void setState(int state) {
+        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
+        mState = state;
+        // 将新状态提供给处理程序,以便 UI 活动可以更新
+        mHandler.obtainMessage(MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
+    }
+
+    /**
+     * 返回当前连接状态。
+     */
+    public synchronized int getState() {
+        return mState;
+    }
+
+    /**
+     * 启动聊天服务。特别地启动 AcceptThread 以在侦听(服务器)模式下开始会话。
+     * 由 Activity onResume() 调用
+     */
+    public synchronized void start() {
+        if (D) Log.d(TAG, "start");
+        // 取消尝试建立连接的任何线程
+        if (mConnectThread != null) {
+            mConnectThread.cancel();
+            mConnectThread = null;
+        }
+        // 取消当前运行连接的任何线程
+        if (mConnectedThread != null) {
+            mConnectedThread.cancel();
+            mConnectedThread = null;
+        }
+        // 启动线程以监听 BluetoothServerSocket
+        if (mAcceptThread == null) {
+            mAcceptThread = new AcceptThread();
+            mAcceptThread.start();
+        }
+        setState(STATE_LISTEN);
+    }
+
+    /**
+     * 启动 ConnectThread 以启动与远程设备的连接。
+     *
+     * @param device 要连接的 BluetoothDevice
+     */
+    public synchronized void connect(BluetoothDevice device) {
+        if (D) Log.d(TAG, "connect to: " + device);
+        // 取消尝试建立连接的任何线程
+        if (mState == STATE_CONNECTING) {
+            if (mConnectThread != null) {
+                mConnectThread.cancel();
+                mConnectThread = null;
+            }
+        }
+        // 取消当前运行连接的任何线程
+        if (mConnectedThread != null) {
+            mConnectedThread.cancel();
+            mConnectedThread = null;
+        }
+        // 启动线程以与给定设备连接
+        mConnectThread = new ConnectThread(device);
+        mConnectThread.start();
+        setState(STATE_CONNECTING);
+    }
+
+    /**
+     * 启动 ConnectedThread 以开始管理蓝牙连接
+     *
+     * @param socket 在其上进行连接的 BluetoothSocket
+     * @param device 已连接的 BluetoothDevice
+     */
+    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
+        if (D) Log.d(TAG, "connected");
+        // 取消完成连接的线程
+        if (mConnectThread != null) {
+            mConnectThread.cancel();
+            mConnectThread = null;
+        }
+        // 取消当前运行连接的任何线程
+        if (mConnectedThread != null) {
+            mConnectedThread.cancel();
+            mConnectedThread = null;
+        }
+        // 取消接受线程,因为我们只想连接到一个设备
+        if (mAcceptThread != null) {
+            mAcceptThread.cancel();
+            mAcceptThread = null;
+        }
+        // 启动线程以管理连接并执行传输
+        mConnectedThread = new ConnectedThread(socket);
+        mConnectedThread.start();
+        // 将连接的设备的名称发送回UI活动
+        Message msg = mHandler.obtainMessage(MESSAGE_DEVICE_NAME);
+        Bundle bundle = new Bundle();
+        bundle.putString(BluetoothChatService.DEVICE_NAME, device.getName());
+        msg.setData(bundle);
+        mHandler.sendMessage(msg);
+        setState(STATE_CONNECTED);
+    }
+
+    /**
+     * Stop all threads
+     */
+    public synchronized void stop() {
+        if (D) Log.d(TAG, "stop");
+        if (mConnectThread != null) {
+            mConnectThread.cancel();
+            mConnectThread = null;
+        }
+        if (mConnectedThread != null) {
+            mConnectedThread.cancel();
+            mConnectedThread = null;
+        }
+        if (mAcceptThread != null) {
+            mAcceptThread.cancel();
+            mAcceptThread = null;
+        }
+        setState(STATE_NONE);
+    }
+
+    /**
+     * 以线程锁(不同步)方式写入 ConnectedThread
+     *
+     * @param out 要写入的字节
+     * @see ConnectedThread#write(byte[])
+     */
+    public void write(byte[] out) {
+        // 创建临时对象
+        ConnectedThread r;
+        // 同步 ConnectedThread 的副本
+        synchronized (this) {
+            if (mState != STATE_CONNECTED) return;
+            r = mConnectedThread;
+        }
+        // 执行写入不同步
+        r.write(out);
+    }
+
+    /**
+     * 指示连接尝试失败并通知 UI 活动.
+     */
+    private void connectionFailed() {
+        setState(STATE_NONE);
+        // 将失败消息发送回活动
+        Message msg = mHandler.obtainMessage(MESSAGE_TOAST);
+        Bundle bundle = new Bundle();
+        bundle.putString(BluetoothChatService.TOAST, "无法连接设备");
+        msg.setData(bundle);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * 指示连接已丢失并通知UI活动
+     */
+    private void connectionLost() {
+        setState(STATE_NONE);
+        // 将失败消息发送回活动
+        Message msg = mHandler.obtainMessage(MESSAGE_TOAST);
+        Bundle bundle = new Bundle();
+        bundle.putString(BluetoothChatService.TOAST, "丢失设备连接");
+        msg.setData(bundle);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * 此线程在侦听传入连接时运行。它的行为像一个服务器端。
+     * 它运行直到接受连接(或直到取消).
+     */
+    private class AcceptThread extends Thread {
+
+        // 本地服务器套接字
+        private final BluetoothServerSocket mmServerSocket;
+
+        public AcceptThread() {
+            BluetoothServerSocket tmp = null;
+            // 创建一个新的侦听服务器套接字
+            try {
+                tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
+            } catch (IOException e) {
+                Log.e(TAG, "listen() failed", e);
+            }
+            mmServerSocket = tmp;
+        }
+
+        public void run() {
+            if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
+            setName("AcceptThread");
+            BluetoothSocket socket = null;
+            // 如果我们没有连接,请监听服务器套接字
+            while (mState != STATE_CONNECTED) {
+                try {
+                    // 这是一个阻塞调用,只会在成功的连接或异常返回
+                    socket = mmServerSocket.accept();
+                } catch (IOException e) {
+                    Log.e(TAG, "accept() failed", e);
+                    break;
+                }
+                // 如果连接被接受
+                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) {
+                                    Log.e(TAG, "Could not close unwanted socket", e);
+                                }
+                                break;
+                            default:
+                        }
+                    }
+                }
+            }
+            if (D) Log.i(TAG, "END mAcceptThread");
+        }
+
+        public void cancel() {
+            if (D) Log.d(TAG, "cancel " + this);
+            try {
+                mmServerSocket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "close() of server failed", e);
+            }
+        }
+    }
+
+    /**
+     * 尝试与设备建立传出连接时,此线程运行。类似于一个客户端与它直通; 连接成功或失败。
+     */
+    private class ConnectThread extends Thread {
+
+        private final BluetoothSocket mmSocket;
+        private final BluetoothDevice mmDevice;
+
+        public ConnectThread(BluetoothDevice device) {
+            mmDevice = device;
+            BluetoothSocket tmp = null;
+            // 获取与给定的蓝牙设备的连接的 BluetoothSocket
+            try {
+                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
+            } catch (IOException e) {
+                Log.e(TAG, "create() failed", e);
+            }
+            mmSocket = tmp;
+        }
+
+        public void run() {
+            Log.i(TAG, "BEGIN mConnectThread");
+            setName("ConnectThread");
+            // 始终取消发现,因为它会减慢连接速度
+            mAdapter.cancelDiscovery();
+            // 连接到 BluetoothSocket
+            try {
+                // 这是一个阻塞调用,只会在成功的连接或异常返回
+                mmSocket.connect();
+            } catch (IOException e) {
+                connectionFailed();
+                // 关闭套接字
+                try {
+                    mmSocket.close();
+                } catch (IOException e2) {
+                    Log.e(TAG, "unable to close() socket during connection failure", e2);
+                }
+                // 启动服务以重新启动侦听模式
+                BluetoothChatService.this.start();
+                return;
+            }
+            // 重置 ConnectThread,因为我们完成了
+            synchronized (BluetoothChatService.this) {
+                mConnectThread = null;
+            }
+            // 启动连接的线程
+            connected(mmSocket, mmDevice);
+        }
+
+        public void cancel() {
+            try {
+                mmSocket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "close() of connect socket failed", e);
+            }
+        }
+    }
+
+    /**
+     * 此线程在与远程设备的连接期间运行。
+     * 它处理所有传入和传出传输。
+     */
+    private class ConnectedThread extends Thread {
+
+        private final BluetoothSocket mmSocket;
+        private final InputStream mmInStream;
+        private final OutputStream mmOutStream;
+
+        public ConnectedThread(BluetoothSocket socket) {
+            Log.d(TAG, "create ConnectedThread");
+            mmSocket = socket;
+            InputStream tmpIn = null;
+            OutputStream tmpOut = null;
+            // 获取 BluetoothSocket 输入和输出流
+            try {
+                tmpIn = socket.getInputStream();
+                tmpOut = socket.getOutputStream();
+            } catch (IOException e) {
+                Log.e(TAG, "temp sockets not created", e);
+            }
+            mmInStream = tmpIn;
+            mmOutStream = tmpOut;
+        }
+
+        public void run() {
+            Log.i(TAG, "BEGIN mConnectedThread");
+            byte[] buffer = new byte[1024];
+            int bytes;
+            while (true) {
+                try {
+                    while (mmInStream.available() == 0) {
+                    }
+                    try {
+                        // 当有数据流入时,线程休眠一段时间,默认 100ms
+                        Thread.sleep(delayTime);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    // 从字节流中读取数据填充到字节数组,返回读取数据的长度
+                    bytes = mmInStream.read(buffer);
+                    // 将消息传回主界面
+                    mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget();
+                } catch (IOException e) {
+                    Log.e(TAG, "disconnected", e);
+                    connectionLost();
+                    break;
+                }
+            }
+        }
+
+        /**
+         * 写入连接的 OutStream。
+         *
+         * @param buffer The bytes to write
+         */
+        public void write(byte[] buffer) {
+            try {
+                mmOutStream.write(buffer);
+                // 将发送的消息共享回 UI 活动
+                mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
+            } catch (IOException e) {
+                Log.e(TAG, "Exception during write", e);
+            }
+        }
+
+        public void cancel() {
+            try {
+                mmSocket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "close() of connect socket failed", e);
+            }
+        }
+    }
+}

+ 19 - 5
app/src/main/java/cn/minbb/producttester/receiver/BluetoothStatusReceiver.java

@@ -1,27 +1,31 @@
 package cn.minbb.producttester.receiver;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Handler;
+import android.widget.Toast;
 
 import java.lang.reflect.Method;
 
 import cn.minbb.producttester.ctrl.App;
+import cn.minbb.producttester.views.ScanDeviceActivity;
 
 public class BluetoothStatusReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
         String action = intent.getAction();
-        System.out.println("广播接收器 action = " + action);
+        System.out.println("接收广播 action = " + action);
         if (BluetoothDevice.ACTION_FOUND.equals(action)) {
             // 获取查找到的蓝牙设备
             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            System.out.println("蓝牙设备 Found = " + device.getName());
+            System.out.println("发现蓝牙设备 = " + device.getName());
             // 如果查找到的设备符合要连接的设备,处理
-            if (device.getName().equalsIgnoreCase(device.getName())) {
+            if (device.getName().equalsIgnoreCase("STOP")) {
                 // 搜索蓝牙设备的过程占用资源比较多,一旦找到需要连接的设备后需要及时关闭搜索
-                App.getApp().getBluetoothAdapter().cancelDiscovery();
+                BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
                 // 获取蓝牙设备的连接状态
                 switch (device.getBondState()) {
                     // 未配对
@@ -41,7 +45,7 @@ public class BluetoothStatusReceiver extends BroadcastReceiver {
                 }
             }
         } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
-            // 状态改变的广播
+            // 绑定状态改变的广播
             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
             if (device.getName().equalsIgnoreCase(device.getName())) {
                 switch (device.getBondState()) {
@@ -56,6 +60,16 @@ public class BluetoothStatusReceiver extends BroadcastReceiver {
                         break;
                 }
             }
+        } else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) {
+            // 配对请求
+            Toast.makeText(App.getApp(), "接收到配对请求", Toast.LENGTH_SHORT).show();
+        } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+            // 蓝牙状态更改
+            if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
+                Toast.makeText(App.getApp(), "蓝牙已关闭", Toast.LENGTH_SHORT).show();
+            }
+        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
+            // 搜索结束
         }
     }
 }

+ 48 - 0
app/src/main/java/cn/minbb/producttester/utils/DataTranslation.java

@@ -0,0 +1,48 @@
+package cn.minbb.producttester.utils;
+
+public class DataTranslation {
+
+    // 将字节数组转化为16进制字符串,确定长度
+    public static String bytesToHexString(byte[] bytes, int a) {
+        String result = "";
+        for (int i = 0; i < a; i++) {
+            // 将高 24 位置 0
+            String hexString = Integer.toHexString(bytes[i] & 0xFF);
+            if (hexString.length() == 1) {
+                hexString = '0' + hexString;
+            }
+            result += hexString.toUpperCase();
+        }
+        return result;
+    }
+
+    // 将字节数组转化为16进制字符串,不确定长度
+    public static String bytes2HexString(byte[] b) {
+        String ret = "";
+        for (int i = 0; i < b.length; i++) {
+            // 将高 24 位置 0
+            String hex = Integer.toHexString(b[i] & 0xFF);
+            if (hex.length() == 1) {
+                hex = '0' + hex;
+            }
+            ret += hex.toUpperCase();
+        }
+        return ret;
+    }
+
+    // 将16进制字符串转化为字节数组
+    public static byte[] hexString2Bytes(String paramString) {
+        int i = paramString.length() / 2;
+        byte[] arrayOfByte = new byte[i];
+        int j = 0;
+        while (true) {
+            if (j >= i) {
+                return arrayOfByte;
+            }
+            int k = 1 + j * 2;
+            int l = k + 1;
+            arrayOfByte[j] = (byte) (0xFF & Integer.decode("0x" + paramString.substring(j * 2, k) + paramString.substring(k, l)).intValue());
+            ++j;
+        }
+    }
+}

+ 123 - 2
app/src/main/java/cn/minbb/producttester/views/MainActivity.java

@@ -4,9 +4,12 @@ import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
 import android.support.annotation.NonNull;
 import android.support.design.widget.BottomNavigationView;
 import android.support.v4.view.ViewPager;
+import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.app.AppCompatDelegate;
 import android.support.v7.widget.Toolbar;
@@ -14,9 +17,11 @@ import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.joanzapata.iconify.widget.IconTextView;
 
+import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -25,6 +30,7 @@ import butterknife.ButterKnife;
 import butterknife.OnClick;
 import cn.minbb.producttester.R;
 import cn.minbb.producttester.adapter.MainPagerAdapter;
+import cn.minbb.producttester.ctrl.BluetoothChatService;
 
 public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {
 
@@ -38,9 +44,15 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationV
     IconTextView icon;
     @BindView(R.id.title)
     TextView title;
+    @BindView(R.id.device)
+    TextView device;
+    @BindView(R.id.noDevice)
+    IconTextView noDevice;
 
     private List<View> views = new ArrayList<>(3);
 
+    private BluetoothChatService chatService = null;
+
     static {
         AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
     }
@@ -51,11 +63,34 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationV
         setContentView(R.layout.activity_main);
         ButterKnife.bind(this);
 
+        // 初始化蓝牙连接服务
+        chatService = new BluetoothChatService(this, mHandler);
+
         initView();
         setListener();
         loadData();
     }
 
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (chatService != null) {
+            // 只有状态是 STATE_NONE,我们知道我们还没有启动蓝牙
+            if (chatService.getState() == BluetoothChatService.STATE_NONE) {
+                // 启动 BluetoothChat 服务
+                chatService.start();
+            }
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mHandler.removeCallbacksAndMessages(null);
+        // 停止蓝牙通信连接服务
+        if (chatService != null) chatService.stop();
+    }
+
     @SuppressLint("InflateParams")
     private void initView() {
         LayoutInflater layoutInflater = LayoutInflater.from(this);
@@ -105,12 +140,21 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationV
         context.startActivity(starter);
     }
 
-    @OnClick(R.id.noDevice)
+    @OnClick({R.id.device, R.id.noDevice})
     public void onViewClicked(View view) {
         switch (view.getId()) {
+            case R.id.device:
+                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
+                builder.setTitle("断开连接")
+                        .setMessage("确定断开设备的蓝牙连接吗?")
+                        .setNegativeButton("取消", null)
+                        .setPositiveButton("确定", (dialog, which) -> chatService.stop())
+                        .create().show();
+                break;
             case R.id.noDevice:
-                ScanDeviceActivity.start(MainActivity.this);
+                startActivityForResult(new Intent(MainActivity.this, ScanDeviceActivity.class), 0);
                 break;
+            default:
         }
     }
 
@@ -135,4 +179,81 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationV
         }
         return false;
     }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
+        builder.setTitle("退出")
+                .setMessage("确定断开设备的蓝牙连接并退出程序吗?")
+                .setNegativeButton("取消", null)
+                .setPositiveButton("确定", (dialog, which) -> finish())
+                .create().show();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        switch (requestCode) {
+            case 0:
+                if (resultCode == RESULT_OK) {
+                }
+        }
+    }
+
+    @SuppressLint("HandlerLeak")
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case BluetoothChatService.MESSAGE_STATE_CHANGE:
+                    System.err.println("************************ MESSAGE_STATE_CHANGE = " + msg.arg1);
+                    switch (msg.arg1) {
+                        case BluetoothChatService.STATE_CONNECTED:
+                            // 已连接
+                            Toast.makeText(getApplicationContext(), "已连接 +", Toast.LENGTH_SHORT).show();
+                            noDevice.setVisibility(View.GONE);
+                            break;
+                        case BluetoothChatService.STATE_CONNECTING:
+                            Toast.makeText(getApplicationContext(), "连接中 +", Toast.LENGTH_SHORT).show();
+                            break;
+                        case BluetoothChatService.STATE_LISTEN:
+                            Toast.makeText(getApplicationContext(), "侦听中 +", Toast.LENGTH_SHORT).show();
+                            break;
+                        case BluetoothChatService.STATE_NONE:
+                            // 无连接
+                            noDevice.setVisibility(View.VISIBLE);
+                            break;
+                    }
+                    break;
+                case BluetoothChatService.MESSAGE_READ:
+                    byte[] readBuf = (byte[]) msg.obj;
+                    String readMessage = null;
+                    try {
+                        readMessage = new String(readBuf, 0, msg.arg1, "GBK");
+                    } catch (UnsupportedEncodingException e) {
+                        e.printStackTrace();
+                    }
+                    System.err.println("/" + readMessage);
+                    //检错误码计算函数
+//                    if (inhex == true) {
+//                        String readMessage = " " + Data_syn.bytesToHexString(readBuf, msg.arg1);
+//                        fmsg += readMessage;
+//                        mConversationView.append(readMessage);
+//                        // 接收计数,更显UI
+//                        countin += readMessage.length() / 2;
+//                        incount.setText("" + countin);
+//                    } else if (inhex == false) {
+//                    }
+                    break;
+                case BluetoothChatService.MESSAGE_DEVICE_NAME:
+                    String connectedDeviceName = msg.getData().getString(BluetoothChatService.DEVICE_NAME);
+                    Toast.makeText(getApplicationContext(), "连接到 + " + connectedDeviceName, Toast.LENGTH_SHORT).show();
+                    break;
+                case BluetoothChatService.MESSAGE_TOAST:
+                    Toast.makeText(getApplicationContext(), msg.getData().getString(BluetoothChatService.TOAST), Toast.LENGTH_SHORT).show();
+                    break;
+            }
+        }
+    };
 }

+ 186 - 78
app/src/main/java/cn/minbb/producttester/views/ScanDeviceActivity.java

@@ -1,9 +1,9 @@
 package cn.minbb.producttester.views;
 
+import android.annotation.SuppressLint;
 import android.app.ProgressDialog;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothSocket;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -13,30 +13,27 @@ import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Message;
 import android.support.annotation.NonNull;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.app.AppCompatDelegate;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
 import android.widget.ListView;
 import android.widget.Toast;
 
-import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-import java.util.UUID;
-import java.util.function.Consumer;
 
 import butterknife.BindView;
 import butterknife.ButterKnife;
 import cn.minbb.producttester.R;
 import cn.minbb.producttester.adapter.DeviceListAdapter;
 import cn.minbb.producttester.ctrl.App;
+import cn.minbb.producttester.ctrl.BluetoothChatService;
 
 public class ScanDeviceActivity extends AppCompatActivity {
 
@@ -45,12 +42,14 @@ public class ScanDeviceActivity extends AppCompatActivity {
 
     private ProgressDialog dialog;
 
-    private BluetoothAdapter adapter = App.getApp().getBluetoothAdapter();
+    private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     private List<DeviceListAdapter.ItemBean> deviceItemBeanList = new ArrayList<>();
     private DeviceListAdapter deviceListAdapter;
     private BluetoothDevice selectDevice;
     private int selectIndex;
 
+    private BluetoothChatService chatService = null;
+
     static {
         AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
     }
@@ -71,18 +70,18 @@ public class ScanDeviceActivity extends AppCompatActivity {
         this.registerReceiver(receiver, intentFilter);
 
         // 检查设备是否支持蓝牙
-        if (adapter == null) {
+        if (bluetoothAdapter == null) {
             // 设备不支持蓝牙
             Toast.makeText(ScanDeviceActivity.this, "设备不支持蓝牙", Toast.LENGTH_LONG).show();
             ScanDeviceActivity.this.finish();
         } else {
-            if (!adapter.isEnabled()) {
+            if (!bluetoothAdapter.isEnabled()) {
                 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                 // 设置蓝牙可见性,最多 300 秒
                 intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 60);
                 ScanDeviceActivity.this.startActivityForResult(intent, 0);
             }
-            Set<BluetoothDevice> devices = adapter.getBondedDevices();
+            Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
             for (BluetoothDevice device : devices) {
                 deviceItemBeanList.add(new DeviceListAdapter.ItemBean(device));
             }
@@ -90,20 +89,31 @@ public class ScanDeviceActivity extends AppCompatActivity {
             deviceList.setAdapter(deviceListAdapter);
         }
 
+        // 初始化蓝牙连接服务
+        chatService = new BluetoothChatService(this, mHandler);
+
         initView();
         setListener();
         loadData();
     }
 
+    @Override
+    protected void onResume() {
+        super.onResume();
+//        if (chatService != null) {
+//            // 只有状态是 STATE_NONE,我们知道我们还没有启动蓝牙
+//            if (chatService.getState() == BluetoothChatService.STATE_NONE) {
+//                // 启动 BluetoothChat 服务
+//                chatService.start();
+//            }
+//        }
+    }
+
     @Override
     protected void onDestroy() {
         super.onDestroy();
         this.unregisterReceiver(receiver);
-    }
-
-    public static void start(Context context) {
-        Intent starter = new Intent(context, ScanDeviceActivity.class);
-        context.startActivity(starter);
+        mHandler.removeCallbacksAndMessages(null);
     }
 
     private void initView() {
@@ -111,37 +121,31 @@ public class ScanDeviceActivity extends AppCompatActivity {
     }
 
     private void setListener() {
-        deviceList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
-                selectIndex = i;
-                selectDevice = deviceItemBeanList.get(i).device;
-                switch (selectDevice.getBondState()) {
-                    case BluetoothDevice.BOND_NONE:
-                        // 未配对 - 尝试配对
-                        try {
-                            Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
-                            createBondMethod.invoke(selectDevice);
-                        } catch (Exception e) {
-                            e.printStackTrace();
-                        }
-                        break;
-                    case BluetoothDevice.BOND_BONDING:
-                        // 配对中
-                        break;
-                    case BluetoothDevice.BOND_BONDED:
-                        // 已配对 - 尝试连接
-                        // 固定的 UUID
-                        String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
-                        UUID uuid = UUID.fromString(SPP_UUID);
-                        try {
-                            BluetoothSocket socket = selectDevice.createRfcommSocketToServiceRecord(uuid);
-                            socket.connect();
-                        } catch (IOException e) {
-                            e.printStackTrace();
-                        }
-                        break;
-                }
+        deviceList.setOnItemClickListener((adapterView, view, i, l) -> {
+            selectIndex = i;
+            selectDevice = deviceItemBeanList.get(i).device;
+            switch (selectDevice.getBondState()) {
+                case BluetoothDevice.BOND_NONE:
+                    // 未配对 - 尝试配对
+                    try {
+                        Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
+                        createBondMethod.invoke(selectDevice);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                    break;
+                case BluetoothDevice.BOND_BONDING:
+                    // 配对中
+                    break;
+                case BluetoothDevice.BOND_BONDED:
+                    showConnectDeviceProgress(true);
+                    // 已配对 - 尝试连接到设备
+                    // BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
+                    // 设备的 MAC 地址
+                    BluetoothDevice device = bluetoothAdapter.getRemoteDevice(selectDevice.getAddress());
+                    chatService.connect(device);
+                    break;
+                default:
             }
         });
     }
@@ -162,7 +166,7 @@ public class ScanDeviceActivity extends AppCompatActivity {
             dialog.setMessage("搜索设备中······");
             dialog.setCancelable(false);
             dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "取消", (dialogInterface, i) -> {
-                adapter.cancelDiscovery();
+                bluetoothAdapter.cancelDiscovery();
                 dialog.dismiss();
             });
             dialog.setOnDismissListener(dialog -> {
@@ -172,7 +176,7 @@ public class ScanDeviceActivity extends AppCompatActivity {
                         count++;
                     }
                 }
-                Toast.makeText(ScanDeviceActivity.this, "查找到 " + count + " 个蓝牙设备", Toast.LENGTH_SHORT).show();
+                Toast.makeText(getApplicationContext(), "查找到 " + count + " 个蓝牙设备", Toast.LENGTH_SHORT).show();
             });
         }
         if (show) {
@@ -198,6 +202,21 @@ public class ScanDeviceActivity extends AppCompatActivity {
         }
     }
 
+    private void showConnectDeviceProgress(boolean show) {
+        if (dialog == null) {
+            dialog = new ProgressDialog(this);
+            dialog.setTitle("连接");
+            dialog.setMessage("设备连接中······");
+            dialog.setCancelable(false);
+        }
+        if (show) {
+            dialog.show();
+        } else {
+            dialog.dismiss();
+            dialog = null;
+        }
+    }
+
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         menu.add(0, 0, 0, "提示").setIcon(R.drawable.ic_info_outline_white_24dp).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
@@ -230,22 +249,22 @@ public class ScanDeviceActivity extends AppCompatActivity {
                 deviceItemBeanList.removeAll(unboundItemBeanList);
                 deviceListAdapter.notifyDataSetChanged();
                 // 打开蓝牙
-                if (!adapter.isEnabled()) {
+                if (!bluetoothAdapter.isEnabled()) {
                     Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                     // 设置蓝牙可见性,最多 300 秒
                     intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
                     ScanDeviceActivity.this.startActivity(intent);
                 }
                 // 寻找蓝牙设备,Android 会将查找到的设备以广播形式发出去
-                if (!adapter.isDiscovering()) {
-                    adapter.startDiscovery();
+                if (!bluetoothAdapter.isDiscovering()) {
+                    bluetoothAdapter.startDiscovery();
                 }
                 // 弹出是否允许扫描蓝牙设备的选择对话框
                 // Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
                 // startActivityForResult(intent, 1);
                 new Handler().postDelayed(() -> runOnUiThread(() -> {
                     showSearchDeviceProgress(false);
-                    adapter.cancelDiscovery();
+                    bluetoothAdapter.cancelDiscovery();
                 }), 20000);
                 break;
         }
@@ -291,6 +310,98 @@ public class ScanDeviceActivity extends AppCompatActivity {
         }
     }
 
+    @SuppressLint("HandlerLeak")
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case BluetoothChatService.MESSAGE_STATE_CHANGE:
+                    System.err.println("MESSAGE_STATE_CHANGE = " + msg.arg1);
+                    switch (msg.arg1) {
+                        case BluetoothChatService.STATE_CONNECTED:
+                            showConnectDeviceProgress(false);
+                            break;
+                        case BluetoothChatService.STATE_CONNECTING:
+                            Toast.makeText(getApplicationContext(), "连接中", Toast.LENGTH_SHORT).show();
+                            break;
+                        case BluetoothChatService.STATE_LISTEN:
+                            Toast.makeText(getApplicationContext(), "侦听中", Toast.LENGTH_SHORT).show();
+                            break;
+                        case BluetoothChatService.STATE_NONE:
+                            Toast.makeText(getApplicationContext(), "无连接", Toast.LENGTH_SHORT).show();
+                            showConnectDeviceProgress(false);
+                            break;
+                    }
+                    break;
+                case BluetoothChatService.MESSAGE_WRITE:
+                    byte[] writeBuf = (byte[]) msg.obj;
+                    // 自动发送
+                    if (false) {
+                        // 自动发送模块
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                            }
+                        }, 1000);
+                    } else if (false) {
+                        mHandler.removeCallbacks(new Runnable() {
+                            @Override
+                            public void run() {
+                            }
+                        });
+                    }
+                    // 发送计数
+//                    if (outhex == true) {
+//                        String writeMessage = Data_syn.Bytes2HexString(writeBuf);
+//                        countout += writeMessage.length() / 2;
+//                        outcount.setText("" + countout);
+//                    } else if (outhex == false) {
+//                        String writeMessage = null;
+//                        try {
+//                            writeMessage = new String(writeBuf, "GBK");
+//                        } catch (UnsupportedEncodingException e1) {
+//                            e1.printStackTrace();
+//                        }
+//                        countout += writeMessage.length();
+//                        outcount.setText("" + countout);
+//                    }
+                    break;
+                case BluetoothChatService.MESSAGE_READ:
+                    byte[] readBuf = (byte[]) msg.obj;
+                    //检错误码计算函数
+//                    if (inhex == true) {
+//                        String readMessage = " " + Data_syn.bytesToHexString(readBuf, msg.arg1);
+//                        fmsg += readMessage;
+//                        mConversationView.append(readMessage);
+//                        // 接收计数,更显UI
+//                        countin += readMessage.length() / 2;
+//                        incount.setText("" + countin);
+//                    } else if (inhex == false) {
+//                        String readMessage = null;
+//                        try {
+//                            readMessage = new String(readBuf, 0, msg.arg1, "GBK");
+//                        } catch (UnsupportedEncodingException e) {
+//                            e.printStackTrace();
+//                        }
+//                        fmsg += readMessage;
+//                        mConversationView.append(readMessage);
+//                        // 接收计数,更新UI
+//                        countin += readMessage.length();
+//                        incount.setText("" + countin);
+//                    }
+                    break;
+                case BluetoothChatService.MESSAGE_DEVICE_NAME:
+                    String connectedDeviceName = msg.getData().getString(BluetoothChatService.DEVICE_NAME);
+                    Toast.makeText(getApplicationContext(), "连接到 " + connectedDeviceName, Toast.LENGTH_SHORT).show();
+                    ScanDeviceActivity.this.finish();
+                    break;
+                case BluetoothChatService.MESSAGE_TOAST:
+                    Toast.makeText(getApplicationContext(), msg.getData().getString(BluetoothChatService.TOAST), Toast.LENGTH_SHORT).show();
+                    break;
+            }
+        }
+    };
+
     private BroadcastReceiver receiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -298,12 +409,10 @@ public class ScanDeviceActivity extends AppCompatActivity {
             if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                 // 获取查找到的蓝牙设备
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
                 if (!deviceItemBeanList.contains(new DeviceListAdapter.ItemBean(device)) && device.getName() != null && device.getAddress() != null) {
                     deviceItemBeanList.add(new DeviceListAdapter.ItemBean(device));
                     deviceListAdapter.notifyDataSetChanged();
                 }
-
                 // 如果查找到的设备符合要连接的设备,处理
 //                if (device.getName().equalsIgnoreCase(device.getName())) {
 //                    // 搜索蓝牙设备的过程占用资源比较多,一旦找到需要连接的设备后需要及时关闭搜索
@@ -329,36 +438,35 @@ public class ScanDeviceActivity extends AppCompatActivity {
             } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
                 // 状态改变的广播
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                if (device.getName().equalsIgnoreCase(device.getName())) {
-                    switch (device.getBondState()) {
-                        case BluetoothDevice.BOND_NONE:
-                            // 取消配对
-                            System.out.println("取消配对");
-                            showMatchDeviceProgress(false);
-                            break;
-                        case BluetoothDevice.BOND_BONDING:
-                            // 正在配对
-                            System.out.println("正在配对");
-                            showMatchDeviceProgress(true);
-                            break;
-                        case BluetoothDevice.BOND_BONDED:
-                            // 完成配对
-                            System.out.println("完成配对");
-                            showMatchDeviceProgress(false);
-                            deviceItemBeanList.set(selectIndex, new DeviceListAdapter.ItemBean(device));
-                            deviceListAdapter.notifyDataSetChanged();
-                            Toast.makeText(ScanDeviceActivity.this, "和设备 " + device.getName() + " 配对成功", Toast.LENGTH_SHORT).show();
-                            break;
-                    }
+                switch (device.getBondState()) {
+                    case BluetoothDevice.BOND_NONE:
+                        // 取消配对
+                        System.out.println("取消配对");
+                        showMatchDeviceProgress(false);
+                        break;
+                    case BluetoothDevice.BOND_BONDING:
+                        // 正在配对
+                        System.out.println("正在配对");
+                        showMatchDeviceProgress(true);
+                        break;
+                    case BluetoothDevice.BOND_BONDED:
+                        // 完成配对
+                        System.out.println("完成配对");
+                        showMatchDeviceProgress(false);
+                        deviceItemBeanList.set(selectIndex, new DeviceListAdapter.ItemBean(device));
+                        deviceListAdapter.notifyDataSetChanged();
+                        Toast.makeText(getApplicationContext(), "和设备 " + device.getName() + " 配对成功", Toast.LENGTH_SHORT).show();
+                        break;
+                    default:
                 }
             } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                 new Handler().postDelayed(() -> runOnUiThread(() -> {
-                    if (!adapter.isEnabled()) {
-                        Toast.makeText(ScanDeviceActivity.this, "蓝牙已关闭", Toast.LENGTH_SHORT).show();
+                    if (!bluetoothAdapter.isEnabled()) {
+                        Toast.makeText(getApplicationContext(), "蓝牙已关闭", Toast.LENGTH_SHORT).show();
                         ScanDeviceActivity.this.finish();
                     } else {
                         deviceItemBeanList.clear();
-                        Set<BluetoothDevice> devices = adapter.getBondedDevices();
+                        Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
                         for (BluetoothDevice device : devices) {
                             deviceItemBeanList.add(new DeviceListAdapter.ItemBean(device));
                         }