Tengo un problema con este código donde la declaración return packageSize se activa antes de la función onGetStatsCompleted y devuelve 0 en lugar del valor correcto. ¿Hay alguna manera de forzar a onGetStatsCompleted a terminar antes de devolver packageSize? Sé que es un problema lógico porque si elimino el comentario en //Thread.sleep funciona bien.

¿Cómo soluciono esto sin usar Thread.sleep o cualquier otro tipo de tiempo de espera en la aplicación? CÓDIGO ORIGINAL:

/**
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

    }
}

EDITAR CÓDIGO:

Esta es la clase StorageInformation

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

        }
    }
}

Llamar a StorageInformation desde una interfaz

    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)

También probé la solución de r2rek y obtuve el mismo resultado

    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

No dude en hacer cualquier pregunta, cualquier ayuda es apreciada.

0
phil652 9 may. 2019 a las 20:06

4 respuestas

La mejor respuesta

La forma más fácil es usar kotlin coroutines y sus suspender funciones.

Comience por agregándolos a su proyecto

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'

Entonces, todo lo que necesita hacer es agregar el modificador suspend a la firma de su método, para que se vea así.

suspend fun getPackageSize(): Long {...}

Y luego puedes obtenerlo así

fun collectAndShow(){
    launch(Dispatchers.IO){
        val size = getPackageSize()
        withContext(Dispatchers.Main){
            textView.text = "App size is: $size"
        }
    }
}

Le recomendaría que su Actividad, Servicio, ViewModel implemente CoroutineScope que puede ayudarlo a prevenir pérdidas de memoria. Si no quieres hacerlo, utiliza GlobalScope.launch pero definitivamente deberías seguir con el primer enfoque.

Entonces se ve así

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
    }
}

Otra razón para usar las rutinas de Kotlin es que algunas bibliotecas de jetpack van a ser o ya son compatibles con las funciones suspend.

EDITAR: Si no puede exponer las funciones de suspensión, puede manejarlo mediante devoluciones de llamada

fun getPackageSize(callback: (Long) -> Unit) {
    launch(Dispatchers.IO) {
        ...
        val size = StorageInformation(getApplicationContext()).getPackageSize()
        callback(size)
    }
}

Y luego en tu otra clase llámalo así

    //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"
    }
}

¡Una vez más no se bloquea, como debería ser!

4
svkaka 16 may. 2019 a las 12:52

Le recomiendo que haga ese trabajo en el hilo de fondo usando RxJava, corutinas o una AsyncTask. Pero podría usar un ContdownLatch para hacer una solución rápida.

//Ugly global variable
val countdownLatch = CountdownLatch(1) //-------CHANGE HERE--------

/**
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()
    }
    countDownLatch.await(1_000, TimeUnit.MILLISECONDS) //-------CHANGE HERE--------
    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
        countDownLatch.countDown() //-------CHANGE HERE--------
    }
}

Para obtener más información sobre cómo funciona, consulte esta excelente respuesta aquí: https://stackoverflow.com/a/17827339/7926889

0
Luciano Ferruzzi 10 may. 2019 a las 00:46

Usar Thread.sleep (..) no solo no se recomienda, sino que también puede bloquear la interfaz de usuario y no producir el resultado que desea (si el método getPackageSizeInfo se ejecuta más de 1 segundo). Sugiero encarecidamente obtener información sobre el hilo de fondo, utilizando AsyncTask o Coroutines, como sugirió @ Luciano-Ferruzzi. Como ya está usando kotlin, buscaría una solución nativa y usaría corutinas, que podrían verse así:

GlobalScope.launch(Dispatchers.Main){

  val getPackageSizeInfo = withContext(Dispacthers.IO) {
pm.javaClass.getMethod(
                "getPackageSizeInfo", String::class.java, IPackageStatsObserver::class.java)
        getPackageSizeInfo.invoke(pm, context.packageName,
                object : CachePackState() {//Call inner class
                })
     }
}

Como puede ver, esto básicamente no hace cambios en su código, excepto para indicar explícitamente los hilos que usaría para partes específicas del código.

* Perdón por cualquier error de código, realmente no lo compilé.

0
r2rek 14 may. 2019 a las 12:55

Esta es una escuela bastante antigua, pero ¿qué tal:

@Volatile
private  var packageSize: Long = -1

Y luego en fun getPackageSize() reemplazar Thread.sleep con:

while(packageSize < 0) {
    Thread.sleep(100)
}
0
David Soroko 14 may. 2019 a las 22:02