添加依赖
implementation 'org.apache.commons:commons-compress:1.23.0'
implementation 'com.github.junrar:junrar:7.5.4'//解压rar
implementation 'org.tukaani:xz:1.9'//解压.7z文件需要
下面 FileUtil.kt 代码中用到了 Context 的拓展方法 getAppDir
fun Context.getAppDir() = getExternalFilesDir("")!!.absolutePath
解压文件的代码 FileUtil.kt
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import android.util.Log
import com.github.junrar.Junrar
import org.apache.commons.compress.archivers.ArchiveEntry
import org.apache.commons.compress.archivers.ArchiveStreamFactory
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry
import org.apache.commons.compress.archivers.sevenz.SevenZFile
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipFile
import java.io.Closeable
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.util.Enumeration
private const val ZIP = ".zip"
private const val RAR = ".rar"
private const val SEVEN_Z = ".7z"
object FileUtil {
private const val TAG = "FileUtil"
val MIME_TYPES = arrayOf(
"application/zip", //压缩比例小,耗时少
"application/rar",
"application/x-7z-compressed", //压缩比例大,耗时多
)
private fun getFileName(context: Context, uri: Uri): String? {
var filename: String? = null
val cursor = context.contentResolver.query(uri, null, null, null, null)
if (cursor != null) {
if (cursor.moveToFirst()) {
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex > -1) {
filename = cursor.getString(nameIndex)
Log.i(TAG, "文件名称 $filename")
}
}
cursor.close()
}
return filename
}
/**
* 解压文件
*
* @return 返回解压后的文件所在的目录
*/
fun extractFiles(context: Context, uri: Uri): String? {
val filename = getFileName(context, uri)
Log.i(TAG, "解压文件 $filename")
if (filename != null) {
context.contentResolver.openInputStream(uri)?.use { istream ->
//由于Android系统对外部存储访问的限制,使用系统框架选取文件,
//复制到APP专用的外部存储目录中(即 context.getExternalFilesDir("")!!.absolutePath),然后进行解压
val copyPath = context.getAppDir() + "/" + filename
FileOutputStream(copyPath).use { ostream ->
istream.copyTo(ostream)
Log.i(TAG, "文件复制完毕 $copyPath")
//解压文件
val outDir = when {
filename.endsWith(ZIP) -> uncompressFile(context, copyPath, ZipFile::class.java, ZipArchiveEntry::class.java)
filename.endsWith(SEVEN_Z) -> uncompressFile(context, copyPath, SevenZFile::class.java, SevenZArchiveEntry::class.java)
filename.endsWith(RAR) -> unrar(context, copyPath)
else -> null
}
if (outDir != null) {
Log.i(TAG, "解压完毕 $outDir")
val deleteSuccess = File(copyPath).delete()
Log.i(TAG, "删除临时文件 $copyPath $deleteSuccess")
}
return outDir
}
}
}
return null
}
private fun extractFiles(context: Context, filePath: String, archiverName: String): String? {
if (!File(filePath).exists()) {
return null
}
val appDir = context.getAppDir()
var outDir = appDir
val factory = ArchiveStreamFactory("UTF-8")
FileInputStream(filePath).use { istream ->
// 7z doesn't support streaming
val archiveInputStream = factory.createArchiveInputStream(archiverName, istream)
var archiveEntry: ArchiveEntry? = archiveInputStream.nextEntry
val entryNameList = mutableListOf()
while (archiveEntry != null) {
if (archiveEntry.isDirectory) {
outDir = appDir + "/" + archiveEntry.name
val dir = File(outDir)
if (!dir.exists()) dir.mkdirs()
} else {
Log.i(TAG, "压缩包中包含的文件: ${archiveEntry.name}")
entryNameList.add(archiveEntry.name)
}
archiveEntry = archiveInputStream.nextEntry
}
Log.e(TAG, "outDir=$outDir")
for (entryName in entryNameList) {
FileOutputStream("$appDir/$entryName").use { ostream ->
istream.copyTo(ostream)
}
}
archiveInputStream.close()
}
Log.i(TAG, "解压完毕")
return outDir
}
private fun uncompressFile(
context: Context, filePath: String,
cls: Class, clsArchiveEntry: Class
): String? {
val file = File(filePath)
if (!file.exists()) {
return null
}
val startTime = System.currentTimeMillis()
val constructor = cls.getDeclaredConstructor(File::class.java)
val closeableFile = constructor.newInstance(file)
val entriesObj = cls.getDeclaredMethod("getEntries").invoke(closeableFile)
val entries = if (entriesObj is Iterable<*>) {
(entriesObj as Iterable).toList()
} else {
(entriesObj as Enumeration).toList()
}
val appDir = context.getAppDir()
var outDir = appDir
val entryList = mutableListOf()
for (archiveEntry in entries) {
if (archiveEntry.isDirectory) {
outDir = appDir + "/" + archiveEntry.name
val dir = File(outDir)
if (!dir.exists()) dir.mkdirs()
} else {
Log.i(TAG, "压缩包中的文件: ${archiveEntry.name}")
entryList.add(archiveEntry)
}
}
val getInputStreamMethod = cls.getDeclaredMethod("getInputStream", clsArchiveEntry)
for (archiveEntry in entryList) {
(getInputStreamMethod.invoke(closeableFile, archiveEntry) as InputStream).use { istream ->
FileOutputStream(appDir + "/" + archiveEntry.name).use { ostream ->
istream.copyTo(ostream)
}
}
}
closeableFile.close()
val time = System.currentTimeMillis() - startTime
val suffix = filePath.substring(filePath.lastIndexOf('.'))
Log.i(TAG, "解压 $suffix 耗时 $time ms")
return outDir
}
/**
* 解压7z
*/
private fun un7z(context: Context, filePath: String): String? {
if (!File(filePath).exists()) {
return null
}
val _7zFile = SevenZFile(File(filePath))
val files = _7zFile.entries.toList()
val appDir = context.getAppDir()
var outDir = appDir
val entryList = mutableListOf()
for (archiveEntry in files) {
if (archiveEntry.isDirectory) {
outDir = appDir + "/" + archiveEntry.name
val dir = File(outDir)
if (!dir.exists()) dir.mkdirs()
} else {
Log.i(TAG, "7Z中包含的文件: ${archiveEntry.name}")
entryList.add(archiveEntry)
}
}
for (archiveEntry in entryList) {
_7zFile.getInputStream(archiveEntry).use { istream ->
FileOutputStream(appDir + "/" + archiveEntry.name).use { ostream ->
istream.copyTo(ostream)
}
}
}
_7zFile.close()
Log.i(TAG, "7Z解压完毕")
return outDir
}
/**
* 解压rar
*/
private fun unrar(context: Context, rarFilePath: String): String? {
val rarFile = File(rarFilePath)
if (!rarFile.exists()) {
return null
}
val startTime = System.currentTimeMillis()
val appDir = context.getAppDir()
var outDir = appDir
val files = Junrar.extract(rarFile, File(context.getAppDir()))
for (file in files) {
if (file.isDirectory) {
outDir = file.absolutePath
break
}
}
/*val contentDescriptions: List = Junrar.getContentsDescription(rarFile)
for (item in contentDescriptions) {
Log.i(TAG, "RAR中文件 ${item.path}")
}*/
val time = System.currentTimeMillis() - startTime
Log.i(TAG, "解压 rar 耗时 $time ms")
return outDir
}
/**
* 解压zip
*/
fun unzip(context: Context, zipFilePath: String): String? {
if (!File(zipFilePath).exists()) {
return null
}
val zipFile = ZipFile(zipFilePath)
val files = zipFile.entries.toList()
val appDir = context.getAppDir()
var outDir = appDir
val entryList = mutableListOf()
for (zipEntry in files) {
if (zipEntry.isDirectory) {
outDir = appDir + "/" + zipEntry.name
val dir = File(outDir)
if (!dir.exists()) dir.mkdirs()
} else {
Log.i(TAG, "ZIP中包含的文件: ${zipEntry.name}")
entryList.add(zipEntry)
}
}
for (zipEntry in entryList) {
zipFile.getInputStream(zipEntry).use { istream ->
FileOutputStream(appDir + "/" + zipEntry.name).use { ostream ->
istream.copyTo(ostream)
}
}
}
zipFile.close()
Log.i(TAG, "ZIP解压完毕")
return outDir
}
}
选择文件
val launcher: ActivityResultLauncher> = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
it?.let { uri ->
executor.execute {
//解压文件
FileUtil.extractFiles(context, uri)?.let { outDir ->
//获得解压文件所在的目录
}
}
}
}
//选择文件
launcher.launch(FileUtil.MIME_TYPES)