1. 程式人生 > 程式設計 >詳解Android10的分割槽儲存機制(Scoped Storage)適配教程

詳解Android10的分割槽儲存機制(Scoped Storage)適配教程

1. 簡介

大家應該都有過這樣的體會,手機用著用著裡面就充斥著各種不懂的資料夾和檔案。甚至是連已經刪除的軟體的資料夾還存在。

為什麼會發生的這樣的問題呢?

因為Google的缺席,導致Android生態野蠻生長,導致很多開發規範沒有完全被落實。
為了解決這樣的問題,Google決定重拳出擊,提出了分割槽儲存(Scoped Storage)機制,也叫沙盒儲存機制。
那麼什麼是沙盒儲存機制呢。
沙盒機制是一種安全機制,用於防止應用讀取其他應用的資料。

  1. 每個應用程式都有自己的儲存空間。
  2. 應用程式不能翻過自己的目錄,去訪問公共目錄。
  3. 應用程式請求的資料都要通過許可權檢測,不符合要求不會被放行。

2. 關於Android10的分割槽機制

詳解Android10的分割槽儲存機制(Scoped Storage)適配教程

以 Android 10(API 級別 29)及更高版本為目標平臺的應用在預設情況下被賦予了對外部儲存裝置的分割槽訪問許可權(即分割槽儲存),對外部儲存檔案訪問方式重新設計,便於使用者更好的管理外部儲存檔案。如果不符合條件的會以相容模式執行,相容模式跟以前一樣,根據路徑可以直接儲存檔案。

應用只能看到本應用專有的目錄(通過 Context.getExternalFilesDir() 訪問)以及特定型別的媒體。除非您的應用需要訪問存放在應用的專有目錄以及 MediaStore 之外的檔案,否則最好使用分割槽儲存。
在釋出Android10的時候官方明確表態:

2020年,主要平臺版本將要求所有應用都使用分割槽儲存,無論應用的目標 SDK 級別是多少。因此,您應該提前確保您的應用能夠使用分割槽儲存。為此,請確保針對搭載 Android 10(API 級別 29)及更高版本的裝置啟用了該行為。
翻譯成通俗語言,不管是使用requestLegacyExternalStorage=true的方式以相容模式執行還是降低targetSDK都無法在接下來2020年的Android(API 29)10更新中被豁免。

所以為了應用的穩定性,應該盡在進行適配。

3. 具體分割槽儲存許可權的介紹

預設情況下,對於targetSdkVersion大於等於29的應用,其訪問許可權範圍限定為分割槽儲存。此應用無需請求與儲存相關的使用者許可權,即可以檢視外部儲存中以下型別的檔案:

  1. 應用外部特定目錄中的檔案(使用getExternalFilesDir()訪問)。
  2. 應用自己建立的照片、視訊和音訊(通過MediaStore訪問)。

分割槽儲存將影響在Android10系統首次安裝啟動、且targetSdkVersion >=29的應用。需要訪問和共享外部儲存檔案的應用會受到影響,需要進行相容性適配。

影響範圍:
在Android 10上執行的應用:
1.targetSdkVersion <= 28,不受影響
2.如果targetSdkVersion >= 29,預設情況應用外部儲存可見性將被過濾,應用需要對分割槽儲存進行適配。

還有值得注意的是以下兩種情況比較特殊,不會受到分割槽儲存的影響:

如果應用最先安裝在Android 10以下的系統,
1) 然後系統通過Fota升級到Android 10
2) 應用通過更新升級到targetSdkVersion >= 29

下面是關於分割槽儲存許可權和其他相關專案的表格。

型別 位置 訪問應用自己生成的檔案 訪問其他應用生成的的檔案 訪問方法 解除安裝應用是否刪除檔案
外部儲存 Photo/ Video/ Audio/ 無需許可權 需要許可權READ_EXTERNAL_STORAGE MediaStore Api
外部儲存 Downloads 無需許可權 無需許可權 通過儲存訪問框架SAF,載入系統檔案選擇器
外部儲存 應用特定的目錄 無需許可權 無法直接訪問 getExternalFilesDir()獲取到屬於應用自己的檔案路徑

4. 專有目錄儲存

應用讀取或寫入應有專有的目錄中的檔案時,不需要獲取儲存許可權。
在應用中想要獲取當前應用的專有儲存目錄路徑是可以用Context.getExternalFilesDir()的方式獲取。

val dirpath = context.getExternalFilesDir("")
val fileString = dirpath + File.separator
val file = File(fileString)
...  // 剩下的步驟是用Java IO或者其他IO庫來寫入資料

5. 共享媒體集合儲存

在共享媒體集合儲存中儲存媒體檔案時,需要根據檔案的型別選擇MediaStore。

把相關資料放入到ContentValues中,最後把ContentValues插入到ContentResolver中,並獲得返回的Uri。

通過Uri過得OutputStream,然後用Okio的IO庫,進行檔案的儲存。

關於Okio的只是以後有機會的話,我們再好好講一講。

不要忘了這裡需要獲取許可權。

// 把圖片下載到共有媒體集合中,並在相簿中顯示
// 建立ContentValues,並加入資訊
val values = ContentValues()
values.put(MediaStore.Images.Media.DESCRIPTION,downloadedFile.name)
values.put(MediaStore.Images.Media.DISPLAY_NAME,downloadedFile.name)
values.put(MediaStore.Images.Media.MIME_TYPE,mimeType)
values.put(MediaStore.Images.Media.TITLE,downloadedFile.name)
values.put(
  MediaStore.Images.Media.RELATIVE_PATH,"${Environment.DIRECTORY_PICTURES}/${downloadedFile.name}"
)
// 插入到ContentResolver,並返回Uri
val insertUri = context.contentResolver.insert(
  MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values
)

if (insertUri != null) {
  // 獲取OutputStream
  val outputStream = context.contentResolver.openOutputStream(insertUri)
if (outputStream != null) {
  sink = outputStream.sink().buffer()
} else {
  return@runCatching FileDownloadResult.OthersError
  }
} else {
  return@runCatching FileDownloadResult.OthersError
}

 val responseBody = response.body ?: return@runCatching FileDownloadResult.OthersError

try {
  val contentLength = responseBody.contentLength()
  if (contentLength > FileUtil.getAvailableSize(dirPath)) {
    continuation.resume(FileDownloadResult.StorageError)
  }
  var totalRead: Long = 0
  var lastRead: Long

  do {
    lastRead = responseBody.source().read(sink.buffer(),BUFFER_SIZE)
    if (lastRead == -1L) {
      break
    }
    totalRead += lastRead
    sink.emitCompleteSegments()
  } while (true)
  sink.writeAll(responseBody.source())
  sink.close()
  responseBody.close()
}

6. 其他

Github: https://github.com/HyejeanMOON/ScopedStorageDemo

到此這篇關於詳解Android10的分割槽儲存機制(Scoped Storage)適配教程的文章就介紹到這了,更多相關Android10 分割槽儲存機制內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!