当前位置: 首页 > 知识库问答 >
问题:

如何在返回值之前等待函数执行完成?

申高峰
2023-03-14

我对这段代码有一个问题,其中返回PackageSize语句是在onGetStatsComplated函数之前触发的,它返回0而不是正确的值。有没有办法在返回PackageSize之前强制onGetStatsComplated完成?我知道这是一个逻辑问题,因为如果我删除//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

欢迎提出任何问题,任何帮助都将不胜感激。

共有3个答案

王豪
2023-03-14

使用线程。睡眠(…)不仅不建议使用,它还可能会锁定UI,并且不会产生所需的结果(如果getPackageSizeInfo方法运行时间超过1秒)。我强烈建议使用AsyncTask或协同程序获取后台线程的信息,就像@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
                })
     }
}

正如您所看到的,这基本上不会对代码进行任何更改,除了明确说明代码特定部分将使用的线程。

*很抱歉出现了代码错误,我并没有真正编译它。

宦树
2023-03-14

我强烈建议您使用RxJava、协同程序或异步任务在后台线程上完成这项工作。但是你可以用一个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

高迪
2023-03-14

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

首先将它们添加到您的项目中

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

我建议您让您的活动、服务、视图模型实现CoroutineScope,这可以帮助您防止内存泄漏。如果不想这样做,请使用GlobalScope。启动,但你肯定应该使用第一种方法。

看起来是这样的

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

再一次,它是非阻塞的,它应该是这样的!

 类似资料:
  • 我仍在为承诺而挣扎,但多亏了这里的社区,我取得了一些进步。 我有一个简单的JS函数,它查询解析数据库。它应该返回结果数组,但显然由于查询的异步性质(因此有承诺),函数在结果之前返回,给我留下一个未定义的数组。 我需要做什么才能让这个函数等待承诺的结果呢? 这是我的代码:

  • 问题内容: 我正在尝试在Node.js / Express中创建一个路由,该路由从两个查询中读取数据,然后基于来自查询的数据来增加计数。由于Node.js是异步的,因此在读取所有数据之前会显示总计。 我创建了一个简单的示例,以达到我目前正在做的事情 我不确定要等到两个功能都完成后才能打印总计,我该做什么。我是否需要创建一个自定义事件发射器来实现这一目标? 问题答案: 拥抱异步性: 或使用Promi

  • 问题内容: 我有以下 我有一个运行一些ajax的函数,然后根据ajax是否成功返回true或false。我从代码的多个位置调用了这个ajax函数。 因为该函数在ajax完成之前结束,所以它总是返回false。如何避免这种情况? 我读到一些建议我在函数中执行的操作,然后将和函数移至我的和函数。但是,在我的方法中,我进行了大量的计算。很多代码。因此,问题在于,如果我将功能移至其他功能,那么我将复制一堆

  • 问题内容: 这是我的情况: 在this.handleFormSubmit()上,我正在执行this.setState() 在this.handleFormSubmit()内部,我正在调用this.findRoutes(); -取决于this.setState()的成功完成 this.setState(); 在this.findRoutes被调用之前没有完成… 在调用this.findRoutes(

  • 我想返回用户的电话号码,但我得到的是[对象对象]。我在想,如果它在等待完成之前返回值。这是我的代码。 输出12345678,以便我可以在异步函数外部访问它。例如,要在个人资料页面中显示它,

  • 我正在尝试使用返回Mono的Spring反应器webclient执行多个HTTP调用。我正在使用Mono.block()等待所有monos完成。这不是等待直到订阅()完成。 有人能帮我解决这个问题,在subscribe()完成后返回更新的用户对象吗?