I have an issue with this code where the return packageSize
statement is triggered before the onGetStatsCompleted
function and it returns 0 instead of the right value.
Is there a way I can force onGetStatsCompleted
to finish before returning packageSize
?
I know it's a logic issue because if I remove the comment at //Thread.sleep
it works fine.
How do I fix this without using Thread.sleep
or any other kind of time out in the application?
ORIGINAL CODE:
/**
Get the size of the app for API < 26
*/
@Throws(InterruptedException::class)
fun getPackageSize(): Long {
val pm = context.packageManager
try {
val getPackageSizeInfo = pm.javaClass.getMethod(
"getPackageSizeInfo", String::class.java, IPackageStatsObserver::class.java)
getPackageSizeInfo.invoke(pm, context.packageName,
object : CachePackState() {//Call inner class
})
} catch (e: Exception) {
e.printStackTrace()
}
//Thread.sleep(1000)
return packageSize
}
/**
Inner class which will get the data size for the application
*/
open inner class CachePackState : IPackageStatsObserver.Stub() {
override fun onGetStatsCompleted(pStats: PackageStats, succeeded: Boolean) {
//here the pStats has all the details of the package
dataSize = pStats.dataSize
cacheSize = pStats.cacheSize
apkSize = pStats.codeSize
packageSize = cacheSize + apkSize
}
}
EDIT CODE:
This is the StorageInformation class
import android.annotation.SuppressLint
import android.app.usage.StorageStatsManager
import android.content.Context
import android.content.pm.IPackageStatsObserver
import android.content.pm.PackageManager
import android.content.pm.PackageStats
/**
This class will perform data operation
*/
internal class StorageInformation(internal var context: Context) {
private var packageSize: Long = 0
private var dataSize: Long = 0
private var cacheSize: Long = 0
private var apkSize: Long = 0
/**
Get the size of the app
*/
@Throws(InterruptedException::class)
suspend fun getPackageSize(): Long {
val pm = context.packageManager
@SuppressLint("WrongConstant")
val storageStatsManager: StorageStatsManager
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
storageStatsManager = context.getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager
try {
val ai = context.packageManager.getApplicationInfo(context.packageName, 0)
val storageStats = storageStatsManager.queryStatsForUid(ai.storageUuid, pm.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA).uid)
cacheSize = storageStats.cacheBytes
apkSize = storageStats.appBytes
packageSize = cacheSize + apkSize
} catch (e: Exception) {
e.printStackTrace()
}
} else {
try {
val getPackageSizeInfo = pm.javaClass.getMethod(
"getPackageSizeInfo", String::class.java, IPackageStatsObserver::class.java)
getPackageSizeInfo.invoke(pm, context.packageName,
object : CachePackState() {//Call inner class
})
} catch (e: Exception) {
e.printStackTrace()
}
}
return packageSize
}
/**
Inner class which will get the data size for the application
*/
open inner class CachePackState : IPackageStatsObserver.Stub() {
override fun onGetStatsCompleted(pStats: PackageStats, succeeded: Boolean) {
//here the pStats has all the details of the package
dataSize = pStats.dataSize
cacheSize = pStats.cacheSize
apkSize = pStats.codeSize
packageSize = cacheSize + apkSize
}
}
}
Calling StorageInformation from an interface
var appSize=""
fun getPackageSize(callback: (Long) -> Unit) {
launch(Dispatchers.IO) {
val size = StorageInformation(getApplicationContext()).getPackageSize()
callback(size)
}
}
fun handlePackageSize(size: Long) {
launch(Dispatchers.Main) {
appSize = formatFileSize(getApplicationContext(), size)
}
}
getPackageSize(::handlePackageSize)
I also tried the solution from r2rek and get the same result
try {
GlobalScope.launch(Dispatchers.Main){
var getPackageSizeInfo = withContext(coroutineContext) {
pm.javaClass.getMethod(
"getPackageSizeInfo", String::class.java, IPackageStatsObserver::class.java)
}
getPackageSizeInfo.invoke(pm, context.packageName,
object : CachePackState() {//Call inner class
})
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return packageSize
Feel free to ask any questions, any help is appreciated.
The easiest way is to use kotlin coroutines and their suspend functions.
Start by adding them to your project
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
Then all you need to do is just add suspend
modifier to your method signature, so it looks like this.
suspend fun getPackageSize(): Long {...}
and then you can obtain it like this
fun collectAndShow(){
launch(Dispatchers.IO){
val size = getPackageSize()
withContext(Dispatchers.Main){
textView.text = "App size is: $size"
}
}
}
I would recommend that you make your Activity, Service, ViewModel implement CoroutineScope which can help you prevent memory leaks. If you don't want to do that use GlobalScope.launch
but you should definitely go with the 1st approach.
So it looks like this
class MainActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Job()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
launch(Dispatchers.IO) {
val size= getPackageSize()
withContext(Dispatchers.Main){
findViewById<TextView>(R.id.textView).text="App size is: $size"
}
}
}
suspend fun getPackageSize(): Long {
//do your stuff
}
}
Another reason to use kotlin coroutines is that some jetpack libraries are gonna or already are supporting suspend
functions.
EDIT: If you cannot expose suspend functions then you can handle it using callbacks
fun getPackageSize(callback: (Long) -> Unit) {
launch(Dispatchers.IO) {
...
val size = StorageInformation(getApplicationContext()).getPackageSize()
callback(size)
}
}
and then in your other class call it like this
//wherever you want to get size
....
getPackageSize(::handlePackageSize)
....
fun handlePackageSize(size: Long) {
//do whatever you want with size
launch(Dispatchers.Main) {
findViewById<TextView>(R.id.textView).text = "APP SIZE= $size"
}
}
Again it's non-blocking, the way it should be!