O Android 12 está chegando, mas neste mês de agosto, a partir da versão 11, os desenvolvedores terão que usar novos padrões para acessar aplicativos em arquivos externos. Se antes você pudesse apenas colocar um sinalizador de que seu aplicativo não oferece suporte a inovações, em breve elas se tornarão obrigatórias para todos. O foco principal é melhorar a segurança.
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 , .