Android存儲(chǔ)挖坑記
[URL](http://blog.desmondyao.com/2016/05/04/android-storage/)
最近在搞Android存儲(chǔ)相關(guān)的業(yè)務(wù)幔嗦,什么Internal/External/Primary/Secondary搞得我都看懵了虎敦,國(guó)內(nèi)也沒(méi)什么好的文章系統(tǒng)的講這個(gè)仿粹,我就挖挖各類(lèi)資料搁吓,整理一下原茅。
- Internal vs External
對(duì)于Internal Storage 與 External Storage吭历,官方文檔上有這么一段話,描述得很詳細(xì)了擂橘,我翻譯了一段下來(lái):
所有的Android設(shè)備都有兩塊存儲(chǔ)區(qū)域:Internal Storage和External Storage晌区。它們的名稱(chēng)來(lái)源于早期的Android系統(tǒng),那時(shí)候大家的手機(jī)都內(nèi)置(Permanent)一塊較小存儲(chǔ)板(即Internal Storage)通贞,并配上一個(gè)的外置的(Removable)儲(chǔ)存卡(即External Storage)朗若。后來(lái)部分手機(jī)開(kāi)始將最初定義的“Internal Storage”,即內(nèi)置存儲(chǔ)昌罩,分成Internal和External兩部分哭懈。這樣一來(lái)就算沒(méi)有外置儲(chǔ)存,手機(jī)也有Internal和External兩塊存儲(chǔ)區(qū)域茎用。這兩塊存儲(chǔ)區(qū)域的區(qū)別是:
Internal Storage | External Storage | |
---|---|---|
可信度 | 永遠(yuǎn)可用(Permanent) | 可能不可用遣总,最典型的當(dāng)設(shè)備作為USB存儲(chǔ)被mount時(shí)不可用 |
訪問(wèn)權(quán)限 App存儲(chǔ)內(nèi)容僅App本身(或共享uid的App)可訪問(wèn)(Root除外 App存儲(chǔ)內(nèi)容全局可讀
內(nèi)容持久 App存儲(chǔ)內(nèi)容隨App卸載而消失 當(dāng)App卸載時(shí),只有存在getExternalFilesDir()路徑下的文件會(huì)消失
適應(yīng)情況
存儲(chǔ)內(nèi)容僅App自己訪問(wèn)時(shí)的最佳選擇
存儲(chǔ)內(nèi)容希望與其他App共享或傳到電腦上轨功,但是不想申請(qǐng)任何權(quán)限時(shí)的最佳選擇
注:此處討論的訪問(wèn)權(quán)限是應(yīng)用路徑下的權(quán)限旭斥。
總結(jié)下來(lái),External存儲(chǔ)區(qū)域有幾個(gè)好處:
- 可以傳到電腦上古涧;
- 可以與其他app共享垂券;
- 在4.4之后的App路徑(Android/data/包名)下讀寫(xiě)不需任何權(quán)限;
- 存在App路徑之外的文件不會(huì)隨App卸載羡滑。
相應(yīng)的菇爪,也有幾個(gè)缺點(diǎn):
- 可能不可用;
- 會(huì)被其他應(yīng)用讀到;
- 在非App路徑下寫(xiě)柒昏、修改文件需要權(quán)限凳宙。
1.1 External Storage的權(quán)限
在Internal Storage的App路徑下(/data/data/包名下),App的讀寫(xiě)操作無(wú)需任何權(quán)限昙楚,我們只需要總結(jié)一下External Storage的情況:
Android版本讀寫(xiě)
4.4以下
無(wú)需權(quán)限
需要申請(qǐng)WRITE_EXTERNAL_STORAGE
4.4及以上
無(wú)需權(quán)限
在App目錄之外寫(xiě)近速,需要申請(qǐng)WRITE_EXTERNAL_STORAGE
關(guān)于讀External的權(quán)限,在Android Developer上有這樣一段話:
目前堪旧,所有App都可以讀External存儲(chǔ)而不需要任何權(quán)限削葱,這一點(diǎn)可能會(huì)在未來(lái)做出改變。如果你希望讀External存儲(chǔ)淳梦,那最好申請(qǐng)一下READ_EXTERNAL_STORAGE
權(quán)限析砸。另外,寫(xiě)權(quán)限已經(jīng)默認(rèn)包含了讀權(quán)限了爆袍。
正常情況下首繁,你用任何文件管理器作郭,點(diǎn)開(kāi)的根目錄就是你的External存儲(chǔ)。你可以到它下面的應(yīng)用目錄弦疮,你會(huì)發(fā)現(xiàn)夹攒,就算是各個(gè)包名下的文件,你也是看得到的胁塞。
1.2 多用戶
在4.2及以上的Android系統(tǒng)中引入了多用戶機(jī)制咏尝。你可能會(huì)發(fā)現(xiàn)在存儲(chǔ)路徑后面有’0’/‘1’的字樣(如/storage/emulated/0/),這后面的數(shù)字表示用戶啸罢。主用戶后面為0编检。
- Primary vs Secondary
這個(gè)Primary和Secondary是怎么來(lái)的呢?實(shí)際上最開(kāi)始Android也沒(méi)有考慮這個(gè)區(qū)分扰才,但是后來(lái)有一個(gè)情況發(fā)生了允懂,就是上面所說(shuō)到的:
后來(lái)部分手機(jī)開(kāi)始將最初定義的“Internal Storage”,即內(nèi)置存儲(chǔ)衩匣,分成Internal和External兩部分蕾总。
那么如果這個(gè)時(shí)候手機(jī)再插入sd卡,那不是有多個(gè)External Storage了嗎舵揭?
這個(gè)時(shí)候谤专,從Internal Storage里面分出來(lái)的那塊“External Storage”我們稱(chēng)之為主存儲(chǔ)(Primary Storage),插入的外置儲(chǔ)存稱(chēng)之為副存儲(chǔ)(Secondary Storage)午绳。
主存儲(chǔ)路徑的獲取方式非常簡(jiǎn)單置侍,可以通過(guò)Environment.getExternalStorageDirectory()
或者Context.getExternalFilesDir(null)
來(lái)獲取。
副存儲(chǔ)路徑在4.4及以上的Android系統(tǒng)中拦焚,可以使用Context.getExternalFilesDirs(null)(注意最后多了一個(gè)’s’)蜡坊,它返回的是一個(gè)字符串?dāng)?shù)組。第0個(gè)就是主存儲(chǔ)路徑赎败,第1個(gè)是副存儲(chǔ)路徑(如果有的話)秕衙。
在4.4及以下系統(tǒng)中,的副存儲(chǔ)的獲取方式就是一個(gè)大坑了僵刮,一個(gè)一個(gè)介紹一下筆者看到過(guò)的方法据忘。
2.1 副儲(chǔ)存路徑-StorageManager
在Android中可以通過(guò)context.getSystemService(STORAGE_SERVICE)
來(lái)獲取到StorageManager
,但是很可惜的是搞糕,它里面有價(jià)值的方法都是hide的勇吊。。
慶幸的是還有反射窍仰。我們可以調(diào)用getVolumeList()
函數(shù)汉规,這個(gè)返回的List里面,主存儲(chǔ)是第0個(gè)驹吮,副存儲(chǔ)(如果有的話)是第1個(gè)针史。你可以看到Environment.getExternalStorageDirectory()
里面就是用它實(shí)現(xiàn)的晶伦,可以說(shuō)這個(gè)方法是目前最穩(wěn)妥的。它通過(guò)系統(tǒng)的MountService來(lái)獲取已mount上來(lái)的設(shè)備啄枕,并且能夠通過(guò)StorageVolume
知道該存儲(chǔ)是否removable婚陪、是否是emulated、mount狀態(tài)等等射亏。
涉及到存儲(chǔ)近忙,由于Android rom千奇百怪,不可能是萬(wàn)全的智润。如果反射出來(lái)的方法缺少變量、方法未辆,或者有別的什么坑窟绷,那只能試一下其他方法來(lái)保底。
靠譜程度:99%
2.2 副存儲(chǔ)路徑-讀配置xml
讀com.android.internal.R.xml.storage_list.xml可以獲取到系統(tǒng)的VolumeList咐柜,但是這種方法是行不通的兼蜈,我們可以從源碼中看看。
在6.0以前的MountService上面看到readStorageList()
這個(gè)函數(shù)拙友,它在構(gòu)造函數(shù)里面就會(huì)被調(diào)用为狸,就是在讀取這個(gè)xml文件。但是我們可以看到它并沒(méi)有在Volume改變的時(shí)候被動(dòng)態(tài)寫(xiě)入遗契。
并且參考AOSP Document,這個(gè)xml文件里面存儲(chǔ)的就是廠商配置的分區(qū)辐棒,它根本無(wú)法更新removable存儲(chǔ)的熱插拔信息。
注意:這個(gè)xml在6.0被移除了(參考AOSP Document)
靠譜程度:0%
2.3 副存儲(chǔ)路徑-mount命令
執(zhí)行Linux shell下的mount命令牍蜂,遍歷每個(gè)mount點(diǎn)漾根,從中找到副存儲(chǔ)。
目前鲫竞,它確實(shí)能夠列出副存儲(chǔ)辐怕。但是同時(shí)會(huì)列出很多很多mount點(diǎn),包括系統(tǒng)mount點(diǎn)从绘,目前好像沒(méi)有已知的靠譜方法能夠從中準(zhǔn)確找出副存儲(chǔ)寄疏。副存儲(chǔ)的命名是沒(méi)有規(guī)律的,枚舉排除系統(tǒng)mount點(diǎn)的方法不能夠100%確保準(zhǔn)確性僵井。
靠譜程度:10%
2.4 副存儲(chǔ)路徑-讀vold.fstab文件
解析/etc/void.fstab陕截,從中找到副存儲(chǔ)位置。
Vold(Volume Daemon)是ServiceManager與kernel層之間的橋梁驹沿,它對(duì)于Volume的信息維護(hù)在/etc/vold.fstab中艘策。
一聽(tīng)就是一個(gè)奇怪的方法,文件位置渊季、信息也可能被各類(lèi)廠商篡改朋蔫,還可能存在瞬時(shí)不一致的情況罚渐,不要考慮它。有興趣的同學(xué)可以研究一下android-storage-vold驯妄。
靠譜程度:0%
總結(jié)
總結(jié)出Android手機(jī)目前的幾種存儲(chǔ)方式:
在6.0之前
6.0之前荷并,所有的存儲(chǔ)類(lèi)型都是Traditional Storage。它支持多用戶青扔、模擬External存儲(chǔ)源织。由于是MBR分區(qū),存儲(chǔ)上線為2TB微猖。
Physical Primary 最原始的樣子是只有機(jī)身自帶的Internal存儲(chǔ)和以External存在的外置存儲(chǔ)谈息,這時(shí)候只有一個(gè)主存儲(chǔ),并且它是Physical的凛剥。
Emulated Primary (Optional Physical Secondary) 之前所說(shuō)侠仇,從Internal Storage分出一塊來(lái)給External Storage。這塊存儲(chǔ)空間就是在Permanent存儲(chǔ)版中”模擬“上去的犁珠。所以你可以看到主存儲(chǔ)經(jīng)常有emulated
字樣逻炊。 如果這時(shí)候還能再插SD卡,則會(huì)多一個(gè)Physical的Secondary存儲(chǔ)犁享。
在6.0之后
正常情況下余素,它的存儲(chǔ)方式與之前的兩種相同,不過(guò)多了一種新的存儲(chǔ)方式:Adoptable Storage
Adoptable Storage
由于External Storage的缺點(diǎn)(有時(shí)不可用炊昆,存儲(chǔ)內(nèi)容沒(méi)有被保護(hù))桨吊,在6.0之后多出了Adoptable存儲(chǔ)方式。
當(dāng)Android系統(tǒng)Adopt了一塊External存儲(chǔ)區(qū)域的時(shí)候窑眯,它會(huì)被視為Internal Storage屏积,同時(shí)會(huì)被格式化與加密。格式化之后是GPT分區(qū)磅甩,存儲(chǔ)上線為9ZB炊林。
當(dāng)你在一個(gè)支持Adoptable Storage的手機(jī)上插入一個(gè)sd卡,它會(huì)提示你是否將這個(gè)sd卡格式化并用作Internal Storage卷要,或者正常作為External Storage使用渣聚。
推薦一篇文章:
CommonsWare’s post,從不同角度詮釋了Internal&External Storage僧叉, 非常不錯(cuò)奕枝!