记设备互联开发中遇到的问题

226 阅读9分钟

一、需要了解和学习的前置知识

1、启动事件并获得返回值:

用于一般在使用相机,获取通讯录电话信息,检查权限等获取操作结果的回调的情况。

// 必须在Activity中才可以使用
// 第一个参数是我们需要处理第二个参数回调的数据的contract类
// 第二个参数是需要获取的回调值
registerForActivityResult(contract:ActivityResultContracts,callback)

i、在返回Activity返回后获取结果:

class MainActivity : AppCompatActivity() {

    private final val TAG = javaClass.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 触发
        findViewById<Button>(R.id.btn).setOnClickListener {
            val intent = Intent(this,SecondActivity::class.java)
            activityLauncher.launch(intent)
        }
    }

    private val activityLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            Log.i(TAG, "activityLauncher")
            // 实际获取的data就是onCreate中传递的Intent
            val data:Intent? = result?.data
            Log.i(TAG, "获取的data=$data")
            // 还可以获取额外的数据
            val str = data?.getStringExtra("str")
            Log.i(TAG, "获取的str=$str")
            val action = data?.action
            Log.i(TAG, "获取的action=$action")
        }
}
class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        findViewById<Button>(R.id.btn).setOnClickListener{
            val intent = Intent()
            intent.action = "hello"
            intent.putExtra("str","world")
            // 设置回调的结果
            setResult(0,intent)
            finish()
        }
    }
}
image.png

ii、检查权限

// 需要在配置清单配置权限
// 单权限检查
class MainActivity : AppCompatActivity() {

    private final val TAG = javaClass.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 检查权限
        if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            permissionLauncher.launch(permission)
        }
    }

    private val permissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()){
            if (it) {
                // 如果授权
                Log.i(TAG,"授权成功!")
            }else{
                // 如果未授权
                Log.i(TAG,"未授权")
            }
        }

    // 模糊定位权限
    private val permission = android.Manifest.permission.ACCESS_COARSE_LOCATION
}
// 多权限申请
class MainActivity : AppCompatActivity() {

    private final val TAG = javaClass.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 检查权限
        // find相当于for循环
        if (permissions.find {ActivityCompat.checkSelfPermission(this,it) 
                != PackageManager.PERMISSION_GRANTED
            } != null) {
            multiplePermissionsLauncher.launch(permissions)
        }
    }
    // 测试的权限,需要将这几个权限加到配置清单
    private val permissions = arrayOf(
        android.Manifest.permission.ACCESS_FINE_LOCATION,
        android.Manifest.permission.BLUETOOTH,
        android.Manifest.permission.BLUETOOTH_ADMIN
    )

    private val multiplePermissionsLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){ permissionsMap ->
        val noGrantedPermissions = ArrayList<String>()
        permissionsMap.entries.forEach{
            if (!it.value) {
                noGrantedPermissions.add(it.key)
            }
        }
        if (noGrantedPermissions.isEmpty()) {
            Log.i(TAG,"权限全部申请通过!")
            // 权限申请通过,可以执行的操作。可以不写任何代码
        }else{
            Log.e(TAG,"权限未全部申请通过,继续申请权限!")
            noGrantedPermissions.forEach{
                if (!shouldShowRequestPermissionRationale(it)) {
                    //用户拒绝权限并且系统不再弹出请求权限的弹窗
                    //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
                }
            }
        }

    }
}

2、handler的使用

3、ListView的使用

val listData = arrayListOf("a", "b", "c", "d", "e", "f", "g", "h", "k")
val lv = findViewById<ListView>(R.id.lv)
lv.adapter =
    ArrayAdapter(this, android.R.layout.simple_list_item_1, listData)
lv.onItemClickListener = AdapterView.OnItemClickListener { parent, view, postion, id ->
    // 这里只需要考虑position,表示点击的是第几个item
    Toast.makeText(this,"当前点击索引值:$postion,值为:${listData[postion]}" ,Toast.LENGTH_SHORT).show()
}

二、蓝牙连接的开发

1、需要了解的类

  • BluetoothManager:被用来提供蓝牙适配器实例的高水平管理类
    • BluetoothAdapter getAdapter():返回蓝牙适配器 官方文档表示: image.png
mBluetoothManager = mContext?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (mBluetoothManager == null) {
    Log.e(TAG, "BluetoothManager初始化失败!")
    return;
}
mBluetoothAdapter = mBluetoothManager!!.adapter
  • BluetoothAdapter:使蓝牙可被发现、获取配对过的设备、创建socket连接通信、通过已知的Mac地址实例化蓝牙设备、扫描附近可发现的蓝牙设备的工具类
    • Constans:大量的常量,用于表示蓝牙的各种行为状态
    • startDiscovery():开始扫描附近可发现的蓝牙设备
    • cacelDiscovery():取消扫描,在蓝牙配对前执行该方法。
    • getBondedDevices():获取已经配对过的蓝牙设备,以Set形式返回
    • getState():获取当前蓝牙适配器的状态:STATE_OFFSTATE_TURNING_ONSTATE_ON, or STATE_TURNING_OFF
    • BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord (String name, UUID uuid):创建一个socket,作为Server端
  • BluetoothDevice:远程连接蓝牙设备。用于创建与相应设备的连接或查询有关设备的信息,例如名称、地址、类和绑定状态。
    • constans:表示蓝牙一些行为的常量。
    • BluetoothSocket createRfcommSocketToServiceRecord (UUID uuid):创建socket,用于作为client端。

2、客户端开发

下方一切的findViewById()需要自己创建,能运行就行,保证基本的逻辑完好!

<!--添加蓝牙互联的权限-->
<!--允许App连接到配对的蓝牙设备,兼容低版本-->
<uses-permission android:name="android.permission.BLUETOOTH" />

<!--允许App发现和配对蓝牙设备,兼容低版本-->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

<!--允许App扫描蓝牙设备-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<!--当前设备可被发现-->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!--App需要与蓝牙设备配对、通讯-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!--App需要获取物理位置-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

1. 初始化蓝牙相关配置

private val TAG = javaClass.simpleName
private var mBluetoothManager: BluetoothManager? = null
var mBluetoothAdapter: BluetoothAdapter? = null
private var mContext: Context? = null
/**
 * 用来初始化蓝牙的相关配置!
 */
fun init(context: Context): Boolean {
    mContext = context
    mBluetoothManager =
        mContext?.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    if (mBluetoothManager == null) {
        Log.e(TAG, "BluetoothManager初始化失败!")
        return false
    }
    mBluetoothAdapter = mBluetoothManager!!.adapter
    if (mBluetoothAdapter == null) {
        Log.e(TAG, "无法获取蓝牙适配器bluetoothAdapter!")
        return false
    }
    if (!checkIsSupportBluetooth()) {
        Log.e(TAG, "设备不支持蓝牙!")
        return false
    }
    return true
}

/**
 * 检查设备是否支持蓝牙
 */
private fun checkIsSupportBluetooth(): Boolean {
    return mBluetoothAdapter!!.isEnabled
}

2.申请权限

private val permissions = arrayOf(
    android.Manifest.permission.BLUETOOTH_SCAN,
    android.Manifest.permission.BLUETOOTH_ADVERTISE,
    android.Manifest.permission.BLUETOOTH_CONNECT,
    android.Manifest.permission.ACCESS_COARSE_LOCATION,
    android.Manifest.permission.ACCESS_FINE_LOCATION
)
/**
 * 请求权限
 */
 // onCreate()中
if (permissions.find { ActivityCompat.checkSelfPermission(this,it) != PackageManager.PERMISSION_GRANTED } != null){
    // 调用下面的launcher
    requestMultiplePermissionLauncher.launch(permissions)
}else{
}

private val requestMultiplePermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions: Map<String, Boolean> ->
    val noGrantedPermissions = ArrayList<String>()
    permissions.entries.forEach {
        if (!it.value) {
            noGrantedPermissions.add(it.key)
        }
    }
    if (noGrantedPermissions.isEmpty()) {
        Log.i(TAG,"权限全部申请通过!")
        // 所有申请权限通过,可以执行后续操作
    } else {
        Log.e(TAG,"权限未通过授权!")
        //未同意授权
        noGrantedPermissions.forEach {
            if (!shouldShowRequestPermissionRationale(it)) {
                //用户拒绝权限并且系统不再弹出请求权限的弹窗
                //这时需要我们自己处理,比如自定义弹窗告知用户为何必须要申请这个权限
            }
        }
    }
}

3.开启广播

蓝牙状态改变的广播、获取扫描到附近蓝牙设备的广播

private val mBluetoothDeviceList: ArrayList<BluetoothDevice> = ArrayList()
private val mBluetoothDeviceData: ArrayList<String> = ArrayList()
private lateinit var deviceListView: ListView
private lateinit var deviceViewAdapter: ArrayAdapter<String>
private lateinit var mBluetoothStateReceiver: BluetoothStateReceiver
private lateinit var mScanBluetoothReceiver: ScanBluetoothDeviceReceiver

override fun onCreate(savedInstanceState: Bundle?) {
    val btStateFilter = IntentFilter().apply {
        addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
        addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
        addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
    }
    // 开启检测蓝牙状态的监听器
    mBluetoothStateReceiver = BluetoothStateReceiver()
    registerReceiver(mBluetoothStateReceiver, btStateFilter)
    // 初始化ListView
    deviceListView = findViewById(R.id.available_device)

    // 注册广播接受,监听扫描结果
    val scanBtButton = findViewById<Button>(R.id.scan_bt)
    scanBtButton.setOnClickListener {
        mScanBluetoothReceiver = ScanBluetoothDeviceReceiver()
        AlertDialog.Builder(this)
            .setTitle("蓝牙搜索")
            .setMessage("是否开始搜索当前可发现的所有的蓝牙设备?")
            .setIcon(R.mipmap.icon)
            .setNegativeButton("取消", null)
            .setNeutralButton("确认") { p0, p1 ->
                // 开启广播
                Log.d(TAG, "开启搜索蓝牙的广播")
                mBluetoothHelper.mBluetoothAdapter?.cancelDiscovery()
                clearDeviceList()
                // 添加需要拦截的蓝牙行为
                val scanIntentFilter = IntentFilter(BluetoothDevice.ACTION_FOUND)
                scanIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
                scanIntentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
                // 注册广播
                registerReceiver(mScanBluetoothReceiver, scanIntentFilter)
                Log.d(TAG, "蓝牙适配器是否为空:${mBluetoothHelper.mBluetoothAdapter == null}")
                // 注册后,开始搜索附近的蓝牙设备
                mBluetoothHelper.mBluetoothAdapter?.startDiscovery()
            }.create().show()
    }
}
/**
 * 监测蓝牙状态的广播:监听器
 */
private inner class BluetoothStateReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d(TAG, "初始化广播,准备监听蓝牙状态...")
        val action = intent!!.action
        Log.d(TAG, "适配器中:actionStateChanged:$action")
        val btStateView = findViewById<TextView>(R.id.bt_is_open)
        if (BluetoothAdapter.ACTION_STATE_CHANGED == action) {
            val state =
                intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, DEFAULT_VALUE_BULETOOTH)
            Log.d(TAG, "state = $state")

            when (state) {
                // 蓝牙关闭
                BluetoothAdapter.STATE_OFF -> {
                    Log.d(TAG, "蓝牙处于关闭...")
                    btStateView.text = "未开启"
                    btStateView.setTextColor(Color.RED)
                }

                BluetoothAdapter.STATE_ON -> {
                    Log.d(TAG, "蓝牙处于开启...")
                    btStateView.text = "开启中"
                    btStateView.setTextColor(Color.GREEN)
                }
            }
        }
        when (action) {
            // 由于蓝牙设备连接成功后,没有能够之间判断是否连接的常量,我们使用handler来操作判断。断开连接有常量字,可以直接使用
            BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
                Log.d(TAG, "断开连接!")
                btStateView.text = "未连接"
                btStateView.setTextColor(Color.RED)
                clearDeviceList()
                findViewById<LinearLayout>(R.id.device_list).visibility = View.VISIBLE
                findViewById<LinearLayout>(R.id.send_data_view).visibility = View.GONE
                findViewById<Button>(R.id.scan_bt).visibility = View.VISIBLE
            }
        }
    }
}
/**
 *  蓝牙设备的监听器,即监听蓝牙扫描时的设备
 */
private inner class ScanBluetoothDeviceReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d(TAG, "开始搜索可发现的蓝牙设备...")
        when (intent!!.action) {
            BluetoothDevice.ACTION_FOUND -> {
                // 发现的蓝牙设备
                val device: BluetoothDevice? =
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                        intent.getParcelableExtra(
                            BluetoothDevice.EXTRA_DEVICE,
                            BluetoothDevice::class.java
                        )
                    } else {
                        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
                    }
                device!!
                if (mBluetoothDeviceList.size > 0 && !mBluetoothDeviceList.contains(device)) {
                    if (device.name != null) {
                        mBluetoothDeviceData.add("${device.name},物理地址:${device.address}")
                        mBluetoothDeviceList.add(device)
                    }
                }
                if (mBluetoothDeviceList.size == 0 && device.name != null) {
                    mBluetoothDeviceData.add("${device.name},物理地址:${device.address}")
                    mBluetoothDeviceList.add(device)
                }

                Log.d(TAG, "搜索到蓝牙设备:name = ${device.name}")
            }

            BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
                Toast.makeText(context, "开始搜索可供发现的蓝牙设备...", Toast.LENGTH_SHORT)
                    .show()
            }

            BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
                Toast.makeText(context, "搜索完成...", Toast.LENGTH_SHORT).show()
            }
        }
        deviceViewAdapter = ArrayAdapter(context!!, android.R.layout.simple_list_item_1, mBluetoothDeviceData)
        deviceListView.adapter = deviceViewAdapter
    }
}
/**
 * 清除数据的工具类
 */
private fun clearDeviceList() {
    if (mBluetoothDeviceList.size > 0) {
        Log.d(TAG, "清除数据!")
        mBluetoothDeviceData.clear()
        mBluetoothDeviceList.clear()
        deviceViewAdapter.notifyDataSetChanged()
        Log.d(
            TAG,
            "清除结果:deviceData.size=${mBluetoothDeviceData.size},deviceList.size=${mBluetoothDeviceList.size}"
        )
    }
}

4.通过ListView的适配器,添加item的点击事件

private val handler = object : Handler(Looper.myLooper()!!) {
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        Log.d(TAG, "msg.what=${msg.what}")
        when (msg.what) {
            BluetoothMessageType.MESSAGE_READ -> {
                val getDataView = findViewById<TextView>(R.id.get_data)
                // 处理数据 byte[] 转 String
                val receiveData = msg.obj as ByteArray
                val data = String(receiveData, StandardCharsets.UTF_8)
                Log.d(TAG, "接收到数据:$data")
                getDataView.text = data
            }

            BluetoothMessageType.MESSAGE_TOAST -> {
                val data = msg.data
                val toastMsg = data.getString("toast")
                Toast.makeText(this@MainActivity, toastMsg, Toast.LENGTH_SHORT).show()
            }
            // 连接成功后,发送handler消息,操作蓝牙展示的组件隐藏,发送数据的组件显示
            BluetoothMessageType.CONNECT_SUCCESS -> {
                Log.d(TAG, "连接成功!")
                val btStateView = findViewById<TextView>(R.id.bt_is_open)
                btStateView.text = "设备配对中"
                btStateView.setTextColor(Color.GREEN)
                clearDeviceList()
                findViewById<LinearLayout>(R.id.device_list).visibility = View.GONE
                findViewById<LinearLayout>(R.id.send_data_view).visibility = View.VISIBLE
                findViewById<Button>(R.id.scan_bt).visibility = View.GONE
            }
        }
    }
}
// onCreate()
deviceListView.onItemClickListener =
    AdapterView.OnItemClickListener { parent, view, position, long ->
        Log.d(TAG, "clicking index = $position")
        val clickingDevice = mBluetoothDeviceList[position]
        AlertDialog.Builder(this).setTitle("蓝牙连接")
            .setMessage("是否连接蓝牙:${clickingDevice.name}")
            .setIcon(R.mipmap.icon)
            .setNegativeButton("取消", null)
            .setPositiveButton("确认") { p0, p1 ->
                Log.d(TAG, "准备连接蓝牙")
                mBluetoothHelper.mBluetoothAdapter?.cancelDiscovery()
                // 连接蓝牙应该在子线程中进行,为耗时操作
                mBluetoothTransferController = BluetoothTransferController(handler)
                mBluetoothTransferController.connectBluetoothDevice(clickingDevice)
                if (mBluetoothTransferController.isConnected()) {
                    handler.obtainMessage(BluetoothMessageType.CONNECT_SUCCESS,"连接成功!").sendToTarget()
                }
                // 开启广播,监听蓝牙连接结果
            }.create().show()
    }

5.编写设备互联的工具类

耗时、阻塞的方法,不能在主线程中运行

class BluetoothTransferController(private val handler: Handler) {
    private val TAG = javaClass.simpleName
    private val bluetoothUUID = UUID.fromString("fc5deb71-9d4b-460b-b725-b06ea79bda5a")
    private var connectThread: ConnectThread? = null
    private var mBluetoothSocket:BluetoothSocket? = null
    
    /**
     * 连接设备的线程
     */
    
    inner class ConnectThread(private val bluetoothDevice: BluetoothDevice) : Thread() {

        // 懒加载
        private val bluetoothSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
            Log.d(TAG,"开始建立socket")
            bluetoothDevice.createRfcommSocketToServiceRecord(bluetoothUUID)
        }

        override fun run() {
            super.run()
            bluetoothSocket?.run {
                try {
                    mBluetoothSocket = this
                    Log.d(TAG,"开始配对设备连接!")
                    connect()
                    initTransferDataThread()
                } catch (e: IOException) {
                    try {
                        close()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                }
            }
        }
        fun cancel() {
            try {
                bluetoothSocket?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
    fun connectBluetoothDevice(bluetoothDevice: BluetoothDevice) {
        Log.d(TAG,"连接蓝牙设备!")
        if (connectThread != null) {
            connectThread?.cancel()
            connectThread = null
        }
        connectThread = ConnectThread(bluetoothDevice)
        connectThread?.start()
    }

    fun isConnected() : Boolean{
        Log.i(TAG,"是否连接?")
        if (mBluetoothSocket == null) {
            return false
        }
        return mBluetoothSocket!!.isConnected
    }
    inner class TransferDataThread(private val bluetoothSocket: BluetoothSocket) : Thread() {

        private var inputStream: InputStream? = null
        private var outputStream: OutputStream? = null
        private var connected = false
        private val mBuffer = ByteArray(1024)

        init {
            Log.d(TAG,"传输数据的线程初始化中!")
            connected = bluetoothSocket.isConnected
            try {
                if (connected) {
                    handler.obtainMessage(BluetoothMessageType.CONNECT_SUCCESS,"连接成功!").sendToTarget()
                    Log.d(TAG,"初始化input和output流...")
                    inputStream = bluetoothSocket.inputStream
                    outputStream = bluetoothSocket.outputStream
                }
            } catch (e: IOException) {
                Log.e(TAG,"初始化出现错误!",e)
                e.printStackTrace()
            }
        }

        override fun run() {
            Log.d(TAG,"传输数据的run线程运行中")
            var numBytes:Int
            while (true) {
                numBytes = try {
                    inputStream!!.read(mBuffer)
                } catch (e: IOException) {
                    Log.e(TAG,"读取数据发生错误!",e)
                    break
                }

                val readMsg = handler.obtainMessage(BluetoothMessageType.MESSAGE_READ,numBytes,-1,mBuffer)
                readMsg.sendToTarget()
            }
        }

        fun writeData(byte: ByteArray) {
            Log.d(TAG,"传输数据的writeData函数...")
            try {
                outputStream!!.write(byte)
            } catch (e: IOException) {
                Log.e(TAG,"发送数据发生错误!",e)
                cancel()
                handler.obtainMessage(BluetoothMessageType.MESSAGE_TOAST,"不能发送数据到互联的设备中...").sendToTarget()
                return
            }
        }

        fun cancel(){
            try {
                bluetoothSocket.close()
            } catch (e: IOException) {
                Log.e(TAG,"关闭socket连接失败!",e)
            }
        }
    }
    // 提取工具类,直接调用
    fun initTransferDataThread(){
        if (mBluetoothSocket == null) {
            Log.d(TAG,"socket未建立,无法写入数据")
            handler.obtainMessage(MessageType.MESSAGE_TOAST,"socket未建立,无法写入数据")
            return
        }
        if (transferDataThread == null) {
            Log.d(TAG,"传输数据线程未开启,准备初始化!")
            transferDataThread = TransferDataThread(mBluetoothSocket!!)
        }
        Log.d(TAG,"准备写入数据!")
        transferDataThread!!.start()
    }
    
    fun writeData(data: ByteArray) {
        transferDataThread!!.writeData(data)
    }
}

6.连接设备

findViewById<Button>(R.id.send_btn).setOnClickListener {
    Thread {
        val sendData = findViewById<EditText>(R.id.send_data)
        if (sendData.text != null) {
            // socket只能传输byte[]数据
            val toByte = sendData.text.toString().toByteArray()
            // 启动handler
            mBluetoothTransferController.writeData(toByte)
        }
    }.start()
}

3、服务端开发

添加相应设备互联的权限

1.连接线程

public class BluetoothConnectController extends Thread {

    private final String TAG = getClass().getSimpleName();
    private final UUID bluetoothUUID = UUID.fromString("fc5deb71-9d4b-460b-b725-b06ea79bda5a");
    private final BluetoothAdapter mAdapter;
    private final BluetoothServerSocket mSocket;
    private final Handler mHandler;

    @SuppressLint("MissingPermission")
    public BluetoothConnectController(BluetoothAdapter adapter, Handler handler) {
        mHandler = handler;
        mAdapter = adapter;
        BluetoothServerSocket tmp;
        try {
            tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord("bluetooth_connection", bluetoothUUID);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        mSocket = tmp;
    }

    @Override
    public void run() {
        Log.i(TAG, "建立连接!");
        BluetoothSocket socket;
        // 建立长连接
        while (true) {
            try {
                Log.i(TAG, "socket开始连接!");
                socket = mSocket.accept();
                Log.i(TAG, "socket连接完成!");
                // 连接成功,开启传输数据的线程
                transferData(socket);
            } catch (IOException e) {
                Log.i(TAG, "socket建立出现错误!", e);
                break;
            }
        }
    }

    public void transferData(BluetoothSocket socket) {
        new Thread(() -> {
            while (true) {
                byte[] res = new byte[1024];
                // todo 建立连接处理数据
                try {
                    InputStream inputStream = socket.getInputStream();
                    inputStream.read(res);
                    Log.i("MainActivity", "res=" + new String(res));
                    // 发送到主线程
                    Message message = new Message();
                    message.what = 1;
                    message.obj = res;
                    mHandler.sendMessage(message);
                    OutputStream outputStream = socket.getOutputStream();
                    String reply = "receive data";
                    outputStream.write(reply.getBytes(StandardCharsets.UTF_8));
                } catch (IOException e) {
                    cancel();
                    break;
                }

            }
        }).start();
    }

    public void cancel() {
        try {
            mSocket.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.创建service

public class BluetoothHelperBinder extends Binder {

    private final String TAG = "MainActivity";

    private BluetoothConnectController mThread = null;


    public void initConnection(BluetoothAdapter bluetoothAdapter, Handler handler) {
        Log.i(TAG, "initConnection()");
        mThread = new BluetoothConnectController(bluetoothAdapter, handler);
        Log.i(TAG, "建立socket流!");
        mThread.start();
        Log.i(TAG, "开启线程成功!");
    }

    public void closeConnection(){
        mThread.cancel();
    }
}
public class BluetoothService extends Service {

    private final BluetoothHelperBinder mBinder = new BluetoothHelperBinder();

    public BluetoothService() {
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("MainActivity", "bluetoothService's onCreate!");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mBinder.closeConnection();
    }
}

3.开启服务,socket进行数据传输

public class MainActivity extends AppCompatActivity {
    private final String TAG = getClass().getSimpleName();
    private final MyHandler handler = new MyHandler(this);
    private BluetoothAdapter mAdapter = null;
    private BluetoothServiceConnection connection = null;
    private boolean isBinder = false;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_main);
        Log.i(TAG, "onCreate!");
        // 因为蓝牙和热点的连接都是长连接,无法同时开启两个长连接,则重新开启一个activity
        findViewById(R.id.btn).setOnClickListener(view -> {
            startActivity(new Intent(this,APActivity.class));
        });
        // 开启一个蓝牙状态的监听器
        IntentFilter intentFilter = new IntentFilter();
        // 拦截断开连接的行为,监测到后,重新创建蓝牙的服务器等待被连接
        intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        registerReceiver(new bluetoothStateReceiver(), intentFilter);
        openBluetoothConnection();
    }


    private void openBluetoothConnection() {
        mAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
        connection = new BluetoothServiceConnection();
        //开启service
        isBinder = bindService(
                new Intent(this, BluetoothService.class),
                connection,
                Context.BIND_AUTO_CREATE);
        if (isBinder) {
            Toast.makeText(this, "绑定服务成功!", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }

    private static class MyHandler extends Handler {

        WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            byte[] obj = ((byte[]) msg.obj);
            String data = new String(obj, StandardCharsets.UTF_8);
            Log.i("MainActivity", "接收到数据:" + data);
            MainActivity activity = mActivity.get();
            if (msg.what == 1) {
                TextView dataView = activity.findViewById(R.id.bluetooth_data);
                dataView.setText(data);
            }
        }
    }

    private class bluetoothStateReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // 断开连接
            if (Objects.equals(action, BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                Log.i(TAG, "设备断开连接!");
                unbindService(connection);
                // 重新开启线程,开始等待被连接
                openBluetoothConnection();
            }
        }
    }

    private class BluetoothServiceConnection implements ServiceConnection {

        private BluetoothHelperBinder bluetoothHelperBinder = null;

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG, "onServiceConnected()");
            bluetoothHelperBinder = ((BluetoothHelperBinder) iBinder);
            bluetoothHelperBinder.initConnection(mAdapter, handler);
            Log.i(TAG, "初始化连接成功!");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            bluetoothHelperBinder.closeConnection();
        }
    }

}

飞书20231211-151319.gif

三、热点互联的开发

热点互联与蓝牙很相似,连接成功后都是通过socket套接字传输数据

1、配置清单添加权限

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

2、客户端开发

1.连接线程

public class WifiConnectController extends Thread {
    private final String TAG = getClass().getSimpleName();
    private final Socket mSocket;
    private final Handler mHandler;
    private InputStream mInputStream;
    private OutputStream mOutputStream;
    public WifiConnectController(Socket socket, Handler handler) {
        Log.i(TAG, "ConnectThread()");
        mSocket = socket;
        mHandler = handler;
    }

    @Override
    public void run() {
        if (mSocket == null) {
            return;
        }
        // 标志设备连接中
        mHandler.obtainMessage(WifiMessageType.CONNECT_SUCCESS).sendToTarget();

        while (true) {
            try {
                mInputStream = mSocket.getInputStream();
                mOutputStream = mSocket.getOutputStream();
                // 每次读取1kb缓存的数据
                byte[] buffer = new byte[1024];
                int bytes;
                // 执行循环开始操作数据
                bytes = mInputStream.read(buffer);
                Log.d(TAG, "读取到数据:" + Arrays.toString(buffer));
                if (bytes > 0) {
                    mHandler.obtainMessage(WifiMessageType.MESSAGE_READ,buffer).sendToTarget();
                }
            } catch (IOException e) {
                mHandler.obtainMessage(WifiMessageType.TOAST, "socket流读取input和output出现错误!");
                Log.e(TAG, "socket流读取input和output出现错误!", e);
                break;
            }
        }
    }

    public void write(byte[] bytes) {
        if (mOutputStream == null) {
            Log.e(TAG, "outputStream流为null,无法写入数据!");
        }
        Log.i(TAG, "正在执行写数据!");
        try {
            Log.i(TAG,"数据:" + Arrays.toString(bytes));
            mOutputStream.write(bytes);
        } catch (IOException e) {
            Log.e(TAG, "写数据出现错误!", e);
            mHandler.obtainMessage(WifiMessageType.TOAST,"写数据出现错误!").sendToTarget();
        }
    }
}

2.启动线程

public class WifiActivity extends AppCompatActivity {

    private final String TAG = getClass().getSimpleName();
    private WifiConnectController wifiConnectController;
    private final MyHandler mHandler = new MyHandler(this);
    private Socket socket = null;
    private String ip = null;
    private int port;

    private static class MyHandler extends Handler {

        WeakReference<WifiActivity> mActivity;

        public MyHandler(WifiActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            Log.i("MainActivity", "handleMessage");
            WifiActivity activity = mActivity.get();
            TextView dataView = activity.findViewById(R.id.data1);
            switch (msg.what) {
                case WifiMessageType.TOAST:{
                    Toast.makeText(activity, msg.obj.toString(), Toast.LENGTH_SHORT).show();
                }
                case WifiMessageType.MESSAGE_READ:{
                    byte[] obj = ((byte[]) msg.obj);
                    String data = new String(obj, StandardCharsets.UTF_8);
                    Log.i("MainActivity", "接收到数据:" + data);
                    dataView.setText(data);
                }
                case WifiMessageType.CONNECT_SUCCESS:{
                    Toast.makeText(activity, "socket连接成功!", Toast.LENGTH_SHORT).show();
                }
            }

        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wifi);

        findViewById(R.id.connect_btn).setOnClickListener(view -> {
            Editable ipText = ((EditText) findViewById(R.id.connect_ip)).getText();
            Editable portText = ((EditText) findViewById(R.id.connect_port)).getText();
            if (ipText != null && portText != null) {
                ip = ipText.toString();
                port = Integer.parseInt(portText.toString());
            }
            Log.i(TAG, "连接的服务器:" + ip + ":" + port);
            new Thread(() -> {
                try {
                    // 先判断是否已经连接,如果已经连接,就不需要再次连接
                    if (socket == null) {
                        //socket = new Socket(InetAddress.getByName(IP),PORT);
                        socket = new Socket();
                        socket.connect(new InetSocketAddress(ip, port), 5000);
                        Log.i(TAG, "连接成功!");
                    }else {
                        Log.i(TAG,"已经连接到服务器,不需要再次重连!");
                        mHandler.obtainMessage(WifiMessageType.TOAST,"已经连接到服务器,不需要再次重连!").sendToTarget();
                    }

                } catch (IOException e) {
                    Log.e(TAG, "连接socket出现错误!", e);
                    mHandler.obtainMessage(WifiMessageType.TOAST,"socket连接错误,无法发送数据!").sendToTarget();
                    return;
                }
                // 开启ap互联的线程
                wifiConnectController = new WifiConnectController(socket, mHandler);
                wifiConnectController.start();
            }).start();
        });
        // 获取需要发送的数据
        EditText dataView = findViewById(R.id.send_data1);

        findViewById(R.id.send_btn1).setOnClickListener(view1 -> {
            if (socket != null) {
                new Thread(() -> {
                    // 转换为byte[],用于socket中传输
                    byte[] bytes = dataView.getText().toString().getBytes(StandardCharsets.UTF_8);
                    wifiConnectController.write(bytes);
                }).start();
            } else {
                Toast.makeText(this, "socket为空,无法传输数据", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

3、服务器开发

1.连接线程

public class WifiConnectController extends Thread {

    private final String TAG = getClass().getSimpleName();
    private ServerSocket mSocket;
    private final Handler mHandler;
    private InputStream mInputStream;
    private OutputStream mOutputStream;


    public WifiConnectController(Handler handler) {
        Log.i(TAG, "ConnectThread()构造方法中");
        mHandler = handler;
    }

    @Override
    public void run() {
        Socket socket = null;
        try {
            InetSocketAddress inetSocketAddress = new InetSocketAddress(InetAddress.getByName("192.168.144.89"),3150);
            mSocket = new ServerSocket();
            mSocket.bind(inetSocketAddress);
            Log.i(TAG, "初始化socket服务器,端口为:3150");
        } catch (IOException e) {
            Log.e(TAG, "初始化socket出现错误!", e);
            return;
        }

        // 开启长连接
        while (true) {
            // 阻塞调用
            try {
                Log.i(TAG, "socket初始化完成,等待被连接!");
                socket = mSocket.accept();
                // 如果服务器建立完成,获取输入流和输出流
                mInputStream = socket.getInputStream();
                mOutputStream = socket.getOutputStream();
                Log.i(TAG, "socket连接完成!");
            } catch (IOException e) {
                mHandler.sendEmptyMessage(-1);
                Log.i(TAG, "socket初始化错误!");
                break;
            }
            // 读取数据
            readData();
        }
    }

    public void readData(){
        while (true) {
            try {
                // 每次读取1kb缓存的数据
                byte[] buffer = new byte[1024];
                int bytes;
                bytes = mInputStream.read(buffer);
                Log.i(TAG, "buffer=" + new String(buffer) + ",bytes=" + bytes);
                if (bytes > 0) {
                    Message message = Message.obtain();
                    message.what = 2;
                    message.obj = buffer;
                    // 发送到handler
                    mHandler.sendMessage(message);
                    String reply = "服务器接受到数据!";
                    byte[] replyBytes = reply.getBytes(StandardCharsets.UTF_8);
                    mOutputStream.write(replyBytes);
                }
            } catch (IOException e) {
                mHandler.sendEmptyMessage(-1);
                Log.e(TAG, "读取数据出现错误!即将关闭socket", e);
                cancel();
                break;
            }
        }
    }

    public void cancel() {
        try {
            if (mSocket != null) {
                mHandler.sendEmptyMessage(-1);
                mSocket.close();
                mInputStream.close();
                mOutputStream.close();
            }
        } catch (IOException e) {
            Log.e(TAG, "关闭socket通道失败!", e);
        }

    }
}

2.开启线程

public class APActivity extends AppCompatActivity {

    private WifiConnectController wifiConnectController;

    private final MyHandler handler = new MyHandler(this);

    private static class MyHandler extends Handler {

        WeakReference<APActivity> mActivity;

        public MyHandler(APActivity activity) {
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            byte[] obj = ((byte[]) msg.obj);
            String data = new String(obj, StandardCharsets.UTF_8);
            Log.i("MainActivity", "接收到数据:" + data);
            APActivity activity = mActivity.get();
            // 接受到客户端传输的数据
            if (msg.what == 2) {
                TextView dataView = activity.findViewById(R.id.ap_data);
                dataView.setText(data);
            }
            // wifi断开连接
            if (msg.what == -1) {
                activity.openAPConnection();
            }

        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_wifi);
        openAPConnection();
    }

    private void openAPConnection(){
        wifiConnectController = new WifiConnectController(handler);
        wifiConnectController.start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        wifiConnectController.cancel();
    }
}

img_v3_0261_df5e1830-14c6-4236-845b-26e2bebcb44g.gif

4、ap互联发现的问题:

1.客户端报连接失败的异常

img_v3_0261_df5e1830-14c6-4236-845b-26e2bebcb44g.gif

  • linux的防火墙未关闭,被拦截
# 清空防火墙规则
iptables -F 
  • 服务端开启的热点的ip和port未生效,或者在代码中传入的ip和port与开启的热点不一样
# 查看ip
adb shell su
ifconfig
# 显示tcp相关的在监听服务状态的ip地址和socket程序识别码和程序名称
netstat -nlpt