我希望能够通过FileProvider将某些文件(通过发送意图)共享为单个压缩文件。
为此,您要做的就是添加ArrayList<Uri>
作为参数,例如:
ArrayList<Uri> uris = MyFileProvider.prepareFileProviderFiles(...)
sharingIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
FileProvider可用于将真实文件传送到外部应用程序。
我不想在我的应用程序中留有一些垃圾文件(压缩文件,仅用于共享),以防万一,如果使用它们的应用程序由于某种原因而完成,崩溃或停止
根据FileProvider的API,我应该实现真实的文件处理:
默认情况下,FileProvider自动返回 与content:// Uri关联的文件的ParcelFileDescriptor。至 获取ParcelFileDescriptor,调用ContentResolver.openFileDescriptor。 要覆盖此方法,您必须提供自己的 FileProvider。
因此它返回一个ParcelFileDescriptor,但是根据创建ParcelFileDescriptor的所有功能,我需要一个真实文件:
是否可以提供一个不存在的文件,但实际上是另一个文件的压缩文件?也许是压缩文件流?
如果这不可能,那么有什么办法可以避免那些垃圾文件?这意味着我可以确定删除过去共享的压缩文件是安全的吗?
如果什至不可能,我该如何确定何时可以删除它们?只是将它们放在缓存文件夹中?我记得,操作系统并不能很好地自动处理缓存文件夹,并在需要时删除旧文件。还是对吗?
答案 0 :(得分:0)
是的,有可能。
将FileProvider复制到您的代码中(您需要使用它来使用某些私有方法-使它们受保护)。创建扩展FileProvider的类。
在public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
中,使用ParcelFileDescriptor.createReliablePipe()(或对于较旧的android使用ParcelFileDescriptor.createPipe())来创建管道和一对ParcelFileDescriptor:readFd和writeFd)。
创建一个单独的线程,然后使用其压缩并写入文件以写入writeFd FileDescriptor。
返回另一个要读取的ParcelFileDescriptor(readFd)。
使用我的命令的示例代码:
public void doShare(File file, Context context){
String packageName = context.getPackageName();
Uri uri = ZipableFileProvider.getUriForFile(context, packageName + ".provider", file, true);
String name = file.getName() + ".zip";
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("application/zip");
sendIntent.putExtra(Intent.EXTRA_SUBJECT, name);
sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
sendIntent.putExtra(Intent.EXTRA_TEXT, "sample file description");
sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent chooserIntent = Intent.createChooser(sendIntent, "chooser title");
context.startActivity(chooserIntent);
}
编辑:以下代码: (但您最好在github上检查实现,因为该类扩展了一些自定义的FileProvider)
/**
* File provider intended to zip files on-the-fly.
* It can send files (just like FileProvider) and zip files.
*
* Use {@link ZipableFileProvider#getUriForFile(Context, String, File, boolean)}
* to create an URI.
*
*/
//@SuppressWarnings("ALL")
public class ZipableFileProvider extends FileProvider {
static final String TAG = "ZipableFileProvider";
/**
* Just like {@link FileProvider#getUriForFile}, but will create an URI for zipping wile while sending
* @param context
* @param authority
* @param file
* @param zipFile
* @return
*/
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
@NonNull File file, boolean zipFile) {
Uri uri = getUriForFile(context, authority, file);
if (zipFile) {
return new Uri.Builder()
.scheme(uri.getScheme())
.authority(uri.getAuthority())
.encodedPath(uri.getPath())
.encodedQuery("zip").build();
}
return uri;
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
File file = getFileForUri(uri);
// if file does not exist -- let parent class handle that
if (file.exists() && isZip(uri)) {
if (file.exists()) {
try {
return startZippedPipe(file);
} catch (IOException e) {
Log.e(TAG, "openFile: ", e);
}
}
}
return super.openFile(uri, mode);
}
private boolean isZip(@NonNull Uri uri) {
return "zip".equals(uri.getQuery());
}
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
// ContentProvider has already checked granted permissions
File file = mStrategy.getFileForUri(uri);
if (projection == null) {
projection = COLUMNS;
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = file.getName() + (isZip(uri) ? ".zip" : "");
} else if (OpenableColumns.SIZE.equals(col)) {
// return size of original file; zip-file might differ
cols[i] = OpenableColumns.SIZE;
values[i++] = file.length();
}
}
cols = copyOf(cols, i);
values = copyOf(values, i);
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
public static ParcelFileDescriptor startZippedPipe(File file) throws IOException {
ParcelFileDescriptor[] pipes = Build.VERSION.SDK_INT >= 19 ?
ParcelFileDescriptor.createReliablePipe() :
ParcelFileDescriptor.createPipe();
new Thread(() -> doZipFile(pipes[1], file)).start();
return pipes[0];
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private static ParcelFileDescriptor startZippedSocketPair(File file) throws IOException {
ParcelFileDescriptor[] pipes = ParcelFileDescriptor.createReliableSocketPair();
new Thread(() -> doZipFile(pipes[1], file)).start();
return pipes[0];
}
/**
* zips and sends a file to a ParcelFileDescriptor writeFd
*
* Note that some apps (like Telegram) receives the file at once.
* Other apps (like Gmail) open the file you share, read some kb and close it,
* and reopen it later (when you really send the email).
* So, it's OK if "Broken pipe" exception thrown.
*
* @param writeFd
* @param inputFile
*/
private static void doZipFile(ParcelFileDescriptor writeFd, File inputFile) {
long start = System.currentTimeMillis();
byte[] buf = new byte[1024];
int writtenSize = 0;
try (FileInputStream iStream = new FileInputStream(inputFile);
ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(writeFd.getFileDescriptor()))) {
zipStream.putNextEntry(new ZipEntry(inputFile.getName()));
int amount;
while (0 <= (amount = iStream.read(buf))) {
zipStream.write(buf, 0, amount);
writtenSize += amount;
}
zipStream.closeEntry();
zipStream.close();
iStream.close();
writeFd.close();
if (BuildConfig.DEBUG)
Log.d(TAG, "doZipFile: done. it took ms: " + (System.currentTimeMillis() - start));
} catch (IOException e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
writeFd.closeWithError(e.getMessage());
} catch (IOException e1) {
Log.e(TAG, "doZipFile: ", e1);
}
}
if (BuildConfig.DEBUG)
Log.d(TAG, "doZipFile: written: " + writtenSize, e);
}
}
}