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

时间:2019-05-09 17:06:47

标签: android kotlin return logic

我对这段代码有疑问,在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 个答案:

答案 0 :(得分:4)

最简单的方法是使用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"
    }
}

再次实现非阻塞,应该如此!

答案 1 :(得分:0)

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

答案 2 :(得分:0)

不仅不建议使用Thread.sleep(..),它还可能锁定UI,并且不会产生所需的结果(如果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
                })
     }
}

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

*很抱歉出现任何代码错误,我并未真正编译它。

答案 3 :(得分:0)

这是一所相当古老的学校,但是:

@Volatile
private  var packageSize: Long = -1

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

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