Android 12 akan segera hadir, tetapi Agustus ini, mulai dari versi 11, pengembang harus menggunakan standar baru untuk mengakses aplikasi ke file eksternal. Jika sebelumnya Anda bisa saja memberi tanda bahwa aplikasi Anda tidak mendukung inovasi, maka segera itu akan menjadi wajib bagi semua orang. Fokus utamanya adalah meningkatkan keselamatan.
API — , . .
, — .
Android Internal Storage (IS) External Storage (ES). SD-, ES , . — IS, ES , , . ES , , , .
IS, . . ES , , (, ).
. IS, ES — , .
Android 10- , 11- .
Google Scoped Storage (SS) ES. , — . IS 10- .
Android 11 Google SS — - SDK 30- API, SS, , . Android , . , 11, , , . , Android 11, .
SS ( 10- ), . Media Content, Storage Access Framework , 11- Android, Datasets . , . , . — . , , . , , .
Media Content, SAF Datasets Shared Storage (ShS). . , .
SS — FileProvider . , .
— , . best practice ( , ), - , . , .
. , , , , .
— , , . — , .
.
iFunny . , .
, API.
interface FilesManipulator {
fun createVideoFile(fileName: String, copy: Copier): Uri
fun createImageFile(fileName: String, copy: Copier): Uri
fun createFile(fileName: String, copy: Copier): Uri
fun getPath(uri: Uri): String
fun deleteFile(uri: Uri)
}
FilesManipulator , , API . Copier — , , . , , , . 10- Android FilesManipulator File API, 10- ( ) — MediaStore API.
.
fun getContentValuesForImageCreating(fileName: String): ContentValues {
return ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.IS_PENDING, FILE_WRITING_IN_PENDING)
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + appFolderName)
}
}
fun createImageFile(fileName: String, copy: Copier): Uri {
val contentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val contentValues = getContentValuesForImageCreating(fileName)
val uri = contentResolver.insert(contentUri, contentValues)
?: throw IllegalStateException("New image file insert error")
downloadContent(uri, copy)
return uri
}
fun downloadContent(uri: Uri, copy: Copier) {
try {
contentResolver.openFileDescriptor(uri, FILE_WRITE_MODE)
.use { pfd ->
if (pfd == null) {
throw IllegalStateException("Got nullable file descriptor")
}
copy.copyTo(FileOutputStream(pfd.fileDescriptor))
}
contentResolver.update(uri, getWriteDoneContentValues(), null, null)
} catch (e: Throwable) {
deleteFile(uri)
throw e
}
}
fun getWriteDoneContentValues(): ContentValues {
return ContentValues().apply {
put(MediaStore.Images.Media.IS_PENDING, FILE_WRITING_DONE)
}
}
, MediaStore.Images.Media.IS_PENDING
, 0 , .
, . URI . SDK, File API . External Storage FileProvider API.
, 30- API, . iFunny , . , query, , SDK.
, , . , .
<manifest … >
<queries>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="smsto, mailto" />
</intent>
…
<package android:name="com.twitter.android" />
<package android:name="com.snapchat.android" />
<package android:name="com.whatsapp" />
<package android:name="com.facebook.katana" />
<package android:name="com.instagram.android" />
<package android:name="com.facebook.orca" />
<package android:name="com.discord" />
<package android:name="com.linkedin.android" />
</queries>
</manifest>
UI- API 29-30 , .
LogCat , Orchestrator java.lang.RuntimeException: Cannot connect to androidx.test.orchestrator.OrchestratorService.
, <package android:name="androidx.test.orchestrator" />
.
, — Allure , .
- Scoped Storage , , . , , , .
, . ShellCommandExecutor, adb shell appops set --uid PACKAGE_NAME MANAGE_EXTERNAL_STORAGE allow
.
Android 11 .
10- Android , Allure . issue Allure, , , 11- . adb shell appops set --uid PACKAGE_NAME LEGACY_STORAGE allow
. , .
— . , WRITE_EXTERNAL_STORAGE
28 API, . (, debug) Allure .
debug-.
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:node="replace" />
, targetSdkVersion 30, Google Play , .