应用程序KDE Connect允许通过 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) {
}
}