<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          相冊(cè)適配 Android 11 繞的那些彎路

          共 6308字,需瀏覽 13分鐘

           ·

          2021-02-19 04:01

          轉(zhuǎn)自:掘金?- Android_ZzT

          一、背景

          最近公司中的相冊(cè)組件被業(yè)務(wù)方反饋了新問題,在 targetSdk=30 的 Android 10 手機(jī)上運(yùn)行相冊(cè),縮略圖會(huì)加載不出來,于是就開啟了這次的趟坑之路。

          定位問題

          首先,我在相冊(cè)Demo中把 targetSdk 設(shè)置到 30, 然后在 Android 10 測(cè)試機(jī)上運(yùn)行,發(fā)現(xiàn)縮略圖完美的顯示了出來。

          很懵逼,為啥相同的代碼 demo 上正常,業(yè)務(wù)方的 app 不正常?

          一定是有什么配置不一樣,才導(dǎo)致了這樣的結(jié)果。

          經(jīng)過了各種找不同 ...

          我發(fā)現(xiàn),demo 的 AndroidManifest.xml 中多了一個(gè)屬性

          <application
          ??android:requestLegacyExternalStorage="true"
          ??...>

          于是,正式開啟了我的適配之路...

          二、requestLegacyExternalStorage 是什么?

          通過翻查官方文檔,大概知道了這個(gè)屬性的意思:在配置targetSdk >= 29,應(yīng)用搭載在Android 10及以上版本的手機(jī)運(yùn)行時(shí),可以暫時(shí)停用「分區(qū)存儲(chǔ)」

          1.「分區(qū)存儲(chǔ)」又是什么?

          分區(qū)存儲(chǔ)

          為了讓用戶更好地管理自己的文件并減少混亂,以 Android 10(API 級(jí)別 29)及更高版本為目標(biāo)平臺(tái)的應(yīng)用在默認(rèn)情況下被賦予了對(duì)外部存儲(chǔ)空間的分區(qū)訪問權(quán)限(即分區(qū)存儲(chǔ))。此類應(yīng)用只能訪問外部存儲(chǔ)空間上的應(yīng)用專屬目錄,以及本應(yīng)用所創(chuàng)建的特定類型的媒體文件。

          在搭載 Android 9(API 級(jí)別 28)或更低版本的設(shè)備上,只要其他應(yīng)用具有相應(yīng)的存儲(chǔ)權(quán)限,任何應(yīng)用都可以訪問外部存儲(chǔ)空間中的應(yīng)用專屬文件。為了讓用戶更好地管理自己的文件并減少混亂,以 Android 10(API 級(jí)別 29)及更高版本為目標(biāo)平臺(tái)的應(yīng)用在默認(rèn)情況下被授予了對(duì)外部存儲(chǔ)空間的分區(qū)訪問權(quán)限(即分區(qū)存儲(chǔ))。啟用分區(qū)存儲(chǔ)后,應(yīng)用將無法訪問屬于其他應(yīng)用的應(yīng)用專屬目錄。

          這是摘自官方文檔的一段話,我們可以把「分區(qū)存儲(chǔ)」簡單解釋為,Android 10?開啟分區(qū)存儲(chǔ)后,你的應(yīng)用在有權(quán)限的情況下也無法隨便訪問其他外部存儲(chǔ)空間中的公有文件夾

          2.「分區(qū)存儲(chǔ)」會(huì)造成什么影響?

          比如在App中展示相冊(cè)縮略圖的時(shí)候,我們會(huì)把 filepath 傳給圖片加載框架去幫助渲染縮略圖,像這樣

          ImageLoader.load(imageView,?Uri.fromFile(path);

          這里的 path 一般為?sdcard/DCIM/...,這明顯為外部存儲(chǔ)空間中的文件夾,且不是應(yīng)用專屬文件,這時(shí)在圖片加載框架層就會(huì)拋出異常java.io.FileNotFoundException。

          假如你用的是 Glide,會(huì)在圖中的代碼位置拋出異常

          三、Android 11 中 requestLegacyExternalStorage 屬性失效

          在繼續(xù)翻閱官方文檔后,又得知了一個(gè)信息:

          注意:當(dāng)您將應(yīng)用更新為以 Android 11(API 級(jí)別 30)為目標(biāo)平臺(tái)后,如果應(yīng)用在搭載 Android 11 的設(shè)備上運(yùn)行,系統(tǒng)會(huì)忽略 requestLegacyExternalStorage 屬性,因此您的應(yīng)用必須做好支持分區(qū)存儲(chǔ)并為這些設(shè)備上的用戶遷移應(yīng)用數(shù)據(jù)的準(zhǔn)備。

          這段信息,簡單可以理解為 requestLegacyExternalStorage=true 只能解燃眉之急,到了 Android 11 上,還是要做適配工作。

          這也成功為我走上彎路,埋下了伏筆 ...

          四、開始走彎路

          1. 只適配 Android 10 (不推薦)

          在Manifest中添加

          <application
          ??android:requestLegacyExternalStorage="true"
          ??...>

          我們剛才知道了,如果應(yīng)用在 Android 11 的設(shè)備上運(yùn)行,系統(tǒng)會(huì)忽略 requestLegacyExternalStorage 屬性,強(qiáng)制開啟分區(qū)存儲(chǔ)??赡苓€是會(huì)出現(xiàn)異常(此處我并沒有真正用 Android 11 的機(jī)器驗(yàn)證)。所以我默認(rèn)認(rèn)為,requestLegacyExternalStorage=true 只能解近憂,但不解本質(zhì)問題。

          2. 放棄 File path,使用 Uri

          前文已經(jīng)提到,我們用訪問 File path 的方式加載縮略圖,會(huì)拋出?java.io.FileNotFoundException

          那么,官方推薦我們?cè)趺醋瞿??大致如下三?/p>

          1. 獲取媒體數(shù)據(jù) id
          2. 獲取縮略圖 uri
          3. 用 uri 加載縮略圖
          val?projection?=?arrayOf(
          ????MediaStore.Video.Media._ID,
          ????MediaStore.Video.Media.DISPLAY_NAME,
          ????MediaStore.Video.Media.DURATION,
          ????MediaStore.Video.Media.SIZE
          )

          ...

          val?query?=?ContentResolver.query(
          ????MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
          ????projection,
          ????selection,
          ????selectionArgs,
          ????sortOrder
          )
          query?.use?{?cursor?->

          ??media.id?=?cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
          ??
          ??...
          ??
          ??media.thumbnailUri?=?ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,?media.id)
          }

          //?Load?thumbnail?of?a?specific?media?item.
          val?thumbnail:?Bitmap?=
          ????????applicationContext.contentResolver.loadThumbnail(
          ????????media.thumbnailUri,?Size(640,?480),?null)

          完整代碼,可參考 developer.android.com/training/da…

          由于這個(gè)變動(dòng)涉及到數(shù)據(jù)源的變化,改動(dòng)點(diǎn)非常多,并且還要用 if else 區(qū)分版本,所以寫了很多膠水代碼 ...

          但是,最終還是成功在 targetSdk=29 Android 10 的手機(jī)上成功顯示出了縮略圖。

          3. 新問題又出現(xiàn)

          相冊(cè)的圖片預(yù)覽功能也不能用了,經(jīng)過排查,發(fā)現(xiàn)是一樣的問題,膠水代碼已經(jīng)寫好,都在射程范圍內(nèi)。于是,用了半小時(shí)又改掉了圖片預(yù)覽的問題。

          正當(dāng)我興奮地覺得馬上要完工的時(shí)候,點(diǎn)了一下視頻預(yù)覽 ... 好吧,看到了熟悉卻又令人絕望的錯(cuò)誤信息,依賴的播放器庫拋出了熟悉的異常?java.io.FileNotFoundException open failed: EACCES (Permission denied)。播放器中也是通過 file path 傳給 ffmpeg 進(jìn)行播放的,但在初始化播放器的時(shí)候就因?yàn)闆]有權(quán)限就直接掛了。

          4. 繞彎想方案

          首先,我找到了播放器的開發(fā)同學(xué)進(jìn)行溝通,能否用傳遞 uri 或者 FileDescriptor 的方式進(jìn)行初始化。得到了幾個(gè)不太友好的結(jié)論:

          1. 傳 uri 到 Native 層,content://media/external/images/media/{media_id},這種 Uri Native 層貌似無法打開(沒再細(xì)查有沒有辦法
          2. 傳 fd 到 Native 層,可能會(huì)涉及 java 層 fd 被 Native 引用,然后無法釋放的問題,如果要釋放還需要開放釋放 fd 的接口
          3. 除了相冊(cè),還有很多地方在將 File path 傳到 Native 層

          然后,開始想怎么能繞過這個(gè)問題,大概找到了 2個(gè) 不靠譜的方案:

          1. 因?yàn)椴荒茉L問公有目錄,那么可以先 copy file 到私有目錄(產(chǎn)品可能要罵街了

          2. 請(qǐng)求 MANAGE_EXTERNAL_STORAGE 權(quán)限

            這是一個(gè)有意思的權(quán)限,官方是這樣說的

            絕大多數(shù)需要共享存儲(chǔ)空間訪問權(quán)限的應(yīng)用都可以遵循共享媒體文件和共享非媒體文件方面的最佳做法。但是,某些應(yīng)用的核心用例需要廣泛訪問設(shè)備上的文件,但無法采用注重隱私保護(hù)的存儲(chǔ)最佳做法高效地完成這些操作。對(duì)于這些情況,Android 提供了一種名為“所有文件訪問權(quán)限”的特殊應(yīng)用訪問權(quán)限

            這段話里說的某些應(yīng)用,比如「殺毒應(yīng)用」「文件瀏覽器」,需要掃描 sdcard 的所有文件,如果沒有權(quán)限就沒法正常工作(很明顯,我們的App不是

            另外,對(duì)于這個(gè)權(quán)限的描述很有意思,長這樣

          如果我是用戶,看到了一個(gè)不需要這些權(quán)限的App卻申請(qǐng)了這種權(quán)限,無疑是一種勸退(產(chǎn)品又要罵街了

          5.冷靜下來,再看文檔

          做到第4步的時(shí)候,我開始意識(shí)到,很有可能繞彎路了,往常的適配工作還沒有這么變態(tài)過。于是我又查了一些資料,找到了這個(gè)視頻,https://www.youtube.com/watch?v=RjyYCUW-9tY&feature=youtu.be

          視頻中對(duì)我們有用的信息大概是這樣,在 Android 10 的時(shí)候,很多開發(fā)者都反應(yīng)了類似的問題,在使用一些 native 的庫時(shí),無法使用 File Api,造成了很多困難。于是,在 Android 11 中,又做了兼容,又可以通過 Java File Api 的方式訪問媒體庫文件了(此時(shí)的我不知道是不是應(yīng)該高興,Android 確實(shí)比蘋果爸爸對(duì)開發(fā)者好)

          后來,我又仔細(xì)的翻了翻官方文檔,確實(shí)找到了一小段不起眼的文字

          使用直接文件路徑和原生庫訪問文件

          為了幫助您的應(yīng)用更順暢地使用第三方媒體庫,Android 11 允許您使用除 MediaStore API 之外的 API 通過直接文件路徑訪問共享存儲(chǔ)空間中的媒體文件。其中包括:

          • File API。
          • 原生庫,例如 fopen()。

          五、結(jié)論

          好吧...

          繞了一個(gè)大圈后,得到了幾個(gè)結(jié)果:

          1. 膠水代碼可能是白寫了,在 targetSdk=29 運(yùn)行在 Android 10 的應(yīng)用上, requestLegacyExternalStorage 屬性完全夠用了(枉我開始我還鄙視它
          2. Android 11 的時(shí)候也不需要適配啥了,雖然 requestLegacyExternalStorage 屬性失效,但相冊(cè)里通過 File Api 訪問的只是媒體庫文件,不會(huì)有任何問題。
          3. 如果 App 中有通過 File Api 訪問外部存儲(chǔ)共有目錄的代碼,還是要需做適配的,至于怎么去做本文就不再討論了

          教訓(xùn)

          繞了一圈之后,得出兩個(gè)教訓(xùn):

          1. 適配新版本的時(shí)候,最好先用真機(jī)測(cè)試一下,萬一完美運(yùn)行就不用適配了
          2. 認(rèn)真讀文檔、認(rèn)真讀文檔、認(rèn)真讀文檔

          * Glide 加載縮略圖

          最后,說個(gè)與適配不太相干的話題,只想看適配內(nèi)容的朋友可以先跳過了。

          我在適配的過程中也跟了一下 glide 加載縮略圖的流程,也搞清了一些問題,順便分享給大家

          1. 為什么向 Glide 傳 content-uri 不會(huì)出錯(cuò),傳 file path 會(huì)報(bào)錯(cuò)?

          上文剛才介紹過,官方提供的獲取相冊(cè)縮略圖的做法是

          //?Load?thumbnail?of?a?specific?media?item.
          val?thumbnail:?Bitmap?=
          ????????applicationContext.contentResolver.loadThumbnail(
          ????????media.thumbnailUri,?Size(640,?480),?null)

          但是我們平時(shí)開發(fā),大多都直接用圖片加載框架,比如 Glide

          Glide
          ??.with(imageView)
          ??.asBitmap()
          ??.load(uri)?//或者?file?path
          ??.into()
          復(fù)制代碼

          在我們沒適配 Android 10 的時(shí)候,傳 file path 會(huì)拋出異常,這我們之前已經(jīng)解釋了。適配之后我們傳入了 content://media/external/images/media/{media_id} 給 Glide,Glide 又是怎么識(shí)別的然后加載出 bitmap 的呢?我?guī)е鴨栴}跟蹤了一下 Glide 加載圖片的過程的源碼,這里我們直接先說結(jié)論。

          StreamLocalUriFetcher

          ??private?InputStream?loadResourceFromUri(Uri?uri,?ContentResolver?contentResolver)
          ??????throws?FileNotFoundException?
          {
          ????switch?(URI_MATCHER.match(uri))?{
          ??????case?ID_CONTACTS_CONTACT:
          ????????return?openContactPhotoInputStream(contentResolver,?uri);
          ??????case?ID_CONTACTS_LOOKUP:
          ??????case?ID_LOOKUP_BY_PHONE:
          ????????//?If?it?was?a?Lookup?uri?then?resolve?it?first,?then?continue?loading?the?contact?uri.
          ????????uri?=?ContactsContract.Contacts.lookupContact(contentResolver,?uri);
          ????????if?(uri?==?null)?{
          ??????????throw?new?FileNotFoundException("Contact?cannot?be?found");
          ????????}
          ????????return?openContactPhotoInputStream(contentResolver,?uri);
          ??????case?ID_CONTACTS_THUMBNAIL:
          ??????case?ID_CONTACTS_PHOTO:
          ??????case?UriMatcher.NO_MATCH:
          ??????default:
          ????????return?contentResolver.openInputStream(uri);
          ????}
          ??}

          uri 經(jīng)過匹配邏輯走到了 default 分支,使用 contentResolver.openInputStream(uri) 的方式來讀取 bitmap,既然是通過系統(tǒng)的 contentResolver 獲取,那一定是沒問題的。

          2. 淺談 Glide 加載圖片流程

          img

          這是我簡單總結(jié)的 Glide 加載圖片的流程,不做詳細(xì)解釋了,簡單介紹一下圖中的關(guān)鍵元素:

          1. 綠圈是時(shí)序
          2. 黃色方塊代表輸入、輸出
          3. 粗實(shí)線框代表類
          4. 細(xì)實(shí)線框代表關(guān)鍵方法
          5. 虛線代表方法屬于哪個(gè)類

          圖中的過程就是這段代碼運(yùn)行的過程

          Glide
          ??.with(imageView)
          ??.asBitmap()
          ??.load(uri)?//或者?file?path
          ??.into()

          參考

          1. Android 存儲(chǔ)用例和最佳做法
          2. Android 11 中的存儲(chǔ)機(jī)制更新
          3. 拖不得了,Android11真的要來了,最全適配實(shí)踐指南奉上
          瀏覽 97
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天堂新版8中文在线8 | 淫色淫香网站 | 伊人啪啪网 | 欧美大屌免费看视频 | 俺也去啦|