繁体   English   中英

Xamarin.Forms(Android)蓝牙间歇性工作

[英]Xamarin.Forms (Android) Bluetooth intermittently working

场景:

我正在使用Xamarin.Forms构建一个Android应用程序,该应用程序将部署到一组设备上。 除其中一个设备外,所有设备都将进行一些数据收集,而其余设备将成为“枢纽”,以聚合所有数据并进行一些报告。 我正在使用Bluetooth进行设备到设备的通信。 标有“主”的“集线器”充当客户端,所有收集器充当服务器。 我有一个原型,几乎可以使用一个服务器和一个客户端。

有时,客户端/主服务器将无法从服务器/收集器读取。 我正在努力寻找造成这种情况的原因,并希望得到任何帮助。

症状:

即使服务器已写入输出流,客户端从InputStream对.Read()的调用有时也会无限期地阻塞。 我在此呼叫中添加了超时,以防止应用程序完全卡住。 这是间歇性发生的,但是我发现了何时可以工作以及何时不能工作的模式

  1. 它似乎与“服务器”应用有关,与客户端无关。 客户端可以保持打开,运行状态,并根据需要发起请求以连接到服务器。
  2. 它始终在首次启动并连接“服务器”应用程序时起作用。 通常它第二次工作。 通过第三个连接,.Read()将始终阻止/超时。 可以这么说,关闭并重新打开服务器上的应用程序可以“清理掉状态”,它将再次运行。
  3. 一旦开始出现故障,它似乎“卡在”故障状态。
  4. 从前台删除该应用程序(但不能关闭/杀死它)似乎可以纠正故障状态,并且只要该应用程序/ UI仍在后台,连接/读取就会成功进行。 一旦恢复到前台,它将再次开始失败。

码:

所有蓝牙处理都由我使用Xamarin.Forms DependencyService注入的单个类/服务完成。 所有设备在启动时(通过此类的构造函数)将在后台线程上无限期地循环,等待连接并重复。 大部分蓝牙代码基于蓝牙聊天示例,以及我发现的其他一些在线资源(一些android native / java,一些Xamarin / C#)

主设备将按需(通过按UI中的按钮触发)尝试连接到任何收集器(通过绑定的蓝牙设备)并从中读取数据。 还有一个简单的UI组件,该组件实际上充当控制台日志。

这是整个服务类。

public class GameDataSyncService : IGameDataSyncService
{
    private const string UUID = "8e99f5f1-4a07-4268-9686-3a288326e0a2";

    private static Task acceptLoopTask;
    private static Task syncDataTask;
    private static readonly object locker = new object();
    private static bool running = false;

    public event EventHandler<DataSyncMessage> MessageBroadcast;

    public GameDataSyncService()
    {
        // Every device will listen and accept incoming connections.  The master will make the connections.
        lock (locker)
        {
            if (acceptLoopTask == null)
            {
                acceptLoopTask = Task.Factory.StartNew(AcceptLoopWorker, TaskCreationOptions.LongRunning);
            }
        }
    }

    public void SyncData()
    {
        lock (locker)
        {
            if (running)
            {
                BroadcastMessage("Previous data sync is still running.", DataSyncMessageType.Warning);
                return;
            }
            else
            {
                running = true;
                syncDataTask = Task.Factory.StartNew(SyncDataWorker);
            }
        }
    }

    private void BroadcastMessage(string message, DataSyncMessageType type = DataSyncMessageType.Info)
    {
        MessageBroadcast?.Invoke(this, new DataSyncMessage { Text = message, Type = type });
    }

    private async Task AcceptLoopWorker()
    {
        int count = 0;

        while (true)
        {
            BluetoothServerSocket serverSocket = null;
            BluetoothSocket clientSocket = null;
            try
            {
                BroadcastMessage($"Listening for incoming connection...", DataSyncMessageType.Debug);

                serverSocket = BluetoothAdapter.DefaultAdapter.ListenUsingRfcommWithServiceRecord(nameof(GameDataSyncService), Java.Util.UUID.FromString(UUID));
                clientSocket = serverSocket.Accept(); // This call blocks until a connection is established.
                BroadcastMessage($"Connection received from {clientSocket.RemoteDevice.Name}.  Sending data...", DataSyncMessageType.Info);

                var bytes = Encoding.UTF8.GetBytes($"Hello World - {string.Join(" ", Enumerable.Repeat(Guid.NewGuid(), ++count))}");

                await clientSocket.OutputStream.WriteAsync(bytes, 0, bytes.Length);
                clientSocket.OutputStream.Flush();

                // Give the master some time to close the connection from their end
                await Task.Delay(1000*3);
            }
            catch (Exception ex)
            {
                BroadcastMessage($"{ex.GetType().FullName}: {ex.Message}", DataSyncMessageType.Debug);
            }
            finally
            {
                try { clientSocket?.InputStream?.Close(); } catch { }
                try { clientSocket?.InputStream?.Dispose(); } catch { }
                try { clientSocket?.OutputStream?.Close(); } catch { }
                try { clientSocket?.OutputStream?.Dispose(); } catch { }
                try { clientSocket?.Close(); } catch { }
                try { clientSocket?.Dispose(); } catch { }
                try { serverSocket?.Close(); } catch { }
                try { serverSocket?.Dispose(); } catch { }

                BroadcastMessage($"Connection closed.", DataSyncMessageType.Debug);
            }
        }
    }

    private async Task SyncDataWorker()
    {
        BroadcastMessage($"Beginning data sync...");

        foreach (var bondedDevice in BluetoothAdapter.DefaultAdapter.BondedDevices.OrderBy(d => d.Name))
        {
            BluetoothSocket clientSocket = null;
            try
            {
                clientSocket = bondedDevice.CreateRfcommSocketToServiceRecord(Java.Util.UUID.FromString(UUID));
                BroadcastMessage($"Connecting to {bondedDevice.Name}...");
                try
                {
                    clientSocket.Connect();
                }
                catch
                {
                    BroadcastMessage($"Connection to {bondedDevice.Name} failed.", DataSyncMessageType.Error);
                }

                while (clientSocket.IsConnected)
                {
                    byte[] buffer = new byte[1024];
                    var readTask = clientSocket.InputStream.ReadAsync(buffer, 0, buffer.Length);
                    if (await Task.WhenAny(readTask, Task.Delay(1000)) != readTask)
                    {
                        BroadcastMessage($"Read timeout...", DataSyncMessageType.Error);
                        break;
                    }

                    int bytes = readTask.Result;
                    BroadcastMessage($"Read {bytes} bytes.", DataSyncMessageType.Success);

                    if (bytes > 0)
                    {
                        var text = Encoding.UTF8.GetString(buffer.Take(bytes).ToArray());
                        BroadcastMessage(text, DataSyncMessageType.Success);
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                BroadcastMessage($"{ex.GetType().FullName}: {ex.Message}", DataSyncMessageType.Debug);
            }
            finally
            {
                try { clientSocket?.InputStream?.Close(); } catch { }
                try { clientSocket?.InputStream?.Dispose(); } catch { }
                try { clientSocket?.OutputStream?.Close(); } catch { }
                try { clientSocket?.OutputStream?.Dispose(); } catch { }
                try { clientSocket?.Close(); } catch { }
                try { clientSocket?.Dispose(); } catch { }
            }
        }

        await Task.Delay(1000 * 3);

        BroadcastMessage($"Data sync complete!");
        lock (locker)
        {
            running = false;
        }
    }
}

我尝试过的方法(下面没有任何效果):

其中大多数来自其他stackoverflow帖子中的“解决方案”。

  1. 在混音中添加任意延迟
  2. 确保按顺序显式关闭/处置所有内容,包括流
  3. 尝试用“不安全”的对口代替插座处理。
  4. 将我的读取超时时间调整为任意长,以防万一还不够。
  5. 在.Accept()建立新连接之前,在服务器/收集器上禁用/重新启用蓝牙(此时已重新尝试随机填充)

视频:

我拍了一段视频。 后面的平板电脑是收集器/服务器。前台的平板电脑是主机/客户端。 视频开始播放时,客户端将显示以前的尝试,并且服务器应用程序处于后台(但正在运行)。 我演示了.Read在收集器/服务器应用程序在后台而不在前台的情况下有效。 每个开始数据同步的请求都有一个对应的“控制台”条目(如果我按得太早,则显示警告) https://youtu.be/NGuGa7upCU4

摘要:

据我所知,我的代码是正确的。 我不知道还有什么要更改/修复以更可靠地工作。 实际的连接似乎已成功(基于服务器/收集器的日志,不幸的是,未在视频中显示),但问题出在.Write(或.Read)中。 任何帮助,建议或见解都会很棒。

尝试以下操作,将其全部更改为使用:

private async Task AcceptLoopWorker()
{
    int count = 0;

    while (true)
    {
        try
        {
            BroadcastMessage("Listening for incoming connection...", DataSyncMessageType.Debug);

            using (var serverSocket = BluetoothAdapter.DefaultAdapter.ListenUsingRfcommWithServiceRecord(nameof(GameDataSyncService), Java.Util.UUID.FromString(UUID)))
            using (var clientSocket = serverSocket.Accept()) // This call blocks until a connection is established.
            {
                BroadcastMessage(string.Format("Connection received from {0}. Sending data...", clientSocket.RemoteDevice.Name), DataSyncMessageType.Info);
                var bytes = System.Text.Encoding.UTF8.GetBytes(string.Format("Hello World - {0}", string.Join(" ", Enumerable.Repeat(Guid.NewGuid(), ++count))));
                await clientSocket.OutputStream.WriteAsync(bytes, 0, bytes.Length);
            }

            await Task.Delay(1000 * 3); // Give the master some time to close the connection from their end
        }
        catch (Java.IO.IOException ex)
        {
            BroadcastMessage(string.Format("IOException {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
        }
        catch (Java.Lang.Exception ex)
        {
            BroadcastMessage(string.Format("Exception {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
        }
    }
}

private async Task SyncDataWorker()
{
    BroadcastMessage("Beginning data sync...");

    foreach (var bondedDevice in BluetoothAdapter.DefaultAdapter.BondedDevices.OrderBy(d => d.Name))
    {
        try
        {
            using (var clientSocket = bondedDevice.CreateRfcommSocketToServiceRecord(Java.Util.UUID.FromString(UUID)))
            {
                BroadcastMessage(string.Format("Connecting to {0}...", bondedDevice.Name));

                if (!clientSocket.IsConnected)
                {
                    clientSocket.Connect();
                }

                if (clientSocket.IsConnected)
                {
                    byte[] buffer = new byte[1024];
                    var readTask = clientSocket.InputStream.ReadAsync(buffer, 0, buffer.Length);
                    if (await Task.WhenAny(readTask, Task.Delay(1000)) != readTask)
                    {
                        BroadcastMessage("Read timeout...", DataSyncMessageType.Error);
                        break;
                    }

                    int bytes = readTask.Result;
                    BroadcastMessage(string.Format("Read {0} bytes.", bytes), DataSyncMessageType.Success);

                    if (bytes > 0)
                    {
                        var text = System.Text.Encoding.UTF8.GetString(buffer.Take(bytes).ToArray());
                        BroadcastMessage(text, DataSyncMessageType.Success);
                        break;
                    }
                }
                else
                {
                    BroadcastMessage("Not Connected...", DataSyncMessageType.Error);
                }
            }
        }
        catch (Java.IO.IOException ex)
        {
            BroadcastMessage(string.Format("IOException {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
        }
        catch (Java.Lang.Exception ex)
        {
            BroadcastMessage(string.Format("Exception {0}: {1}", ex.GetType().FullName, ex.Message), DataSyncMessageType.Debug);
        }
    }

    await Task.Delay(1000 * 3);

    BroadcastMessage("Data sync complete!");
    lock (locker)
    {
        running = false;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM