1

应用程序KDE Con​​nect允许通过 SFTP 从台式计算机远程浏览 Android 设备。从 Android 4.4 开始,开发人员不再直接通过文件系统对 SD 卡进行写入权限。所以我正在尝试使用存储访问框架(DocumentFile 等)移植 SFTP 模块

我正在使用 and 获得许可,Intent.ACTION_OPEN_DOCUMENT_TREE并将FLAG_GRANT_WRITE_URI_PERMISSION上下文传递给我的班级。

我能够在班级内的 SD 卡上创建新的空文件、重命名文件和删除文件,因此我相信我获得了必要的权限。但是,传输文件会导致创建一个空文件(0 字节)。我可以看到传输需要一定的时间,并且在桌面端有一个进度条,所以它不仅仅是中止。

这是来自 Apache SSHD 库的 SftpSubsystem 类的相关部分(请参阅此处的文档),并附有我自己的评论来解释发生了什么:

public class SftpSubsystem implements Command, Runnable, SessionAware, FileSystemAware {
    // This method receives a buffer from an InputStream and processes it
    // according to its type. In this situation, it would also contain
    // a block of the file being transferred (4096 bytes)
    protected void process(Buffer buffer) {
        int type = buffer.getByte();

        switch (type) {
            case WRITE:
                FileHandle fh = getHandleFromString(buffer.getString());
                long offset = buffer.getLong();
                byte[] data = buffer.getBytes();

                fh.write(data, offset);
                break;
            // other cases
        }   
    }

    // This class is a handle to a file (duh) with
    // an OutputStream to write and InputStream to read
    protected static class FileHandle {
        SshFile file;
        OutputStream output;
        long outputPos;
        InputStream input;
        long inputPos;

        // Method called inside process()
        public void write(byte[] data, long offset) throws IOException {
            if (output != null && offset != outputPos) {
                IoUtils.closeQuietly(output);
                output = null;
            }
            if (output == null) {
                // This is called once at the start of the transfer.
                // This is what I think I need to rewrite to make
                // it work with DocumentFile objects.
                output = file.createOutputStream(offset);
            }
            output.write(data);
            outputPos += data.length;
        }
    }
}

我想重写的 createOutputStream() 的原始实现,因为 RandomAccessFile 不适用于 DocumentFile:

public class NativeSshFile implements SshFile {
    private File file;

    public OutputStream createOutputStream(final long offset)
            throws IOException {

        // permission check
        if (!isWritable()) {
            throw new IOException("No write permission : " + file.getName());
        }

        // move to the appropriate offset and create output stream
        final RandomAccessFile raf = new RandomAccessFile(file, "rw");
        try {
            raf.setLength(offset);
            raf.seek(offset);

            // The IBM jre needs to have both the stream and the random access file
            // objects closed to actually close the file
            return new FileOutputStream(raf.getFD()) {
                public void close() throws IOException {
                    super.close();
                    raf.close();
                }
            };
        } catch (IOException e) {
            raf.close();
            throw e;
        }
    }
}

我尝试实现它的方法之一:

class SimpleSftpServer {
    static class AndroidSshFile extends NativeSshFile {

        // This is the DocumentFile that is stored after
        // create() created the empty file
        private DocumentFile docFile;

        public OutputStream createOutputStream(final long offset) throws IOException {
            // permission check
            if (!isWritable()) {
                throw new IOException("No write permission : " + docFile.getName());
            }

            ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(docFile.getUri(), "rw");
            FileDescriptor fd = pfd.getFileDescriptor();
            try {
                android.system.Os.lseek(fd, offset, OsConstants.SEEK_SET);
            } catch (ErrnoException e) {
                Log.e("SimpleSftpServer", "" + e);
                return null;
            }

            return new FileOutputstream(fd, offset);
        }
    }
}

我也尝试了一个简单的(偏移被忽略,但这只是一个测试):

    public OutputStream createOutputStream(final long offset) throws IOException {
        // permission check
        if (!isWritable()) {
            throw new IOException("No write permission : " + docFile.getName());
        }

        return context.getContentResolver().openOutputStream(docFile.getUri());
    }

我还尝试使用 FileChannel 并刷新和同步 FileOutputStream。

知道为什么我最终得到一个空文件吗?

编辑:这是一个小例子,我只是从现有文件中写入一个新文件。它有效,但这不是我真正想要做的(参见上面的代码),但我想我会提供一个示例来表明我了解如何写入 OutputStream 的基础知识。

private void createDocumentFileFromFile() {
        File fileToRead = new File("/storage/0123-4567/lady.m4a");
        File fileToWrite = new File("/storage/0123-4567/lady2.m4a");
        File dir = fileToWrite.getParentFile();
        DocumentFile docDir = DocumentFile.fromTreeUri(context, SimpleSftpServer.externalStorageUri);

        try {
            DocumentFile createdFile = docDir.createFile(null, fileToWrite.getName());
            Uri uriToRead = Uri.fromFile(fileToRead);
            InputStream in = context.getContentResolver().openInputStream(uriToRead);
            OutputStream out = context.getContentResolver().openOutputStream(createdFile.getUri());
            try {
                int nbOfBytes = 0;
                final int BLOCKSIZE = 4096;
                byte[] bytesRead = new byte[BLOCKSIZE];
                while (true) {
                    nbOfBytes = in.read(bytesRead);
                    if (nbOfBytes == -1) {
                        break;
                    }
                    out.write(bytesRead, 0, nbOfBytes);
                }
            } finally {
                in.close();
                out.close();
            }
        } catch (IOException e) {

        }
    }
4

1 回答 1

0

“使用 ACTION_OPEN_DOCUMENT_TREE 时,您的应用程序只能访问用户选择的目录中的文件。您无权访问驻留在此用户选择的目录之外的其他应用程序的文件。

这种用户控制的访问允许用户准确地选择他们愿意与您的应用共享的内容。”

这意味着,您只能读取/写入/删除已存在文件或所选目录的子目录中的内容/元数据,用户接受“舒适”的范围。

实际上,用户在此文件夹中为 ea 文件/子目录授予了 Uri 列表的权限,有单独的 uri 权限。

现在例如,如果我将尝试使用 DocumentFile 在选定的 Uri 中创建新文件,但如果我尝试将新数据输出到该文件,我将失败,因为用户没有授予写入这个新创建文件的权限。

他只授予在目录路径级别写入,意味着在这里创建新文件。

当您尝试将文件移动/传输到没有用户许可的其他路径时,也会发生同样的情况。

路径可以是文件夹或文件,对于每个新路径,用户需要授予新访问权限。

移动文件 = 新路径写入刚刚创建的文件 = 新路径

于 2021-10-16T17:03:22.630 回答