У меня есть приложение, которое нужно сканировать устройства Bluetooth на неопределенное время. Таким образом, мой вариант использования: я сканирую устройства Bluetooth в течение 5 секунд, получаю все рекламные данные, откладываю 1 секунду и повторяю сканирование снова. Мой код отлично работает в течение 7-8 часов (иногда более 24 часов), а затем запускается с ошибкой утечки памяти.
public class BLEScan:IBLEScan
{
private CancellationTokenSource _scanCancellationToken;
private volatile bool _IsScanning;
public int ScanTimeout { get; set; } = 5000;
public bool IsScanning
{
get { return _IsScanning; }
private set { _IsScanning = value; }
};
private readonly BluetoothManager _bluetoothManager;
private readonly BluetoothAdapter _bluetoothAdapter;
private readonly Api21BleScanCallback _api21ScanCallback;
private PendingIntent pendingIntent;
public BLEScan()
{
_bluetoothManager = (BluetoothManager)Android.App.Application.Context.GetSystemService(Android.Content.Context.BluetoothService);
_bluetoothAdapter = _bluetoothManager.Adapter;
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
{
_api21ScanCallback = new Api21BleScanCallback();
}
}
public async Task StartScanning(CancellationToken cancellationToken)
{
if (IsScanning)
{
Console.WriteLine("Scan Already Running.");
return;
}
IsScanning = true;
_scanCancellationToken = new CancellationTokenSource();
try
{
using (cancellationToken.Register(() => _scanCancellationToken?.Cancel()))
{
await StartScanNative();
await Task.Delay(ScanTimeout, _scanCancellationToken.Token);
CleanUpScan();
}
}
catch
{
}
}
public void CleanUpScan()
{
StopScanNative();
if (_scanCancellationToken != null)
{
_scanCancellationToken.Dispose();
_scanCancellationToken = null;
}
IsScanning = false;
}
public void StopScanNative()
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
{
_bluetoothAdapter.BluetoothLeScanner?.StopScan(pendingIntent);
}
}
public Task StartScanNative()
{
if (Build.VERSION.SdkInt > BuildVersionCodes.Lollipop)
{
Intent receiverIntent = new Intent(Application.Context, typeof(Api21BleScanCallback));
receiverIntent.SetAction("com.company.ACTION_FOUND");
pendingIntent = PendingIntent.GetBroadcast(Application.Context, 1, receiverIntent, PendingIntentFlags.UpdateCurrent);
_bluetoothAdapter.BluetoothLeScanner.StartScan(null, null, pendingIntent);
}
return Task.FromResult(true);
}
[BroadcastReceiver]
[IntentFilter(new[] { "com.company.ACTION_FOUND" })]
public class Api21BleScanCallback : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
var action = intent.Action;
if (intent.HasExtra(BluetoothLeScanner.ExtraListScanResult))
{
var x = intent.GetParcelableArrayListExtra(BluetoothLeScanner.ExtraListScanResult);
// Parse necessary data here
}
}
}
}
public interface IBLEScan
{
int ScanTimeout { get; set; }
bool IsScanning { get;}
Task StartScanning(CancellationToken cancellationToken = default(CancellationToken));
}
Я не уверен, какой объект вызывает эту ошибку. Я также реализовал сканирование в сервисе, но ошибка та же. Само сканирование выполняется в задаче, и объект BLEscan лениво инициализируется.
public async override void OnCreate()
{
base.OnCreate();
var adapter = LazyPodScanner.Current;
var task = Task.Run(async () =>
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await adapter.StartScanning(cancellationTokenSource.Token);
await Task.Delay(1000);
}
}, cancellationTokenSource.Token);
}
Кроме того, иногда также возникает ошибка переполнения глобальной справочной таблицы.
Реализация LLE-инициализированного сканирования BLE:
public class LazyPodScanner
{
static readonly Lazy<IBLEScan> Implementation = new Lazy<IBLEScan>(CreateImplementation, System.Threading.LazyThreadSafetyMode.PublicationOnly);
static IBLEScan CreateImplementation()
{
return new BLEScan();
}
public static IBLEScan Current
{
get
{
var val = Implementation.Value;
if (val == null)
{
throw NotImplementedInReferenceAssembly();
}
return val;
}
}
internal static Exception NotImplementedInReferenceAssembly()
{
return new NotImplementedException("Lazy Initialization of Scanner failed");
}
}