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

时间:2018-02-06 17:18:01

标签: c# android bluetooth xamarin.forms

情境:

我正在使用将部署到一组设备的Xamarin.Forms构建Android应用程序。除了其中一个设备之外的所有设备都将进行一些数据收集,剩下的设备将是" hub"汇总所有数据并进行一些报告。我正在使用蓝牙进行设备到设备的通信。标记为主服务器的' hub充当客户端,并且所有收集器充当服务器。我有一个原型使用单个服务器和客户端...几乎。

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

症状:

客户端对InputStream的.Read()调用偶尔会无限期地阻塞,即使服务器已写入输出流。我已为此次通话添加了超时,以防止该应用完全卡住。 这种情况间歇性地发生,但我发现了一些模式,当它工作时,它没有

  1. 这似乎与服务器'有关。应用程序,而不是客户端。客户端可以保持打开,运行并根据需要发起连接到服务器的请求。
  2. 它始终是第一次使用服务器'应用程序已启动并连接到。它通常是第二次工作。通过第三个连接,.Read()将一直阻塞/超时。关闭并重新打开服务器上的应用程序"清理平板"可以这么说它会再次发挥作用。
  3. 一旦它开始失败,它似乎被卡住了。处于失败的状态。
  4. 从前台删除应用程序(但不关闭/终止它)似乎可以纠正故障状态,只要应用程序/ UI保留在后台,连接/读取就会成功发生。一旦恢复到前台,它就会再次失败。
  5. 代码:

    所有蓝牙处理都是由我使用Xamarin.Forms DependencyService注入的单个类/服务完成的。所有设备在启动时(通过此类的构造函数)将在后台线程上无限循环,等待连接并重复。这个蓝牙代码很大程度上基于蓝牙聊天示例,以及我发现的一些其他在线资源(一些Android原生/ 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. 尝试用他们的&#39; Insecure&#39;替换套接字处理。对应。
    4. 将我的读取超时调整为任意长度,以防第二次不够。
    5. 在服务器/收集器之前禁用/重新启用蓝牙。接受()新连接(此时使用随机的东西)
    6. 视频

      我拍了一段关于这件事的视频。 后面的平板电脑是收集器/服务器前台的平板电脑是主/客户端。视频启动时,客户端显示一些先前的尝试,服务器应用程序在后台(但正在运行)。我演示了.Read在收集器/服务器应用程序在后台时工作,但不在前台。每个开始数据同步的请求都有一个对应的&#34;控制台&#34; (或者如果我太快按下它就会发出警告) https://youtu.be/NGuGa7upCU4

      要点:

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

1 个答案:

答案 0 :(得分:0)

尝试以下操作,将所有内容更改为使用:

Param(
   [string]$collectionurl = "http://ictfs2015:8080/tfs/DefaultCollection",
   [string]$projectName = "ProjectName",
   [string]$BuildId = "44",
   [string]$user = "username",
   [string]$token = "password"
)

# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))

$uri = "$($collectionurl)/$($projectName)/_apis/build/builds/$BuildId/timeline?api-version=2.0"
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
$steps = $response.records | where {$_.result -eq 'failed' -and $_.type -eq 'Task'} # Filter the failed steps

$failedsteps = @()

foreach($step in $steps){

    $customObject = new-object PSObject -property @{
          "StepId" = $step.id
          "type" = $step.type
          "TaskName" = $step.name
          "startTime" = $step.startTime
          "finishTime" = $step.finishTime
          "state" = $step.state
          "result" = $step.result
          "changeId" = $step.changeId
          "workerName" = $step.workerName
        } 

    $failedsteps += $customObject       
}

$failedsteps | Select `
                StepId, 
                type, 
                TaskName,
                startTime,
                finishTime,
                state,
                result,
                changeId,
                workerName #|export-csv -Path C:\FailedBuildSteps.csv -NoTypeInformation