Как мне ждать выполнения функции перед возвратом значения? - PullRequest
0 голосов
/ 09 мая 2019

У меня проблема с этим кодом, когда оператор return packageSize запускается перед функцией onGetStatsCompleted и возвращает 0 вместо правильного значения.Есть ли способ заставить onGetStatsCompleted закончить перед возвратом packageSize?Я знаю, что это логическая проблема, потому что, если я удаляю комментарий на //Thread.sleep, он работает нормально.

Как я могу это исправить, не используя Thread.sleep или какой-либо другой тайм-аут в приложении? ОРИГИНАЛЬНЫЙ КОД:

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

    }
}

КОД РЕДАКТИРОВАНИЯ:

Это класс 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

        }
    }
}

Вызывая StorageInformation из интерфейса

    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)

Я также попробовал решение от r2rek и получил тот же результат

    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

Не стесняйтесь задавать любые вопросы, любая помощь приветствуется.

Ответы [ 4 ]

4 голосов
/ 14 мая 2019

Самый простой способ - использовать сопрограммы kotlin и их функции приостановки.

Начните с , добавив их в свой проект

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

Затем все, что вам нужно сделать, это просто добавить модификатор suspend в сигнатуру вашего метода, чтобы он выглядел следующим образомthis.

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

и затем вы можете получить его следующим образом

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

Я бы порекомендовал вам сделать вашу Activity, Service, ViewModel реализующей CoroutineScope , которая можетпоможет вам предотвратить утечки памяти.Если вы не хотите этого делать, используйте GlobalScope.launch, но вам определенно следует пойти с 1-м подходом.

Вот так это выглядит

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

Другая причина использования сопрограмм kotlin заключается в том, что некоторые библиотеки Jetpack поддерживают или уже поддерживают функции suspend.

РЕДАКТИРОВАТЬ: Если вы не можете предоставить функции приостановки, то вы можете обработать его с помощью обратных вызовов

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

, а затем в другом классе вызвать еговот так

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

Опять неблокируемо, так и должно быть!

0 голосов
/ 15 мая 2019

Это довольно старая школа, но как насчет:

@Volatile
private  var packageSize: Long = -1

и затем в fun getPackageSize() заменить Thread.sleep на:

while(packageSize < 0) {
    Thread.sleep(100)
}
0 голосов
/ 14 мая 2019

Использование Thread.sleep (..) не только не рекомендуется, но также может блокировать пользовательский интерфейс и не давать желаемого результата (если метод getPackageSizeInfo работает дольше 1 секунды). Я бы настоятельно рекомендовал получать информацию о фоновой ветке, используя AsyncTask или Coroutines, как предложил @ Luciano-Ferruzzi. Поскольку вы уже используете kotlin, я бы выбрал нативное решение и использовал сопрограммы, которые могли бы выглядеть примерно так:

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

Как вы можете видеть, это в основном не меняет ваш код, за исключением явного указания потоков, которые вы будете использовать для определенных частей кода.

* Извините за ошибки в коде, я не скомпилировал его.

0 голосов
/ 10 мая 2019

Я настоятельно рекомендую вам выполнить эту работу в фоновом потоке, используя RxJava, сопрограммы или AsyncTask.Но вы можете использовать ContdownLatch, чтобы сделать быстрое исправление.

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

Для получения дополнительной информации о том, как это работает, проверьте этот отличный ответ здесь: https://stackoverflow.com/a/17827339/7926889

...