這個(gè)問(wèn)題其實(shí)在很早之前Android10提出的時(shí)候就開(kāi)始做了適配矾睦,但是為什么寫(xiě)這篇文章呢痒筒,是因?yàn)锳ndroid11快來(lái)了反症,目前我們這邊沒(méi)有android11的手機(jī)進(jìn)行測(cè)試贼邓,所以會(huì)產(chǎn)生一些疑問(wèn)塑径,打算寫(xiě)出來(lái)统舀,希望能有大佬們指點(diǎn)一二誉简。
廢話不多說(shuō)闷串,直接開(kāi)始。
android 10提出了分區(qū)存儲(chǔ)的概念碉熄。開(kāi)發(fā)者無(wú)法直接獲取外部存儲(chǔ)的讀寫(xiě)權(quán)限锈津,用我們平常的話來(lái)說(shuō)就是無(wú)法直接讀寫(xiě)SD卡上的東西一姿。
可以看看官方的描述:https://developer.android.com/training/data-storage/files/external-scoped
這是什么意思呢,別急爆存,我們按照一些場(chǎng)景來(lái)舉例携冤,讓你能夠大概知道Android10這種環(huán)境下如何進(jìn)行適配菜循。
一. 場(chǎng)景舉例
PS:如果不知道分區(qū)存儲(chǔ)是什么個(gè)情況癌幕,建議在Android11的手機(jī)上并設(shè)置targetSdkVersion為29橙喘,跑一遍應(yīng)用,寫(xiě)個(gè)讀寫(xiě)外部存儲(chǔ)的操作和簸,看看會(huì)發(fā)生什么
1. 目標(biāo)版本targetSdkVersion設(shè)置為29以下的情況
這種情況下能正常的讀寫(xiě)SD卡的文件,也就是Environment.getExternalStorageDirectory()路徑的文件,我很早之前測(cè)試過(guò)霉赡,好像我記得即便手機(jī)是Android10幔托,只要targetSdkVersion不設(shè)置超過(guò)29也不會(huì)有外部存儲(chǔ)的讀寫(xiě)問(wèn)題(不對(duì)的話請(qǐng)指出)
如果是這樣的話重挑,最方便的做法就是不把targetSdkVersion設(shè)置超過(guò)29就不會(huì)有問(wèn)題
2. 使用應(yīng)用自身的外部存儲(chǔ)路徑
使用應(yīng)用自身的外部存儲(chǔ)不會(huì)受影響谬哀,只不過(guò)這個(gè)路徑的文件會(huì)在卸載時(shí)也會(huì)被刪除谦屑。
簡(jiǎn)單來(lái)說(shuō)就是context.getExternalFilesDir()不受影響, Environment.getExternalStorageDirectory()才受影響帘睦。
3. 如何文件再升級(jí)前已經(jīng)存在的情況
怎么去了解這種情況涝焙,我舉個(gè)栗子,手機(jī)是Android10隧哮,假如第一次裝的應(yīng)用的targetSdkVersion是28桶良,此時(shí)是能正常使用原本的Environment.getExternalStorageDirectory()對(duì)外部存儲(chǔ)文件做讀寫(xiě)操作,那么這個(gè)文件就存在了榆鼠。這時(shí)候神妹,應(yīng)用更新题翻,新版本的應(yīng)用是使用targetSdkVersion設(shè)置29的,安裝應(yīng)用(無(wú)論覆蓋安裝還是卸載安裝),此時(shí)依舊能使用舊的方式讀寫(xiě)外部存儲(chǔ)嵌赠,即便手動(dòng)去文件管理器刪除你外部存儲(chǔ)的文件,依舊能正常讀寫(xiě)熄赡。
那么返過(guò)來(lái)姜挺,還是上邊的栗子,在安裝新的應(yīng)用之前彼硫,將舊的應(yīng)用卸載炊豪,并且手動(dòng)去文件管理器刪除你外部存儲(chǔ)的文件,意思就是安裝前清空掉所有拧篮,安裝之后词渤,如果使用Environment.getExternalStorageDirectory()對(duì)外部存儲(chǔ)文件做讀寫(xiě)操作,會(huì)報(bào)錯(cuò)串绩。
4. 設(shè)置requestLegacyExternalStorage屬性
在Manifest中設(shè)置requestLegacyExternalStorage = false能夠關(guān)閉不使用分區(qū)存儲(chǔ)缺虐,也就是說(shuō)設(shè)置這個(gè)屬性之后,你依舊能像以前一樣正常的讀寫(xiě)外部存儲(chǔ)的數(shù)據(jù)礁凡。
這個(gè)方法也是很多人會(huì)去使用的一個(gè)方法高氮,我剛開(kāi)始的時(shí)候也是使用這個(gè)方法,一條代碼能解決問(wèn)題的操作顷牌,它不香嗎剪芍,但是這個(gè)方式也會(huì)存在問(wèn)題,下面會(huì)說(shuō)窟蓝。
5. 根據(jù)官網(wǎng)的要求罪裹,執(zhí)行分區(qū)存儲(chǔ)
其實(shí)也有很多博主也寫(xiě)過(guò)關(guān)于這個(gè)方面的內(nèi)容,無(wú)外乎是兩種方式运挫,使用MediaStore或存儲(chǔ)訪問(wèn)框架(SAF)
按照之前我們讀寫(xiě)外部公共存儲(chǔ)文件的操作一般是這樣寫(xiě)的
String path = Environment.getExternalStorageDirectory()
String name = "TestFN"
File file = new File(path, name);
// todo 然后執(zhí)行IO流讀寫(xiě)
然后如果使用MediaStore的話状共,舉個(gè)栗子的吧,就弄一個(gè)寫(xiě)文件的Demo滑臊,因?yàn)榫W(wǎng)上很多都是圖片視頻什么的口芍,假如寫(xiě)到Downloads文件夾里
ContentResolver resolver = getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, "FILENAME");
//設(shè)置文件類(lèi)型(有哪些類(lèi)型網(wǎng)上很容易查到,如果不設(shè)置的話雇卷,就是默認(rèn)沒(méi)有擴(kuò)展名的文件)
values.put(MediaStore.Downloads.MIME_TYPE, "text/plain");
//這個(gè)方法只可在Android10的手機(jī)上執(zhí)行鬓椭,設(shè)置路徑
values.put(MediaStore .Downloads.RELATIVE_PATH, "Download/mypath");
Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
// 寫(xiě)入文件
Uri insertUri = resolver.insert(external, values);
// io寫(xiě)入
OutputStream os = null;
String test = "aaaaaaaaaa";
try {
os = resolver.openOutputStream(insertUri);
if(os == null){
return;
}
byte[] buffer = test.getBytes();
os.write(buffer, 0, buffer.length);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
之后可以打開(kāi)文件管理器,在Download文件夾下mypath文件夾下能找到FILENAME.txt文件关划,打開(kāi)能看到里面的內(nèi)容aaaaaaaaaa小染。
使用SAF的話就更加簡(jiǎn)單了
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent, code);
然后在onActivityResult中可以獲取到uri,再執(zhí)行讀寫(xiě)操作贮折。
二. 分區(qū)存儲(chǔ)分析
按照谷歌的意思裤翩,大概意思是執(zhí)行分區(qū)存儲(chǔ)是為了能夠更好的管理文件,以前的方式太亂了。
站在用戶的角度去思考踊赠,確實(shí)是這個(gè)改變會(huì)很舒服呵扛,很多流氓軟件卸載之后會(huì)殘留文件在SD卡里面繼續(xù)占內(nèi)存,我去刪的話還要一個(gè)一個(gè)刪除筐带,現(xiàn)在方便了今穿,比如我之前裝了10個(gè)流氓軟件,現(xiàn)在我直接把DownLoad文件夾刪除伦籍,這個(gè)文件夾下的10個(gè)流氓軟件的東西都會(huì)全部刪除蓝晒。
但是站在開(kāi)發(fā)者角度就有點(diǎn)難受了,因?yàn)橐m配Android11帖鸦,到時(shí)候要做數(shù)據(jù)遷移芝薇,莫名其妙的增加了工作量。
然后關(guān)于上面提出的兩個(gè)方案作儿,對(duì)于非媒體文件來(lái)說(shuō)洛二,比如說(shuō)txt這些,我個(gè)人是打算使用MediaStore來(lái)存數(shù)據(jù)立倍,但是我看很多人說(shuō)這些文件最后用SAF灭红,其實(shí)這里我有一個(gè)疑問(wèn),SAF會(huì)打開(kāi)彈框和用戶進(jìn)行交互口注,但并不是所有的需求都是想彈一個(gè)頁(yè)面和用戶交互的吧变擒?比如我捕獲到了異常寫(xiě)到一個(gè)txt文件,想存到外部存儲(chǔ)中寝志,難道我還要彈出一個(gè)彈框問(wèn)用戶這個(gè)異常文件要存到哪嗎娇斑?
三. Android11分區(qū)存儲(chǔ)分析
寫(xiě)這篇文章的這個(gè)時(shí)間點(diǎn),Android11還沒(méi)出
但是存在預(yù)覽版材部,因?yàn)闆](méi)有Google Pixel 4/4 XL毫缆、Pixel 3a/3a XL、Pixel 3/3 XL 和 Pixel 2/2 XL這些機(jī)型進(jìn)行測(cè)試(不想使用模擬器)乐导,那具體的情況估計(jì)等出了之后才知道苦丁,這里就只能做一些簡(jiǎn)單的分析。
個(gè)人覺(jué)得比較靠譜的兩個(gè)消息渠道來(lái)源:
(1)官方的文檔物臂,中文的https://developer.android.google.cn/preview/privacy/storage#scoped-storage
(2)B站的這個(gè)視頻 https://www.bilibili.com/video/BV1fT4y1g74Z
它們大致說(shuō)的內(nèi)容都一樣旺拉,這里拿官方文檔簡(jiǎn)單分析下。
Android11提出強(qiáng)制執(zhí)行分區(qū)存儲(chǔ)棵磷,在Android10中是非強(qiáng)制的蛾狗,上面說(shuō)了,在Manifest中配置requestLegacyExternalStorage屬性可以關(guān)閉分區(qū)存儲(chǔ)仪媒。按這Android11文檔的意思沉桌,是不是在Android11的手機(jī)上即便配置requestLegacyExternalStorage屬性也沒(méi)效,所以上面說(shuō)了再Android10中使用requestLegacyExternalStorage來(lái)規(guī)避分區(qū)儲(chǔ)存的操作,是不可靠的留凭,雖然一行代碼能解決問(wèn)題很香佃扼,但還是建議盡快換種方式。
文檔說(shuō)“在大多數(shù)情況下冰抢,您可以將數(shù)據(jù)遷移到您的應(yīng)用專(zhuān)用目錄松嘶。”挎扰,專(zhuān)用目錄就是context.getExternalFilesDir(),卸載之后也會(huì)刪除巢音,這里還提出了一個(gè)Android11的新屬性 preserveLegacyExternalStorage遵倦,設(shè)置為true也依舊能使用舊的方式讀寫(xiě)儲(chǔ)存,但是重新安裝應(yīng)用等操作之后官撼,這個(gè)屬性也沒(méi)有用了梧躺,意思是這個(gè)屬性是給開(kāi)發(fā)者用來(lái)做數(shù)據(jù)遷移的,其實(shí)Android10的時(shí)候就應(yīng)該開(kāi)始遷移數(shù)據(jù)去適配分區(qū)存儲(chǔ)了傲绣。為了防止很多人沒(méi)做適配掠哥,都用了requestLegacyExternalStorage暫時(shí)關(guān)閉,谷歌爸爸還是很仁慈的打算再給一次機(jī)會(huì)秃诵。
根據(jù)現(xiàn)在的信息得到的東西還是比較少的续搀,具體的情況只能等到Android11發(fā)布之后才知道,比如說(shuō)手機(jī)是Android11的系統(tǒng)菠净,但是我目標(biāo)版本還是設(shè)置低于29禁舷,這種情況是不是也不需要開(kāi)啟分區(qū)存儲(chǔ)。如果說(shuō)即使目標(biāo)版本低于29的情況毅往,在Android11的手機(jī)上也會(huì)強(qiáng)制開(kāi)啟分區(qū)存儲(chǔ)牵咙,那就是說(shuō)明你一定要去做適配了,趁現(xiàn)在還是Android10的時(shí)候趕緊做適配攀唯,新寫(xiě)的文件就開(kāi)始放到分區(qū)存儲(chǔ)的目錄中洁桌,原本就已經(jīng)存在的文件就做個(gè)遷移。