Unzipp file with Zip4j library
Java library has a built-in class java.util.zip.ZipFile and java.util.zip.ZipInputSteam. However, those class only can unzip files without a password. For those zip files with password protected, we use 3rd party library Zip4j
I have created a password protected zip file for testing. Structure like this. The name with ‘/’ is a directory. All files are photo in jpg format.
Test zip file download link: https://drive.google.com/uc?export=download&id=1XDMUGuvOPkTR2TH_Ikb5-2SIR7nXQ6yk
Unzip password: aB3d
Add dependency in the module gradle.
I have created a new module for this practice and copied the code used in the previous practice download file with Okhttp. Three buttons inside LinearLayout are added below the download progress bar. I will show 3 methods to unzip with ZipFile and ZipInputStream from the library Zip4j. A progress bar and TextView are added under the 3 buttons to show the unzip progress.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/downloadButton" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:text="download" /> <LinearLayout android:id="@+id/progressLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/downloadButton"> <ProgressBar android:id="@+id/downloadProgressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/> <TextView android:id="@+id/downloadProgressText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0 %" /> </LinearLayout> <LinearLayout android:id="@+id/buttonArrayLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/progressLayout"> <Button android:id="@+id/unzip1Button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="unzip1" /> <Button android:id="@+id/unzip2Button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="unzip2" /> <Button android:id="@+id/unzip3Button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="unzip3" /> </LinearLayout> <LinearLayout android:id="@+id/unZipProgressLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/buttonArrayLayout"> <ProgressBar android:id="@+id/unzipProgressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/> <TextView android:id="@+id/unzipProgressText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0 %" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
val unzip1Button = findViewById(R.id.unzip1Button) as Button unzip1Button.setOnClickListener{ Log.d(TAG, "unzip1 " + savePath.absolutePath) unzip1(savePath) } val unzip2Button = findViewById(R.id.unzip2Button) as Button unzip2Button.setOnClickListener{ Log.d(TAG, "unzip2 " + savePath.absolutePath) unzip2(savePath) } val unzip3Button = findViewById(R.id.unzip3Button) as Button unzip3Button.setOnClickListener { Log.d(TAG, "unzip3 " + savePath.absolutePath) unzip3(savePath) }
Extract All files
The first method is the simplest way. The File object of the zipped file is used to create the ZipFIle instance. Then we set the password required for unzip. We just unzip all files(including the directories) into a folder by invoking ZipFile.extractAll(String) with the path to the destination folder. Since unzip may require heavy workload, execute the unzip workload inside a coroutine to avoid blocking the main thread. We cannot monitor the unzip progress by this method.
fun unzip1(file: File){ val unzipedFolder = File(applicationContext.cacheDir, "folder1") if(!unzipedFolder.exists()) unzipedFolder.mkdir() val zipFile = ZipFile(file) //ZipFile class from Zip4j library zipFile.setPassword(unzipPassword.toCharArray()) //unzip password networkRequestScope.launch { zipFile.extractAll(unzipedFolder.absolutePath) } }
fun unzip2(file: File) { val unzipedFolder = File(applicationContext.cacheDir, "folder2") if (!unzipedFolder.exists()) unzipedFolder.mkdir() val zipFile = ZipFile(file) //this ZipFile from Zip4j library zipFile.setPassword(unzipPassword.toCharArray()) val fileHeaders = zipFile.fileHeaders var uncompressedSize = 0L networkRequestScope.launch { fileHeaders.forEach { uncompressedSize += it.uncompressedSize // can use to check if the device has enough free space } val totalSize = uncompressedSize uncompressedSize = 0L fileHeaders.forEach { Log.d(TAG, "Entry name: ${it.fileName} size: ${it.uncompressedSize}") if(!it.isDirectory){ zipFile.extractFile(it, unzipedFolder.absolutePath) uncompressedSize += it.uncompressedSize val percentage = (((uncompressedSize * 100) / totalSize)) runOnUiThread{ unzipProgressBar.progress = percentage.toInt() unzipProgressText.text = "${percentage.toInt()}%" } } } } }
Monitor unzip progress of each file
The 3nd method is to monitor the extracting progress of a single file. If a big file is zipped, we may also want to know the progress to extracting it.
One way is to use ZipFile.getInputStream(FileHeader) to extract a file, instead of ZipFile.extract(FileHeaders, String) . Then we count the number of bytes read by the method InputStream.read(byte[]). The file size can be obtained by FileHeader.uncompressedSize in the 2nd method. I put the code into the block comment /**/
Here I use another way: ZipInputStream (from Zip4j library) to extract files, instead of ZipFile.
First, I have a while loop to calculate the total uncompressed size of all files. Then actually extraction is done in the 2nd while loop. Each entry is checked if it is a directory. If it is a directory, then a directory of the same name is created in the destination folder. If not, we use InputStream and OutputStream to transfer the data. After each read/write transaction, we could update the extraction progress. The file uncompressed size is obtained from the Entry(LocalFileHeader).fun unzip3(file: File){ val buffer = ByteArray(READ_BUFFER_SIZE) var byteRead = 0 var fileByteRead = 0L val unzipedFolder = File(applicationContext.cacheDir, "folder3") if(!unzipedFolder.exists()) unzipedFolder.mkdir() networkRequestScope.launch { //first calculate the total uncompressed size of all files var uncompressedSize = 0L val zipIS = ZipInputStream(FileInputStream(file), unzipPassword.toCharArray()) while(true){ val entry = zipIS.nextEntry if (entry == null) break uncompressedSize += entry.uncompressedSize } val totalSize = uncompressedSize uncompressedSize = 0L Log.d(TAG, "total size: ${totalSize}") val zipInputStream = ZipInputStream(FileInputStream(file), unzipPassword.toCharArray()) while (true) { val entry = zipInputStream.nextEntry if (entry == null) break val temp = File(unzipedFolder, entry.fileName) Log.d(TAG, "unzip3 entry: ${entry.fileName}") if (entry.isDirectory) { temp.mkdir() } else { val fileSize = entry.uncompressedSize fileByteRead = 0L val bos = BufferedOutputStream(FileOutputStream(temp)) while (true) { byteRead = zipInputStream.read(buffer) if (byteRead == -1) break bos.write(buffer, 0, byteRead) fileByteRead += byteRead Log.d(TAG, "${entry.fileName} ${fileByteRead} of ${fileSize} has been read") } bos.flush() bos.close() uncompressedSize += fileSize val percentage = (((uncompressedSize * 100) / totalSize)) runOnUiThread{ unzipProgressBar.progress = percentage.toInt() unzipProgressText.text = "${percentage.toInt()}%" } } } } }
The completed code can be found in Github.


Comments
Post a Comment