我正在编写一个需要扩展文件的应用程序,并且希望确保它与Android Q兼容。看起来提供的文档没有涉及到Android Q中的更改。在Android Q中,getExternalStorageDirectory()
将无法使用,那么我们如何访问扩展文件呢?
我正在编写一个需要扩展文件的应用程序,并且希望确保它与Android Q兼容。看起来提供的文档没有涉及到Android Q中的更改。在Android Q中,getExternalStorageDirectory()
将无法使用,那么我们如何访问扩展文件呢?
从问题中链接的文档中,我们知道扩展文件的名称具有以下形式:
[main|patch].<expansion-version>.<package-name>.obb
getObbDir() 方法返回以下格式的扩展文件的具体位置:
<shared-storage>/Android/obb/<package-name>/
那么,问题是如何访问这些文件呢?
为了回答这个问题,我取了一个包含五个APK文件的目录,并使用JOBB创建了一个名为“main.314159.com.example.opaquebinaryblob.obb”的OBB文件。我的意图是挂载并读取此OBB文件,以在小型演示应用程序中显示APK文件名和每个APK(作为Zip文件读取)中条目数的计数。
演示应用程序还将尝试在外部存储目录下的各个目录中创建/读取测试文件。
以下操作是在运行最新版本的“Q”(Android 10.0(Google APIs))的Pixel XL模拟器上执行的。该应用程序具有以下特征:
我提前查看了这个小应用程序使用 getObbDir() 返回的目录,并发现它是
/storage/emulated/0/Android/obb/com.example.opaquebinaryblob
所以我上传了我的OBB文件到
/storage/emulated/0/Android/obb/com.example.opaquebinaryblob/main.314159.com.example.opaquebinaryblob.obb
使用Android Studio。这是文件最终的位置。
那么,我们能否挂载并读取这个OBB文件?我们能否在外部文件路径中创建/读取其他目录中的文件?以下是应用程序在API 29上报告的内容:唯一可以访问的文件位于/storage/emulated/0/Android/obb/com.example.opaquebinaryblob。层次结构中的其他文件无法创建或读取。(有趣的是,这些文件的存在可以被确定。)
对于前面的显示,应用程序打开OBB文件并直接读取它,而不是挂载它。
当我们尝试挂载OBB文件并转储其内容时,报告如下:
Android Q似乎正在限制对外部文件目录的访问,同时允许基于应用程序包名称的定向访问。这正是我们所期望的。MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var myObbFile: File
private lateinit var mStorageManager: StorageManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
obbDumpText.movementMethod = ScrollingMovementMethod()
val sb = StringBuilder()
val extStorageDir = Environment.getExternalStorageDirectory()
sb.appendln("getExternalStorageDirectory() reported at $extStorageDir").appendln()
myObbFile = File(obbDir, BLOB_FILE_NAME)
val obbDir = obbDir
sb.appendln("obbDir reported at $obbDir").appendln()
myObbFile = File(obbDir, BLOB_FILE_NAME)
val directoryPathList = listOf(
"$extStorageDir",
"$extStorageDir/Pictures",
"$extStorageDir/Android/obb/com.example.anotherpackage",
"$extStorageDir/Android/obb/$packageName"
)
var e: Exception?
for (directoryPath in directoryPathList) {
val fileToCheck = File(directoryPath, TEST_FILE_NAME)
e = checkFileReadability(fileToCheck)
if (e == null) {
sb.appendln("$fileToCheck is accessible.").appendln()
} else {
sb.appendln(e.message)
try {
sb.appendln("Trying to create $fileToCheck")
fileToCheck.createNewFile()
sb.appendln("Created $fileToCheck")
e = checkFileReadability(fileToCheck)
if (e == null) {
sb.appendln("$fileToCheck is accessible").appendln()
} else {
sb.appendln("e").appendln()
}
} catch (e: Exception) {
sb.appendln("Could not create $fileToCheck").appendln(e).appendln()
}
}
}
if (!myObbFile.exists()) {
sb.appendln("OBB file doesn't exist: $myObbFile").appendln()
obbDumpText.text = sb.toString()
return
}
e = checkFileReadability(myObbFile)
if (e != null) {
// Need to request READ_EXTERNAL_STORAGE permission before reading OBB file
sb.appendln("Need READ_EXTERNAL_STORAGE permission.").appendln()
obbDumpText.text = sb.toString()
return
}
sb.appendln("OBB is accessible at")
.appendln(myObbFile).appendln()
mStorageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
obbDumpText.text = sb.toString()
}
private fun dumpMountedObb(obbMountPath: String) {
val obbFile = File(obbMountPath)
val sb = StringBuilder().appendln("Dumping OBB...").appendln()
sb.appendln("OBB file path is $myObbFile").appendln()
sb.appendln("OBB mounted at $obbMountPath").appendln()
val listFiles = obbFile.listFiles()
if (listFiles == null || listFiles.isEmpty()) {
Log.d(TAG, "No files in obb!")
return
}
sb.appendln("Contents of OBB").appendln()
for (listFile in listFiles) {
val zipFile = ZipFile(listFile)
sb.appendln("${listFile.name} has ${zipFile.entries().toList().size} entries.")
.appendln()
}
obbDumpText.text = sb.toString()
}
private fun checkFileReadability(file: File): Exception? {
if (!file.exists()) {
return IOException("$file does not exist")
}
var inputStream: FileInputStream? = null
try {
inputStream = FileInputStream(file).also { input ->
input.read()
}
} catch (e: IOException) {
return e
} finally {
inputStream?.close()
}
return null
}
fun onClick(view: View) {
mStorageManager.mountObb(
myObbFile.absolutePath,
null,
object : OnObbStateChangeListener() {
override fun onObbStateChange(path: String, state: Int) {
super.onObbStateChange(path, state)
val mountPath = mStorageManager.getMountedObbPath(myObbFile.absolutePath)
dumpMountedObb(mountPath)
}
}
)
}
companion object {
const val BLOB_FILE_NAME = "main.314159.com.example.opaquebinaryblob.obb"
const val TEST_FILE_NAME = "TestFile.txt"
const val TAG = "MainActivity"
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/obbDumpText"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
android:text="Click the button to view content of the OBB."
android:textColor="@android:color/black"
app:layout_constraintBottom_toTopOf="@+id/dumpMountObb"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread_inside" />
<Button
android:id="@+id/dumpMountObb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Dump\nMounted OBB"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/obbDumpText"
app:layout_constraintVertical_bias="0.79" />
</androidx.constraintlayout.widget.ConstraintLayout>
针对此处的后续 链接:
自 Android 4.4(API 级别 19)起,应用程序可以在没有外部存储权限的情况下读取 OBB 扩展文件。但是,某些 Android 6.0(API 级别 23)及更高版本的实现仍需要该权限,因此您需要在应用程序清单中声明 READ_EXTERNAL_STORAGE 权限,并在运行时请求权限...
这是否适用于 Android Q?目前还不清楚。演示显示模拟器上不需要该权限。希望这在所有设备上都能保持一致。
getExternalStorageDirectory()
,尽管它已被标记为过时。我不知道obb/
目录是否可读。默认情况下,它是不可读的,但他们可能对此做出了例外,并没有记录下来。 - CommonsWare