@@ -33,6 +33,7 @@ class ApkAnalyzerFragment : Fragment() {
3333 private var contextText: TextView ? = null
3434 private var btnStart: Button ? = null
3535 private var progressBar: ProgressBar ? = null
36+ private var deferredFile: java.io.File ? = null
3637
3738 private val pickApkLauncher = registerForActivityResult(ActivityResultContracts .StartActivityForResult ()) { result ->
3839 if (result.resultCode == Activity .RESULT_OK ) {
@@ -78,6 +79,11 @@ class ApkAnalyzerFragment : Fragment() {
7879
7980 updateContent()
8081 setupClickListeners()
82+
83+ deferredFile?.let { file ->
84+ deferredFile = null
85+ analyzeFile(file)
86+ }
8187 }
8288
8389 private fun updateContent () {
@@ -98,16 +104,26 @@ class ApkAnalyzerFragment : Fragment() {
98104 pickApkLauncher.launch(intent)
99105 }
100106
107+ fun analyzeFile (file : java.io.File ) {
108+ if (! isAdded || view == null ) {
109+ deferredFile = file
110+ return
111+ }
112+ runAnalysis { analyzeApkFromFile(file) }
113+ }
114+
101115 private fun analyzeApkInBackground (uri : Uri ) {
116+ runAnalysis { analyzeApkFromUri(uri) }
117+ }
118+
119+ private fun runAnalysis (block : suspend () -> String ) {
102120 progressBar?.visibility = View .VISIBLE
103121 btnStart?.isEnabled = false
104122 contextText?.text = " Analyzing APK..."
105123
106124 viewLifecycleOwner.lifecycleScope.launch {
107125 val result = runCatching {
108- withContext(Dispatchers .IO ) {
109- analyzeApkStructure(uri)
110- }
126+ withContext(Dispatchers .IO ) { block() }
111127 }.getOrElse { e ->
112128 " Failed to analyze APK: ${e.message} "
113129 }
@@ -118,25 +134,28 @@ class ApkAnalyzerFragment : Fragment() {
118134 }
119135 }
120136
121- private fun analyzeApkStructure (uri : Uri ): String {
122- val result = StringBuilder ()
123-
124- // Create a temporary file to copy the APK content
137+ private fun analyzeApkFromUri (uri : Uri ): String {
125138 val tempFile = java.io.File .createTempFile(" apk_" , " .apk" , requireContext().cacheDir)
126-
127- return runCatching {
128- // Copy the content from the URI to the temp file
139+ return try {
129140 requireContext().contentResolver.openInputStream(uri)?.use { input ->
130- tempFile.outputStream().use { output ->
131- input.copyTo(output)
132- }
141+ tempFile.outputStream().use { output -> input.copyTo(output) }
133142 }
143+ analyzeApkFromFile(tempFile)
144+ } finally {
145+ tempFile.delete()
146+ }
147+ }
148+
149+ private fun analyzeApkFromFile (file : java.io.File ): String {
150+ val result = StringBuilder ()
134151
135- val zipFile = ZipFile (tempFile)
152+ return runCatching {
153+ ZipFile (file).use { zipFile ->
136154
137155 result.append(" APK STRUCTURE:\n " )
138156
139157 val entries = zipFile.entries().toList().sortedBy { it.name }
158+ val entryMap = entries.associateBy { it.name }
140159 val explicitDirectories = mutableSetOf<String >()
141160 val implicitDirectories = mutableSetOf<String >()
142161 val files = mutableListOf<String >()
@@ -145,7 +164,7 @@ class ApkAnalyzerFragment : Fragment() {
145164 var totalUncompressedSize = 0L
146165 var totalCompressedSize = 0L
147166 var totalEntries = 0
148- val apkFileSize = tempFile .length()
167+ val apkFileSize = file .length()
149168
150169 entries.forEach { entry ->
151170 totalEntries++
@@ -196,12 +215,11 @@ class ApkAnalyzerFragment : Fragment() {
196215 )
197216
198217 keyFiles.forEach { keyFile ->
199- val exists = files.any { it == keyFile }
200- if (exists) {
201- val entry = entries.find { it.name == keyFile }
202- val uncompressedSize = entry?.size?.let { formatFileSize(it) } ? : " ?"
203- val compressedSize = entry?.compressedSize?.let { formatFileSize(it) } ? : " ?"
204- val compressionRatio = if (entry != null && entry.size > 0 ) {
218+ val entry = entryMap[keyFile]
219+ if (entry != null ) {
220+ val uncompressedSize = formatFileSize(entry.size)
221+ val compressedSize = formatFileSize(entry.compressedSize)
222+ val compressionRatio = if (entry.size > 0 ) {
205223 String .format(" %.1f%%" , (entry.compressedSize.toDouble() / entry.size.toDouble()) * 100 )
206224 } else " N/A"
207225 result.append(" • $keyFile : ✓ Raw: $uncompressedSize , Compressed: $compressedSize ($compressionRatio )\n " )
@@ -222,7 +240,7 @@ class ApkAnalyzerFragment : Fragment() {
222240 if (parts.size >= 3 ) {
223241 val arch = parts[1 ]
224242 val libName = parts.last()
225- val entry = entries.find { it.name == lib }
243+ val entry = entryMap[ lib]
226244 val sizes = Pair (entry?.size ? : 0L , entry?.compressedSize ? : 0L )
227245 archMap.getOrPut(arch) { mutableListOf () }.add(Pair (libName, sizes))
228246 }
@@ -255,8 +273,8 @@ class ApkAnalyzerFragment : Fragment() {
255273 resourceDirs.forEach { dir ->
256274 // Calculate total size for files in this directory
257275 val dirFiles = files.filter { it.startsWith(dir) && it.count { c -> c == ' /' } == dir.count { c -> c == ' /' } }
258- val dirUncompressedSize = dirFiles.sumOf { fileName -> entries.find { it.name == fileName } ?.size ? : 0L }
259- val dirCompressedSize = dirFiles.sumOf { fileName -> entries.find { it.name == fileName } ?.compressedSize ? : 0L }
276+ val dirUncompressedSize = dirFiles.sumOf { fileName -> entryMap[ fileName] ?.size ? : 0L }
277+ val dirCompressedSize = dirFiles.sumOf { fileName -> entryMap[ fileName] ?.compressedSize ? : 0L }
260278
261279 if (dirUncompressedSize > 0 ) {
262280 result.append(" • $dir (${dirFiles.size} files) - Raw: ${formatFileSize(dirUncompressedSize)} , Compressed: ${formatFileSize(dirCompressedSize)} \n " )
@@ -269,7 +287,7 @@ class ApkAnalyzerFragment : Fragment() {
269287
270288 // Large files analysis (files > 100KB)
271289 val largeFiles = files.mapNotNull { fileName ->
272- entries.find { it.name == fileName } ?.let { entry ->
290+ entryMap[ fileName] ?.let { entry ->
273291 if (entry.size > 100 * 1024 ) {
274292 Triple (fileName, entry.size, entry.compressedSize)
275293 } else null
@@ -312,19 +330,11 @@ class ApkAnalyzerFragment : Fragment() {
312330 val hasProguard = files.any { it == " proguard/mappings.txt" } || files.any { it.contains(" mapping.txt" ) }
313331 result.append(" • Code Obfuscation: ${if (hasProguard) " Detected" else " None detected" } \n " )
314332
315- zipFile.close()
316-
317333 result.toString()
318- }.fold(
319- onSuccess = {
320- tempFile.delete()
321- it
322- },
323- onFailure = { e ->
324- tempFile.delete()
325- " Failed to analyze APK: ${e.message} "
326334 }
327- )
335+ }.getOrElse { e ->
336+ " Failed to analyze APK: ${e.message} "
337+ }
328338 }
329339
330340 private fun formatFileSize (bytes : Long ): String {
0 commit comments