我对这段代码有疑问,在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
随时提出任何问题,我们将为您提供任何帮助。
答案 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)
}