【问题标题】:How do I wait for a function to be done executing before returning a value?如何在返回值之前等待函数执行完毕?
【发布时间】:2019-09-27 13:58:35
【问题描述】:

我对这段代码有一个问题,其中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

如有任何问题,请随时提出,我们将不胜感激。

【问题讨论】:

    标签: android kotlin return logic


    【解决方案1】:

    我强烈建议您使用 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

    【讨论】:

    • 我也不想使用倒计时,但我会尝试使用 AsyncTask
    【解决方案2】:

    不仅不推荐使用 Thread.sleep(..),它还可能会锁定 UI,并且不会产生您想要的结果(如果 getPackageSizeInfo 方法运行时间超过 1 秒)。 正如@Luciano-Ferruzzi 所建议的那样,我强烈建议使用 AsyncTask 或 Coroutines 获取有关后台线程的信息。由于您已经在使用 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,我更新了问题以显示我做了什么
    【解决方案3】:

    最简单的方法是使用kotlin coroutines 及其挂起函数。

    adding他们开始你的项目

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

    那么你需要做的就是在你的方法签名中添加suspend修饰符,所以它看起来像这样。

    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,但您绝对应该使用第一种方法。

    原来是这样的

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

    它又是非阻塞的,它应该是这样的!

    【讨论】:

    • 这似乎是一个可能的解决方案。我得到未解决的参考调度程序并且找不到导入。我在 gradle.properties 中也有 kotlin.coroutines=enable。你知道为什么吗?
    • @phil652 你在你的项目中添加了协程吗? github.com/Kotlin/kotlinx.coroutines#gradle 核心和安卓部分
    • 如果你正确添加了依赖,你应该可以访问kotlinx.coroutines.Dispatchers
    • @phil652 是的,我刚刚检查了 kotlinVersion = 1.2.61 和 coroutinesVersion = 1.2.1 的组合工作正常。你能展示一下你使用的是什么 kotlin 依赖项吗?
    • 我在 build.gradle 中添加了协程的 2 实现。调度程序已解决,但我得到 Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: public fun CoroutineScope.launch(context: CoroutineContext = ..., start: CoroutineStart = ..., block: suspend CoroutineScope.() → Unit): Job defined in kotlinx.coroutine'withContext(CoroutineContext, suspend CoroutineScope.() -&gt; T): T' is only available since Kotlin 1.3 and cannot be used in Kotlin 1.2
    【解决方案4】:

    这是相当古老的学校,但怎么样:

    @Volatile
    private  var packageSize: Long = -1
    

    然后在fun getPackageSize() 中将Thread.sleep 替换为:

    while(packageSize < 0) {
        Thread.sleep(100)
    }
    

    【讨论】:

    • 到目前为止,这是唯一可行的解​​决方案。我只是不知道这是否是一个好的解决方案, Thread.sleep 阻塞了一个线程,从我学到的最佳实践中,最好远离它们。我可能是错的,我对 Kotlin 还是很陌生。如果由于某种原因 packageSize 总是 这也可能成为一个无限循环
    • 1) Thread.sleep 是基本的核心 Java 功能,所以我很好奇您的“最佳实践”评论。您正在经历竞争条件,因此需要某种线程协调。 2) 如果packageSize 可能始终是负面的,那绝对是您需要解决的问题。您可以使用CountDownLatch 方法,它本质上是相同的,但需要更多的机器,并允许您有条件地等待await(long timeout, TimeUnit unit)
    • @DavidSoroko 你是在认真推荐他阻止 UI 100 毫秒吗?!
    • 我不推荐任何特定的睡眠值,无论持续时间如何,代码都是正确的,或者如果不考虑旋转 CPU,则根本没有睡眠。
    猜你喜欢
    • 2020-04-07
    • 1970-01-01
    • 2021-12-02
    • 1970-01-01
    • 1970-01-01
    • 2020-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多