是否可以在 FileProvider 中提供一个实际上不存在的文件的压缩文件?
Is it possible to provide a zipped file in FileProvider, of a file that doesn't really exist?
背景
我希望能够通过 FileProvider 将一些文件(通过发送意图)作为单个压缩文件进行共享,但无需实际创建此文件。
为此,您所做的就是添加 ArrayList<Uri>
作为参数,例如:
ArrayList<Uri> uris = MyFileProvider.prepareFileProviderFiles(...)
sharingIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
问题
FileProvider 可用于将真实 文件传送到外部应用程序。
我不希望我的应用程序中有一些垃圾文件(压缩文件,仅用于共享)毫无目的地存在,以防使用它们的应用程序已完成、崩溃或停止运行一些原因。
我发现了什么
根据 FileProvider 的 API,我应该实现真实的文件处理:
By default, FileProvider automatically returns the
ParcelFileDescriptor for a file associated with a content:// Uri. To
get the ParcelFileDescriptor, call ContentResolver.openFileDescriptor.
To override this method, you must provide your own subclass of
FileProvider.
所以它 returns 一个 ParcelFileDescriptor,但是根据创建 ParcelFileDescriptor 的所有函数,我需要一个真实的文件:
问题
是否可以让它提供一个实际上并不存在的文件,但实际上是另一个 file/s 的压缩文件?也许是压缩文件的流?
如果这不可能,我有什么办法可以避免这些垃圾文件吗?这意味着我可以确定删除我过去共享的压缩 file/s 是安全的吗?
如果连这都做不到,我如何决定何时可以删除它们?只是将它们放在缓存文件夹中?我记得 OS 并没有真正自动很好地处理缓存文件夹,在需要时删除旧文件。还不对吗?
由于我尝试了很长时间,所以我只会接受一个我可以测试自己的工作方案。
编辑:基于答案, I've made a tiny sample here。这是它的代码:
清单
...
<provider
android:name=".ZipFilesProvider"
android:authorities="${applicationId}.zip_file_provider"
android:exported="false"
android:grantUriPermissions="true"/>
ZipFilesProvider.kt
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import android.provider.OpenableColumns
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.concurrent.thread
class ZipFilesProvider : ContentProvider() {
override fun onCreate() = true
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?) = 0
override fun delete(uri: Uri, arg1: String?, arg2: Array<String>?) = 0
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun getType(uri: Uri) = ZIP_FILE_MIME_TYPE
override fun attachInfo(context: Context, info: ProviderInfo) {
super.attachInfo(context, info)
authority = info.authority
}
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
val filesPathsToCompress = getFilesPathsToCompress(uri)
filesPathsToCompress.forEach { if (!it.exists()) throw FileNotFoundException(it.absolutePath) }
val pipes = if (Build.VERSION.SDK_INT >= 19) ParcelFileDescriptor.createReliablePipe() else ParcelFileDescriptor.createPipe()
thread {
val writeFd = pipes[1]
try {
ZipOutputStream(FileOutputStream(writeFd.fileDescriptor)).use { zipStream: ZipOutputStream ->
filesPathsToCompress.forEach {
zipStream.putNextEntry(ZipEntry(it.name))
FileInputStream(it).copyTo(zipStream)
zipStream.closeEntry()
}
zipStream.close()
writeFd.close()
}
} catch (e: IOException) {
e.printStackTrace()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
writeFd.closeWithError(e.message)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
return pipes[0]
}
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
val filesPathsToCompress = getFilesPathsToCompress(uri)
val fileToCompressInto = uri.encodedPath!!.substringAfter("/")
val columnNames = projection ?: arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
val ret = MatrixCursor(columnNames)
val values = arrayOfNulls<Any>(columnNames.size)
for (i in columnNames.indices) {
when (columnNames[i]) {
MediaStore.MediaColumns.DISPLAY_NAME -> values[i] = fileToCompressInto
MediaStore.MediaColumns.SIZE -> {
var totalFilesSize = 0L
filesPathsToCompress.forEach { totalFilesSize += it.length() }
values[i] = totalFilesSize
}
}
}
ret.addRow(values)
return ret
}
companion object {
lateinit var authority: String
const val ZIP_FILE_MIME_TYPE = "application/zip"
private fun getFilesPathsToCompress(uri: Uri): HashSet<File> {
val filesPathsToCompress = HashSet<File>(uri.queryParameterNames.size)
uri.queryParameterNames.forEach {
val path = uri.getQueryParameters(it)[0]// alternative: String(Base64.decode(uri.getQueryParameters(it)[0], Base64.URL_SAFE))
filesPathsToCompress.add(File(path))
}
return filesPathsToCompress
}
fun prepareFilesToShareAsZippedFile(filesToCompress: Collection<String>, zipFileName: String): Uri {
val builder = Uri.Builder().scheme("content").authority(authority).encodedPath(zipFileName)
for ((index, filePath) in filesToCompress.withIndex())
builder.appendQueryParameter(index.toString(), filePath)// alternative: String(Base64.encode(filePath.toByteArray(), Base64.URL_SAFE)))
return builder.build()
}
}
}
MainActivity.kt
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import java.io.File
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val installedPackages = packageManager.getInstalledPackages(0)
val filesToCompress = ArrayList<String>()
val maxFiles = 3
var maxTotalSize = 10 * 1024L * 1024L
for (installedPackage in installedPackages) {
val filePath = installedPackage.applicationInfo.publicSourceDir
val file = File(filePath)
val fileSize = file.length()
if (maxTotalSize - fileSize >= 0) {
maxTotalSize-= fileSize
filesToCompress.add(filePath)
if (filesToCompress.size >= maxFiles)
break
}
}
val uri = ZipFilesProvider.prepareFilesToShareAsZippedFile(filesToCompress, "someZipFile.zip")
val intent = Intent(Intent.ACTION_SEND).setType(ZipFilesProvider.ZIP_FILE_MIME_TYPE).putExtra(Intent.EXTRA_STREAM, uri)
startActivity(Intent.createChooser(intent, ""))
}
}
是的,有可能。
将 FileProvider 复制到您的代码中(您需要它来使用一些私有方法 - 使它们受到保护)。创建扩展 FileProvider 的 class。
在public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
中使用ParcelFileDescriptor.createReliablePipe()(或ParcelFileDescriptor.createPipe()用于旧的android)创建一个管道和一对ParcelFileDescriptor为此:readFd 和 writeFd).
创建一个单独的线程并使用它来压缩和写入文件到 writeFd FileDescriptor。
Return 另一个要读取的 ParcelFileDescriptor (readFd)。
编辑:代码如下:
(但是你最好检查 github 的实现,因为 class 扩展了一点定制的 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);
}
}
}
您可以对一个 zip 文件中的多个文件执行相同的操作。您只需要:
创建可用于提取文件列表的 Uri。 (注意:如果你在Uri查询中使用'.',你会失败。我不知道为什么。我在我的代码中对文件名进行了Base64编码)
将所有 zip 文件写入一个 zip 流。
中实施
示例activity 共享&zip 文件(一个或多个)的代码:
https://github.com/Babay88/AndroidCodeSamplesB/blob/master/sharedzipexample/src/main/java/ru/babay/sharedzipexample/MainActivity.java
背景
我希望能够通过 FileProvider 将一些文件(通过发送意图)作为单个压缩文件进行共享,但无需实际创建此文件。
为此,您所做的就是添加 ArrayList<Uri>
作为参数,例如:
ArrayList<Uri> uris = MyFileProvider.prepareFileProviderFiles(...)
sharingIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
问题
FileProvider 可用于将真实 文件传送到外部应用程序。
我不希望我的应用程序中有一些垃圾文件(压缩文件,仅用于共享)毫无目的地存在,以防使用它们的应用程序已完成、崩溃或停止运行一些原因。
我发现了什么
根据 FileProvider 的 API,我应该实现真实的文件处理:
By default, FileProvider automatically returns the ParcelFileDescriptor for a file associated with a content:// Uri. To get the ParcelFileDescriptor, call ContentResolver.openFileDescriptor. To override this method, you must provide your own subclass of FileProvider.
所以它 returns 一个 ParcelFileDescriptor,但是根据创建 ParcelFileDescriptor 的所有函数,我需要一个真实的文件:
问题
是否可以让它提供一个实际上并不存在的文件,但实际上是另一个 file/s 的压缩文件?也许是压缩文件的流?
如果这不可能,我有什么办法可以避免这些垃圾文件吗?这意味着我可以确定删除我过去共享的压缩 file/s 是安全的吗?
如果连这都做不到,我如何决定何时可以删除它们?只是将它们放在缓存文件夹中?我记得 OS 并没有真正自动很好地处理缓存文件夹,在需要时删除旧文件。还不对吗?
由于我尝试了很长时间,所以我只会接受一个我可以测试自己的工作方案。
编辑:基于答案
清单
...
<provider
android:name=".ZipFilesProvider"
android:authorities="${applicationId}.zip_file_provider"
android:exported="false"
android:grantUriPermissions="true"/>
ZipFilesProvider.kt
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import android.provider.OpenableColumns
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.concurrent.thread
class ZipFilesProvider : ContentProvider() {
override fun onCreate() = true
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?) = 0
override fun delete(uri: Uri, arg1: String?, arg2: Array<String>?) = 0
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun getType(uri: Uri) = ZIP_FILE_MIME_TYPE
override fun attachInfo(context: Context, info: ProviderInfo) {
super.attachInfo(context, info)
authority = info.authority
}
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
val filesPathsToCompress = getFilesPathsToCompress(uri)
filesPathsToCompress.forEach { if (!it.exists()) throw FileNotFoundException(it.absolutePath) }
val pipes = if (Build.VERSION.SDK_INT >= 19) ParcelFileDescriptor.createReliablePipe() else ParcelFileDescriptor.createPipe()
thread {
val writeFd = pipes[1]
try {
ZipOutputStream(FileOutputStream(writeFd.fileDescriptor)).use { zipStream: ZipOutputStream ->
filesPathsToCompress.forEach {
zipStream.putNextEntry(ZipEntry(it.name))
FileInputStream(it).copyTo(zipStream)
zipStream.closeEntry()
}
zipStream.close()
writeFd.close()
}
} catch (e: IOException) {
e.printStackTrace()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
writeFd.closeWithError(e.message)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
return pipes[0]
}
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
val filesPathsToCompress = getFilesPathsToCompress(uri)
val fileToCompressInto = uri.encodedPath!!.substringAfter("/")
val columnNames = projection ?: arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
val ret = MatrixCursor(columnNames)
val values = arrayOfNulls<Any>(columnNames.size)
for (i in columnNames.indices) {
when (columnNames[i]) {
MediaStore.MediaColumns.DISPLAY_NAME -> values[i] = fileToCompressInto
MediaStore.MediaColumns.SIZE -> {
var totalFilesSize = 0L
filesPathsToCompress.forEach { totalFilesSize += it.length() }
values[i] = totalFilesSize
}
}
}
ret.addRow(values)
return ret
}
companion object {
lateinit var authority: String
const val ZIP_FILE_MIME_TYPE = "application/zip"
private fun getFilesPathsToCompress(uri: Uri): HashSet<File> {
val filesPathsToCompress = HashSet<File>(uri.queryParameterNames.size)
uri.queryParameterNames.forEach {
val path = uri.getQueryParameters(it)[0]// alternative: String(Base64.decode(uri.getQueryParameters(it)[0], Base64.URL_SAFE))
filesPathsToCompress.add(File(path))
}
return filesPathsToCompress
}
fun prepareFilesToShareAsZippedFile(filesToCompress: Collection<String>, zipFileName: String): Uri {
val builder = Uri.Builder().scheme("content").authority(authority).encodedPath(zipFileName)
for ((index, filePath) in filesToCompress.withIndex())
builder.appendQueryParameter(index.toString(), filePath)// alternative: String(Base64.encode(filePath.toByteArray(), Base64.URL_SAFE)))
return builder.build()
}
}
}
MainActivity.kt
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import java.io.File
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val installedPackages = packageManager.getInstalledPackages(0)
val filesToCompress = ArrayList<String>()
val maxFiles = 3
var maxTotalSize = 10 * 1024L * 1024L
for (installedPackage in installedPackages) {
val filePath = installedPackage.applicationInfo.publicSourceDir
val file = File(filePath)
val fileSize = file.length()
if (maxTotalSize - fileSize >= 0) {
maxTotalSize-= fileSize
filesToCompress.add(filePath)
if (filesToCompress.size >= maxFiles)
break
}
}
val uri = ZipFilesProvider.prepareFilesToShareAsZippedFile(filesToCompress, "someZipFile.zip")
val intent = Intent(Intent.ACTION_SEND).setType(ZipFilesProvider.ZIP_FILE_MIME_TYPE).putExtra(Intent.EXTRA_STREAM, uri)
startActivity(Intent.createChooser(intent, ""))
}
}
是的,有可能。
将 FileProvider 复制到您的代码中(您需要它来使用一些私有方法 - 使它们受到保护)。创建扩展 FileProvider 的 class。
在
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
中使用ParcelFileDescriptor.createReliablePipe()(或ParcelFileDescriptor.createPipe()用于旧的android)创建一个管道和一对ParcelFileDescriptor为此:readFd 和 writeFd).创建一个单独的线程并使用它来压缩和写入文件到 writeFd FileDescriptor。
Return 另一个要读取的 ParcelFileDescriptor (readFd)。
编辑:代码如下: (但是你最好检查 github 的实现,因为 class 扩展了一点定制的 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);
}
}
}
您可以对一个 zip 文件中的多个文件执行相同的操作。您只需要:
创建可用于提取文件列表的 Uri。 (注意:如果你在Uri查询中使用'.',你会失败。我不知道为什么。我在我的代码中对文件名进行了Base64编码)
将所有 zip 文件写入一个 zip 流。
示例activity 共享&zip 文件(一个或多个)的代码: https://github.com/Babay88/AndroidCodeSamplesB/blob/master/sharedzipexample/src/main/java/ru/babay/sharedzipexample/MainActivity.java