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

我们如何访问Android Q中的扩展文件?

左丘昊天
2023-03-14

我正在编写一个需要扩展文件的应用程序,我想确保它与Android Q兼容。看来提供的文档没有解决Android Q中的更改。在Android Q中,getExternalStorageDirectory()将无法使用,所以我们如何访问扩展文件?

共有1个答案

公德明
2023-03-14

从问题中链接到的文档中,我们知道扩展文件的名称的形式如下:

[mainpatch]. . .obb

getObbDir()方法以以下形式返回扩展文件的特定位置:

为了回答这个问题,我取了一个包含五个APK文件的目录,并使用JobB创建了一个名为“main.314159.com.example.opaqueBinaryBlob.OBB”的OBB文件。我的意图是挂载并读取这个OBB文件,以在一个小型演示应用程序中显示APK文件名和每个APK(读取为Zip文件)中的条目数。

演示应用还会尝试在外部存储目录下的各个目录中创建/读取测试文件。

下面是在运行最新可用版本“Q”(Android 10.0(Google API))的Pixel XL模拟器上执行的。该应用程序具有以下特点:

  • 目标SDKVersion 29
  • MINSDKVersion 18
  • 清单中未指定显式权限

我在前面偷看了一下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文件吗?我们可以在外部文件路径中的其他目录中创建/读取文件吗?以下是app对API 29的报道:

唯一可访问的文件位于/storage/emulated/0/android/obb/com.example.opaqueBinaryBlob中。无法创建或读取层次结构中的其他文件。(然而,有趣的是,这些文件的存在是可以确定的。)

对于前面的显示,应用程序打开OBB文件并直接读取它,而不挂载它。

当我们尝试挂载OBB文件并转储其内容时,报告的内容如下:

这正是我们所期待的。简而言之,Android Q似乎限制了对外部文件目录的访问,同时允许根据应用程序的包名进行有针对性的访问。

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)起,App无需外部存储许可即可读取OBB扩展文件。但是,Android 6.0(API级别23)及更高版本的某些实现仍然需要权限,因此您将需要在app清单中声明READ_EXTERNAL_STORAGE权限,并在运行时请求权限...

 类似资料:
  • 我想做一个像相册一样的lib改编成Android Q 由于范围存储,不推荐使用; 我们不能直接通过这样的路径读取文件 没有URI之类的值,因此我无法通过ContentProvider获取图片。 我们可以通过这种方式只打开一个图片(下面的代码),并且在回调中接收到一个URI; 但是我想访问所有的图片,那么,我如何扫描所有的图片在Android Q?

  • 问题内容: 我是新手,尝试快速创建UIColor类的扩展为 之后,我以以下方式访问该方法 我不知道该声明应作为参数传递给我。 问题答案: 您已经定义了一个 实例方法 ,这意味着您只能在一个实例上调用它: 发生编译器错误“缺少参数”是因为 实例方法是Swift中的Curried Function ,因此可以等效地称为 (但这是一件奇怪的事情,我添加它只是为了解释错误消息的来源。) 但是,您真正想要的

  • 听说Android Q推出了一个名为“范围存储”的新安全功能,限制外部存储中的访问文件。我的问题是我必须从应用程序中保存一个文本文档到用户指定的位置。这是否需要任何类型的权限,而不是Q设备中的和?

  • 我们已经在上一节准备好了需要编译的源文件,接下来需要的便是把它们编译成目标文件了。因为在*nix平台和win平台下的编译步骤有些差异,所以这个地方需要分成两块介绍,很不幸,win部分还没有整理,请随时关注本项目。 在*nix下编译 第一步:我们需要根据config.m4文件生成一个configure脚本、Makefile等文件,这一步有phpize来帮我们做: $ phpize PHP Api V

  • 我想知道是否有一种方法可以用“简单的方法”来完成这件事,也许有人知道一个解决方案: 我正在使用javax.swing.text.html.HTMLDocument类,但由于某种原因,我需要的至少2个方法是非公开的,即便如此,我需要“重写”它们,以更改一些内容,但在某种程度上,我需要保留HTMLDocument类,因为我使用了大量的javax.swing.text.html包... 因此,我首先要做

  • 我正在开发一个chrome扩展,遇到了一个大问题。 奇怪的是,我可以访问iframe的html。因此,这段代码在chrome扩展中可以完美地工作: 我尝试将“all_frames”:true放入清单文件中,但没有成功:(