Из документации , связанной с вопросом, мы знаем, что имя файла расширения имеет вид:
.. [Главная | патч] <расширение-версия> <имя пакета> .obb
и метод getObbDir () возвращает конкретное расположение для файлов расширения в следующей форме:
<разделяемой хранения> / / / <имя пакета> Android ФЖДА /
Итак, вопрос в том, как нам получить доступ к таким файлам?
Чтобы ответить на этот вопрос, я взял каталог, содержащий пять файлов APK, и создал файл OBB с именем "main.314159.com.example.opaquebinaryblob.obb", используя JOBB . Мое намерение состоит в том, чтобы смонтировать и прочитать этот файл OBB, чтобы отобразить имена файлов APK и количество записей в каждом APK (считанные в виде Zip-файлов) в небольшом демонстрационном приложении.
Демонстрационное приложение также попытается создать / прочитать тестовые файлы в различных каталогах в каталоге внешнего хранилища.
Следующее было выполнено на эмуляторе Pixel XL с последней доступной версией «Q» (Android 10.0 (API Google)). Приложение имеет следующие характеристики:
- targetSdkVersion 29
- minSdkVersion 18
- Нет явных разрешений
указанный в манифесте
Я заглянул вперед, чтобы узнать, какой каталог getObbDir () возвращает для этого небольшого приложения, и обнаружил, что это
/ хранение / эмулировать / 0 / Android / ФЖДА / com.example.opaquebinaryblob
поэтому я загрузил свой файл OBB в
/ хранение / эмулировать / 0 / Android / ФЖДА / 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? Это не понятно. Демо показывает, что это не для эмулятора. Я надеюсь, что это будет единообразно для всех устройств.