【发布时间】:2019-05-16 11:32:44
【问题描述】:
我正在修改一个现有的应用程序,它在 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;
}
我找不到显示此工作的单个示例。我在这里看到一篇文章说必须将文件导入外部缓存文件夹,还有一篇文章说第二个参数应该包含文件名,但这些都不起作用。
我一直在拔头发 - 救命!!
【问题讨论】:
-
不确定您尝试使用的实际目标路径,但尝试使用
ExternalCacheDir.AbsolutePath作为测试。 (即,该导入方法假设您将提供“外部存储”路径)。您还可以检查logcat以了解ImportFile方法报告的错误。 -
我以为我在使用 ExternalCacheDir,但日志显示:
ContextImpl - Failed to ensure directory: /storage/extSdCard/Android/data/com.myco.myapp/cacheAPP Log - Ensuring image path exists: /storage/emulated/0/Android/data/com.myco.myapp/cacheAPP Log - External cache folder at /storage/emulated/0/Android/data/com.myco.myapp/cache/imagesAPP Log - Attempting to download ID 689595157 to /storage/emulated/0/Android/data/com.myco.myapp/cache/images/DSC_0001.JPGFailed to find mounted volume for /storage/extSdCard/Android/data/com.myco.myapp/cache -
您在该插槽中安装了 SDCard 吗?如果
cache目录不存在,您必须创建它。您可以尝试CacheDir.AbsolutePath,因为它始终可用(自从我处理该api以来已经有一段时间了,但我知道ImportFile为dest.path调用“外部存储”,我记得必须ImportFile文件首先到外部存储,然后复制到我的应用程序的内部存储(这是我的应用程序对最终目标的要求。我相信还有其他 SO 的答案也表明......) -
谢谢,我认为无论如何使用 ExxternalCacheDir 都可以。该设备没有安装外部 SD - 它是由我们的客户指定的,他们有相当特定的安全要求。我会弹出一个,看看是否会改变路径。
-
感谢您的帮助。我放了一张微型 SD 卡,
GetExternalCacheDirs()然后给了我两个文件夹。第二个是 /storage/extSdCard/Android/data/com.myco.myapp/cache 在解决另一个问题后,我已经成功下载了文件:)
标签: c# android xamarin import mtp