为什么MtpDevice.ImportFile总是失败?

时间:2019-05-16 11:32:45

标签: c# android xamarin import mtp

我正在修改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;
}

我找不到一个显示此功能的示例。我在这里看到一篇文章说该文件必须导入到外部缓存文​​件夹,而另一篇文章则说第二个参数应包括文件名,但都不起作用。

为此,我一直在拔头发-救命!

1 个答案:

答案 0 :(得分:1)

因此,感谢SushiHangover既向我指出了这个问题,又使我对logcat的乐趣睁开了眼睛。答案是语句

  

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

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

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

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

  

destPath 字符串:文件传输目标的路径。该路径应位于Environment.getExternalStorageDirectory()定义的外部存储中。此值不得为null。

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