c# - 为什么 MtpDevice.ImportFile 总是失败?
问题描述
我正在修改一个现有的应用程序,在 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 String : 文件传输目的地的路径。此路径应位于 Environment.getExternalStorageDirectory() 定义的外部存储中。此值不得为空。
对我来说,这根本不清楚。
推荐阅读
- python - 从字典列表聚合时间序列(Python)
- php - 在 php 数组上转换 mws 结果 .csv
- c# - 即使包含不记名令牌,Netcore API 也会返回 401
- java - 在eclipse上发布tomcat v7.0时出错
- javascript - 从节点 js 调用静态 javascript 文件的函数
- node.js - 部署到heroku应用程序期间的NodeJS应用程序错误
- r - 引导 A/B 测试结果(每个访问者向量的收入)
- c# - LINQ - 从数组中删除数据库表中的行
- php - Wordpress Plesk 真正的 cron 作业 - 不是每 15 分钟运行一次
- c++ - 无法使用 opencv_contrib 模块完成“make -j7”