如何获得每个StorageVolume的免费和总大小? [英] How to get free and total size of each StorageVolume?
问题描述
Google(非常遗憾) 计划破坏存储权限 ,以便应用无法使用标准File API(和文件路径)访问文件系统.许多都是 反对 ,因为它改变了应用访问存储的方式而且在很多方面都是受限制的API.
Google (sadly) plans to ruin storage permission so that apps won't be able to access the file system using the standard File API (and file-paths). Many are against it as it changes the way apps can access the storage and in many ways it's a restricted and limited API.
因此,我们将需要在将来的某些Android版本上完全使用SAF(存储访问框架)(在Android Q上,我们至少可以暂时使用
As a result, we will need to use SAF (storage access framework) entirely on some future Android version (on Android Q we can, at least temporarily, use a flag to use the normal storage permission), if we wish to deal with various storage volumes and reach all files there.
因此,举例来说,假设您要制作一个文件管理器并显示设备的所有存储量,并为它们中的每一个显示有多少个总字节和可用字节.这样的事情看起来很合法,但是由于我找不到找到这种方法的方法.
So, for example, suppose you want to make a file manager and show all the storage volumes of the device, and show for each of them how many total and free bytes there are. Such a thing seems very legitimate, but as I can't find a way to do such a thing.
从API 24开始( 此处 > ),我们终于可以列出所有存储卷,如下所示:
Starting from API 24 (here), we finally have the ability to list all of the storage volumes, as such:
val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumes = storageManager.storageVolumes
事实是,此列表上的每个项目都没有功能来获取其大小和可用空间.
Thing is, there is no function for each of the items on this list to get its size and free space.
无论如何,Google的 应用程序设法获取了此信息,而没有授予任何形式的许可:
However, somehow, Google's "Files by Google" app manages to get this information without any kind of permission being granted :
并且此功能已在装有Android 8的Galaxy Note 8上进行了测试.甚至没有最新版的Android.
And this was tested on Galaxy Note 8 with Android 8. Not even the latest version of Android.
因此,这意味着即使在Android 8上,也应该有一种无需任何许可即可获取此信息的方法.
So this means there should be a way to get this information without any permission, even on Android 8.
有些类似于获取自由空间的方法,但是我不确定是否确实如此.虽然如此.这是它的代码:
There is something similar to getting free-space, but I'm not sure if it's indeed that. It seems as such, though. Here's the code for it:
val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumes = storageManager.storageVolumes
AsyncTask.execute {
for (storageVolume in storageVolumes) {
val uuid: UUID = storageVolume.uuid?.let { UUID.fromString(it) } ?: StorageManager.UUID_DEFAULT
val allocatableBytes = storageManager.getAllocatableBytes(uuid)
Log.d("AppLog", "allocatableBytes:${android.text.format.Formatter.formatShortFileSize(this,allocatableBytes)}")
}
}
但是,我找不到类似的东西来获取每个StorageVolume实例的总空间.假设我对此没错,我已在 此处 要求它
However, I can't find something similar for getting the total space of each of the StorageVolume instances. Assuming I'm correct on this, I've requested it here.
您可以在我写给该问题的答案中找到更多的信息,但目前,这是所有变通办法的结合,这些工作不是变通办法,而是在某些情况下有效.
You can find more of what I've found in the answer I wrote to this question, but currently it's all a mix of workarounds and things that aren't workarounds but work in some cases.
-
getAllocatableBytes
确实是获得可用空间的方式吗? - 我如何能在不请求任何许可的情况下,像在Google的应用程序上那样,获得每个StorageVolume的可用空间和实际总空间(在某些情况下,由于某些原因我会得到较低的值)?
- Is
getAllocatableBytes
indeed the way to get the free space? - How can I get the free and real total space (in some cases I got lower values for some reason) of each StorageVolume, without requesting any permission, just like on Google's app?
推荐答案
以下内容使用fstatvfs(FileDescriptor)
来检索统计信息,而无需求助于反射或传统文件系统方法.
The following uses fstatvfs(FileDescriptor)
to retrieve stats without resorting to reflection or traditional file system methods.
要检查程序的输出以确保其产生合理的结果,以显示总的,已使用的和可用的空间,我在运行API 29的Android仿真器上运行了"df"命令.
To check the output of the program to make sure it is producing reasonable result for total, used and available space I ran the "df" command on an Android Emulator running API 29.
在adb shell中输出"df"命令并报告1K块:
"/数据"对应于StorageVolume#isPrimary为true时使用的主要" UUID.
"/data" corresponds to the "primary" UUID used when by StorageVolume#isPrimary is true.
"/storage/1D03-2E0E"对应于StorageVolume#uuid报告的"1D03-2E0E" UUID.
"/storage/1D03-2E0E" corresponds to the "1D03-2E0E" UUID reported by StorageVolume#uuid.
generic_x86:/ $ df
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/root 2203316 2140872 46060 98% /
tmpfs 1020140 592 1019548 1% /dev
tmpfs 1020140 0 1020140 0% /mnt
tmpfs 1020140 0 1020140 0% /apex
/dev/block/vde1 132168 75936 53412 59% /vendor
/dev/block/vdc 793488 647652 129452 84% /data
/dev/block/loop0 232 36 192 16% /apex/com.android.apex.cts.shim@1
/data/media 793488 647652 129452 84% /storage/emulated
/mnt/media_rw/1D03-2E0E 522228 90 522138 1% /storage/1D03-2E0E
应用使用 fstatvfs 报告(以1K块为单位):
Reported by the app using fstatvfs (in 1K blocks):
对于/tree/primary:/document/primary: 总计= 793,488使用空间= 647,652可用= 129,452
For /tree/primary:/document/primary: Total=793,488 used space=647,652 available=129,452
对于/tree/1D03-2E0E:/document/1D03-2E0E: 总计= 522,228使用空间= 90可用= 522,138
For /tree/1D03-2E0E:/document/1D03-2E0E: Total=522,228 used space=90 available=522,138
总计匹配.
fstatvfs 描述 fstatvfs 返回的详细信息可以在此处.
Detail on what fstatvfs returns can be found here.
以下小应用程序显示可访问卷的已用字节,可用字节和总字节.
The following little app displays used, free and total bytes for volumes that are accessible.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var mStorageManager: StorageManager
private val mVolumeStats = HashMap<Uri, StructStatVfs>()
private val mStorageVolumePathsWeHaveAccessTo = HashSet<String>()
private lateinit var mStorageVolumes: List<StorageVolume>
private var mHaveAccessToPrimary = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mStorageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
mStorageVolumes = mStorageManager.storageVolumes
requestAccessButton.setOnClickListener {
val primaryVolume = mStorageManager.primaryStorageVolume
val intent = primaryVolume.createOpenDocumentTreeIntent()
startActivityForResult(intent, 1)
}
releaseAccessButton.setOnClickListener {
val takeFlags =
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
val uri = buildVolumeUriFromUuid(PRIMARY_UUID)
contentResolver.releasePersistableUriPermission(uri, takeFlags)
val toast = Toast.makeText(
this,
"Primary volume permission released was released.",
Toast.LENGTH_SHORT
)
toast.setGravity(Gravity.BOTTOM, 0, releaseAccessButton.height)
toast.show()
getVolumeStats()
showVolumeStats()
}
getVolumeStats()
showVolumeStats()
}
private fun getVolumeStats() {
val persistedUriPermissions = contentResolver.persistedUriPermissions
mStorageVolumePathsWeHaveAccessTo.clear()
persistedUriPermissions.forEach {
mStorageVolumePathsWeHaveAccessTo.add(it.uri.toString())
}
mVolumeStats.clear()
mHaveAccessToPrimary = false
for (storageVolume in mStorageVolumes) {
val uuid = if (storageVolume.isPrimary) {
// Primary storage doesn't get a UUID here.
PRIMARY_UUID
} else {
storageVolume.uuid
}
val volumeUri = uuid?.let { buildVolumeUriFromUuid(it) }
when {
uuid == null ->
Log.d(TAG, "UUID is null for ${storageVolume.getDescription(this)}!")
mStorageVolumePathsWeHaveAccessTo.contains(volumeUri.toString()) -> {
Log.d(TAG, "Have access to $uuid")
if (uuid == PRIMARY_UUID) {
mHaveAccessToPrimary = true
}
val uri = buildVolumeUriFromUuid(uuid)
val docTreeUri = DocumentsContract.buildDocumentUriUsingTree(
uri,
DocumentsContract.getTreeDocumentId(uri)
)
mVolumeStats[docTreeUri] = getFileStats(docTreeUri)
}
else -> Log.d(TAG, "Don't have access to $uuid")
}
}
}
private fun showVolumeStats() {
val sb = StringBuilder()
if (mVolumeStats.size == 0) {
sb.appendln("Nothing to see here...")
} else {
sb.appendln("All figures are in 1K blocks.")
sb.appendln()
}
mVolumeStats.forEach {
val lastSeg = it.key.lastPathSegment
sb.appendln("Volume: $lastSeg")
val stats = it.value
val blockSize = stats.f_bsize
val totalSpace = stats.f_blocks * blockSize / 1024L
val freeSpace = stats.f_bfree * blockSize / 1024L
val usedSpace = totalSpace - freeSpace
sb.appendln(" Used space: ${usedSpace.nice()}")
sb.appendln(" Free space: ${freeSpace.nice()}")
sb.appendln("Total space: ${totalSpace.nice()}")
sb.appendln("----------------")
}
volumeStats.text = sb.toString()
if (mHaveAccessToPrimary) {
releaseAccessButton.visibility = View.VISIBLE
requestAccessButton.visibility = View.GONE
} else {
releaseAccessButton.visibility = View.GONE
requestAccessButton.visibility = View.VISIBLE
}
}
private fun buildVolumeUriFromUuid(uuid: String): Uri {
return DocumentsContract.buildTreeDocumentUri(
EXTERNAL_STORAGE_AUTHORITY,
"$uuid:"
)
}
private fun getFileStats(docTreeUri: Uri): StructStatVfs {
val pfd = contentResolver.openFileDescriptor(docTreeUri, "r")!!
return fstatvfs(pfd.fileDescriptor)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d(TAG, "resultCode:$resultCode")
val uri = data?.data ?: return
val takeFlags =
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(uri, takeFlags)
Log.d(TAG, "granted uri: ${uri.path}")
getVolumeStats()
showVolumeStats()
}
companion object {
fun Long.nice(fieldLength: Int = 12): String = String.format(Locale.US, "%,${fieldLength}d", this)
const val EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents"
const val PRIMARY_UUID = "primary"
const val TAG = "AppLog"
}
}
activity_main.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/volumeStats"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
android:fontFamily="monospace"
android:padding="16dp" />
<Button
android:id="@+id/requestAccessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:text="Request Access to Primary" />
<Button
android:id="@+id/releaseAccessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
android:text="Release Access to Primary" />
</LinearLayout>
这篇关于如何获得每个StorageVolume的免费和总大小?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!