Android 12 arrive bientôt, mais en août, à partir de la version 11, les développeurs devront utiliser de nouvelles normes pour accéder aux applications aux fichiers externes. Si auparavant vous pouviez simplement mettre un indicateur indiquant que votre application ne prend pas en charge les innovations, elles deviendront bientôt obligatoires pour tout le monde. L’objectif principal est d’améliorer la sécurité.
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 , .