是否可以在FileProvider中提供一个实际上不存在的压缩文件?

时间:2019-01-01 18:22:43

标签: android-intent android-fileprovider

背景

我希望能够通过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的所有功能,我需要一个真实文件:

问题

  1. 是否可以提供一个不存在的文件,但实际上是另一个文件的压缩文件?也许是压缩文件流?

  2. 如果这不可能,那么有什么办法可以避免那些垃圾文件?这意味着我可以确定删除过去共享的压缩文件是安全的吗?

  3. 如果什至不可能,我该如何确定何时可以删除它们?只是将它们放在缓存文件夹中?我记得,操作系统并不能很好地自动处理缓存文件夹,并在需要时删除旧文件。还是对吗?

1 个答案:

答案 0 :(得分:0)

是的,有可能。

  1. 将FileProvider复制到您的代码中(您需要使用它来使用某些私有方法-使它们受保护)。创建扩展FileProvider的类。

  2. public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)中,使用ParcelFileDescriptor.createReliablePipe()(或对于较旧的android使用ParcelFileDescriptor.createPipe())来创建管道和一对ParcelFileDescriptor:readFd和writeFd)。

  3. 创建一个单独的线程,然后使用其压缩并写入文件以写入writeFd FileDescriptor。

  4. 返回另一个要读取的ParcelFileDescriptor(readFd)。

我的实现在这里: https://github.com/Babay88/AndroidCodeSamplesB/blob/master/ShareZipped/src/main/java/ru/babay/codesamples/sharezip/

使用我的命令的示例代码:

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);
        }
    }
}