【问题标题】:Why does MtpDevice.ImportFile always fail?为什么 MtpDevice.ImportFile 总是失败?
【发布时间】: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


【解决方案1】:

因此,感谢 SushiHangover 指出问题所在,并让我看到 logcat 的乐趣。答案是声明

文件必须导入外部缓存文​​件夹

绝对正确 - 但它必须实际上是外部的。

即使您没有物理外部媒体,GetExternalCacheDirs() 实际上也会返回一个文件夹,这对我来说似乎很疯狂,但您就是这样。

顺便说一句,目标路径也必须包含文件名。文档说:

destPath String:文件传输目的地的路径。此路径应位于 Environment.getExternalStorageDirectory() 定义的外部存储中。此值不得为空。

对我来说,这根本不清楚。

【讨论】:

  • 很高兴您找到了解决方案。 ?
猜你喜欢
  • 1970-01-01
  • 2018-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-23
  • 2018-03-08
相关资源
最近更新 更多