我正在修改Xamarin中用C#编写的,专门为Android构建的现有应用程序。
现有的应用程序会在相机的wifi卡中搜索文件,然后通过HTTP界面下载文件。我正在尝试让它使用USB电缆束缚的方式下载文件,但无法下载文件。
我正在使用MtpDevice尝试下载,但是ImportFile函数始终失败。不幸的是,它从不抛出异常或提供任何有关原因的有用信息。
下面的代码几乎显示了我在做什么,尽管我删除了很多与问题无关的东西。
在修改现有应用程序时,代码有些复杂,因为有一种方法可以找到文件,然后后台线程调用另一种方法进行实际下载,因此用户可以在下载文件时继续工作。
另外,我在GetFileListAsync中显示的某些代码实际上是在其他方法中使用的,因为它们已被多次使用...我没有包括权限的内容,因为它们似乎都可以正常工作。
这是首先被调用的方法,用于查找对象句柄的列表。
public async Task<List<MyFileClass>> GetFileListAsync()
{
var results = new List<MyFileClass>();
UsbDeviceConnection usbDeviceConnection = null;
MtpDevice mtpDevice = null;
UsbDevice usbDevice = null;
// all this stuff works as expected...
UsbManager usbManager = (UsbManager)Android.App.Application.Context.GetSystemService(Context.UsbService);
try
{
if (usbManager.DeviceList != null && usbManager.DeviceList.Count > 0)
{
foreach (var usbAccessory in usbManager.DeviceList)
{
var device = usbAccessory.Value;
usbDevice = usbAccessory.Value;
break; // should only ever be one, but break here anyway
}
}
}
catch (Exception ex)
{
Log.Error(ex, "Error getting USB devices");
}
if (usbDevice == null)
{
Log.Information("ConnectedDevice is null");
return false;
}
if (!UsbManager.HasPermission(usbDevice))
{
Log.Information("Requesting permission must have failed");
return false;
}
try
{
usbDeviceConnection = UsbManager.OpenDevice(usbDevice);
}
catch (Exception ex)
{
Log.Error(ex, "opening usb connection");
return false;
}
try
{
mtpDevice = new MtpDevice(usbDevice);
}
catch (Exception ex)
{
Log.Error(ex, "creating mtpdevice");
return false;
}
try
{
mtpDevice.Open(usbDeviceConnection);
}
catch (Exception ex)
{
Log.Error(ex, " opening mtpdevice");
usbDeviceConnection.Close();
return false;
}
// then start looking for files
var storageUnits = mtpDevice.GetStorageIds();
if (storageUnits == null || storageUnits.Length == 0)
{
Log.Information("StorageUnits is empty");
mtpDevice.Close();
usbDeviceConnection.Close();
return false;
}
foreach (var storageUnitId in storageUnits)
{
var storageUnit = mtpDevice.GetStorageInfo(storageUnitId);
if (storageUnit != null)
{
// recurse directories to get list of files
await RecurseMTPDirectories(results, storageUnitId, docketId, docket, db);
}
}
}
private async Task RecurseMTPDirectories(List<MyFileClass> files, int storageUnitId, string parentFolder = "\\", int parentHandle = -1)
{
var results = new List<FlashAirFile>();
var directoryObjects = new List<MtpObjectInfo>();
var objectHandles = mtpDevice.GetObjectHandles(storageUnitId, 0, parentHandle);
if (objectHandles != null && objectHandles.Length > 0)
{
foreach (var objectHandle in objectHandles)
{
MtpObjectInfo objectInfo = mtpDevice.GetObjectInfo(objectHandle);
if (objectInfo != null)
{
if (objectInfo.Format == MtpFormat.Association)
{
// add to the list to recurse into, but do this at the end
directoryObjects.Add(objectInfo);
}
else
{
// it' a file - add it only if it's one we want
try
{
var file = new MyFileClass()
{
TotalBytes = objectInfo.CompressedSize,
FileNameOnCard = objectInfo.Name,
DirectoryOnCard = parentFolder,
MTPHandle = objectHandle
};
}
catch (Exception e)
{
Log.Error(e, " trying to create MTP FlashAirFile");
}
}
}
}
}
foreach (var directoryObject in directoryObjects)
{
string fullname = parentFolder + directoryObject.Name;
await RecurseMTPDirectories(files, storageUnitId, $"{fullname}\\", directoryObject.ObjectHandle);
}
return results;
}
我知道可以一次获得所有句柄,而不是通过文件夹递归,但是现在我正在做,就像旧代码一样。
MyFileClass对象的列表被添加到SQLite数据库中,然后后台线程一次将它们从队列中取出并调用DownloadFileAsync以获取每个文件。
此方法使用与GetFileListAsync方法中使用的设备相同的设备,并且它还会检查权限是否仍然可用。
public async Task<int> DownloadFileAsync(MyFileClass file, string destination)
{
int receivedBytes = 0;
int objectHandle = file.MTPHandle;
connectedDevice = await GetAttachedDevice();
if (connectedDevice == null || !UsbManager.HasPermission(connectedDevice))
return receivedBytes;
if (!await OpenAttachedDevice())
return receivedBytes;
var rootFolder = await FileSystem.Current.GetFolderFromPathAsync(destination);
var localFile = rootFolder.Path;
try
{
Log.Information($"Attempting to download ID {objectHandle} to {localFile}");
// try downloading just using path
bool success = mtpDevice.ImportFile(objectHandle, localFile);
if (!success)
{
// try it with a / on the end of the path
localFile += '/';
Log.Information($"Attempting to download ID {file.DownloadManagerId} to {localFile}");
success = mtpDevice.ImportFile(objectHandle, localFile);
}
if (!success)
{
// try it with the filename on the end of the path as well
localFile += file.FileNameOnSdCard;
Log.Information($"Attempting to download ID {file.DownloadManagerId} to {localFile}");
success = mtpDevice.ImportFile(objectHandle, localFile);
}
if (!success)
{
throw new Exception($"mtpDevice.ImportFile failed for {file.FileNameOnSdCard}");
}
// do stuff here to handle success
}
catch (Exception ex) when (ex is OperationCanceledException || ex is TaskCanceledException)
{
// do some other stuff here in the database
//rethrow the exception so it can be handled further up the chain.
throw;
}
return receivedBytes;
}
我找不到一个显示此功能的示例。我在这里看到过一篇文章,说该文件必须导入到外部缓存文件夹,而一篇文章说第二个参数应该包括文件名,但这些都不起作用。
我一直在为此拔头发-救命啊!
最佳答案
因此,感谢SushiHangover既指出了我的问题,也让我看到了logcat带来的乐趣。答案是声明
该文件必须导入到外部缓存文件夹
是绝对正确的-但它实际上必须是外部的。
即使您没有物理上的外部媒体,GetExternalCacheDirs()实际上也会返回一个文件夹,这对我来说似乎很疯狂,但是您到了那里。
顺便说一句,目标路径必须包含文件名也是正确的。该文件说:
destPath字符串:文件传输目标的路径。此路径应位于Environment.getExternalStorageDirectory()定义的外部存储中。此值不得为null。
对我来说,这根本不清楚。