android7.1获取存储权限,Android外部存储
原標(biāo)題:Android外部存儲(chǔ)
作者:莊燦杰,騰訊移動(dòng)客戶端開(kāi)發(fā) 工程師
商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系騰訊WeTest獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
原文鏈接:http://wetest.qq.com/lab/view/368.html
WeTest 導(dǎo)讀
外部存儲(chǔ)作為開(kāi)發(fā)中經(jīng)常接觸的一個(gè)重要系統(tǒng)組成,在Android歷代版本中,有過(guò)許許多多重要的變更。我也曾疑惑過(guò),為什么一個(gè)簡(jiǎn)簡(jiǎn)單單外部存儲(chǔ),會(huì)存在存在這么多奇奇怪怪的路徑:/sdcard、/mnt/sdacrd、/storage/extSdCard、/mnt/shell/emulated/0、/storage/emulated/0、/mnt/shell/runtime/default/emulated/0…其實(shí),這背后代表了一項(xiàng)項(xiàng)技術(shù)的成熟與發(fā)布:模擬外部存儲(chǔ)、多用戶、運(yùn)行時(shí)權(quán)限…
一、各版本外部存儲(chǔ)特性
1、Android 4.0
● 支持模擬外部存儲(chǔ)(通過(guò)FUSE實(shí)現(xiàn))
● 出現(xiàn)了主外部存儲(chǔ),以及二級(jí)外部存儲(chǔ)(沒(méi)有接口對(duì)外暴露)
● 支持MTP(Media Transfer Protocol)、PTP協(xié)議(Picture Transfer Protocol)
2、Android 4.1
● 開(kāi)發(fā)者選項(xiàng)出現(xiàn)”強(qiáng)制應(yīng)用聲明讀權(quán)限才可以進(jìn)行讀操作”的開(kāi)關(guān)
3、Android 4.2
● 支持多用戶,每個(gè)用戶擁有獨(dú)立的外部存儲(chǔ)
4、Android 4.4
● 讀操作需要聲明READ_EXTERNAL_STORAGE權(quán)限
● 應(yīng)用讀寫在外部存儲(chǔ)的應(yīng)用目錄(/sdcard/Android//)不需要聲明權(quán)限
● 增加了Context.getExternalFilesDirs() 接口,可以獲取應(yīng)用在主外部存儲(chǔ)和其他二級(jí)外部存儲(chǔ)下的files路徑
● 引入存儲(chǔ)訪問(wèn)框架(SAF,Storage Access Framework)
5、Android 6.0
● 外部存儲(chǔ)支持動(dòng)態(tài)權(quán)限管理
● Adoptable Storage特性
6、Android 7.0
● 引入作用域目錄訪問(wèn)
補(bǔ)充一個(gè)點(diǎn):
如果應(yīng)用的minSdkVersion和targetSdkVersion設(shè)置成<=3,系統(tǒng)會(huì)默認(rèn)授予READ_EXTERNAL_STORAGE權(quán)限。
二、部分特性講解
1.模擬外部存儲(chǔ)
a. 必要性
● FAT32 屬于微軟專利,可能存在許可和法律問(wèn)題(相關(guān)文章);
● 可以定制Android自己的外部存儲(chǔ)訪問(wèn)規(guī)則;
● 為多用戶做鋪墊;
b. 實(shí)現(xiàn)原理
系統(tǒng)/system/bin/sdcard守護(hù)進(jìn)程,使用FUSE實(shí)現(xiàn)類FAT格式SD卡文件系統(tǒng)的模擬,也就是我們經(jīng)常說(shuō)的內(nèi)置SD卡。(詳細(xì)代碼可以參考:/xref/system/core/sdcard/sdcard.c)
用戶空間文件系統(tǒng)(Filesystem in
Userspace,簡(jiǎn)稱FUSE)是一個(gè)面向類Unix計(jì)算機(jī)操作系統(tǒng)的軟件接口,它使無(wú)特權(quán)的用戶能夠無(wú)需編輯內(nèi)核代碼而創(chuàng)建自己的文件系統(tǒng)。目前Linux通過(guò)內(nèi)核模塊對(duì)此進(jìn)行支持。
sdcard守護(hù)進(jìn)程模擬外部存儲(chǔ)大致流程(Android 4.0為例):
● 首先,指定/data/media目錄用于模擬外部存儲(chǔ)。該路徑的owner和group一般為media_rw,這樣保證只有sdcard程序或root進(jìn)程能夠訪問(wèn)該目錄。
● sdcard守護(hù)進(jìn)程啟動(dòng)后,打開(kāi)/dev/fuse設(shè)備。
● 在/mnt/sdcard目錄掛載fuse文件系統(tǒng)。
● 開(kāi)線程,在線程中處理文件系統(tǒng)事件,并將結(jié)果寫回。
經(jīng)過(guò)上面一系列步驟,sdcard進(jìn)程在/mnt/sdcard路徑上創(chuàng)建了一個(gè)FUSE文件系統(tǒng),所有對(duì)/mnt/sdcard將轉(zhuǎn)為事件由sdcard守護(hù)進(jìn)程處理,并對(duì)應(yīng)到/data/media目錄。
例如,應(yīng)用創(chuàng)建/mnt/sdcard/a文件,實(shí)際是創(chuàng)建/data/media/a文件。
c. 優(yōu)點(diǎn)
● 模擬外部存儲(chǔ)容量和/data分區(qū)是共享的,用戶數(shù)據(jù)在內(nèi)外存儲(chǔ)的分配更加自由;
● 模擬外部存儲(chǔ)本身不可卸載,不會(huì)因?yàn)樾遁d導(dǎo)致應(yīng)用訪問(wèn)出現(xiàn)問(wèn)題,也減少了外部因素導(dǎo)致被破壞的情況;
● 所有的訪問(wèn)都經(jīng)過(guò)sdcard守護(hù)進(jìn)程,Android可以定制訪問(wèn)規(guī)則;
d. 劣勢(shì)
● 性能上存在一定損失
e. 影響
● Android 6.0以后,由于動(dòng)態(tài)權(quán)限管理的需要,會(huì)存在多個(gè)fuse掛載點(diǎn),這導(dǎo)致inotify/FileObserver對(duì)外部存儲(chǔ)進(jìn)行文件事件監(jiān)控時(shí),會(huì)丟失事件。
inotify是Linux核心子系統(tǒng)之一,做為文件系統(tǒng)的附加功能,它可監(jiān)控文件系統(tǒng)并將異動(dòng)通知應(yīng)用程序。 ——
維基百科(https://zh.wikipedia.org/wiki/Inotify)
2、多用戶
a. 支持版本
● Android 4.2開(kāi)始支持多用戶,但僅限平板;
● Android 5.0開(kāi)始,設(shè)備制造商可以在編譯時(shí)候開(kāi)啟多用戶模塊;
b. 背景知識(shí)
● 綁定掛載——mount —bind
MS_BIND (Linux 2.4 onward)
Perform a bind mount, making a file or a directory subtree visible at
another point within a file system. Bind mounts may cross file system
boundaries and span chroot(2) jails. The filesystemtype and
dataarguments are ignored. Up until Linux 2.6.26, mountflagswas also
ignored (the bind mount has the same mount options as the underlying
mount point). —— **mount(2) - Linux man page
圖例(來(lái)自https://xionchen.github.io/2016/08/25/linux-bind-mount):
1) 將/home目錄樹(shù)bind到/mnt/backup:
2) bind完成之后,對(duì)/mnt/backup的訪問(wèn)將等同于對(duì)/home的訪問(wèn),原/mnt/backup變?yōu)椴豢梢?jiàn)。
● 掛載命名空間
Mount namespaces provide isolation of the list of mount points seen by
the processes in each namespace instance. Thus, the processes in each
of the mount namespace instances will see distinct single-directory
hierarchies. ——mount_namespaces(7) - Linux manual page - man7.org
通俗的講,掛載命名空間實(shí)現(xiàn)了掛載點(diǎn)的隔離,在不同掛載命名空間的進(jìn)程,看到的目錄層次不同。
● 掛載傳播之共享掛載、從屬掛載、私有掛載
掛載命名空間實(shí)現(xiàn)了完全的隔離,但對(duì)于有些情況并不適用。例如在Linux系統(tǒng)上,進(jìn)程A在命名空間1掛載了一張CD-ROM,這時(shí)候命名空間2因?yàn)楦綦x無(wú)法看到這張CD-ROM。
為了解決這個(gè)問(wèn)題,引入了掛載傳播(mount propagation)。傳播掛載定義了掛載點(diǎn)的傳播類型:
1)共享掛載,此類型的掛載點(diǎn)會(huì)加入一個(gè)peer group,并會(huì)在group內(nèi)傳播和接收掛載事件;
2)從屬掛載,此類型的掛載點(diǎn)會(huì)加入一個(gè)peer group,并會(huì)接收group內(nèi)的掛載事件,但不傳播;
3)共享/從屬掛載,上面兩種類型的共存體。可以從一個(gè)peer group(此時(shí)類型為從屬掛載)接收掛載事件,再傳播到另一個(gè)peer group;
4)私有掛載,此類型的掛載點(diǎn)沒(méi)有peer group,既不傳播也不接收掛載事件;
5)不可綁定掛載,不展開(kāi)講;
peer group的形成條件為,一個(gè)掛載點(diǎn)被設(shè)置成共享掛載,并滿足以下任意一種情況:
1)掛載點(diǎn)在創(chuàng)建新的命名空間時(shí)被復(fù)制
2)從該掛載點(diǎn)創(chuàng)建了一個(gè)綁定掛載
另外再補(bǔ)充下傳播類型的轉(zhuǎn)換:
1)如果一個(gè)共享掛載是peer group中僅存的掛載點(diǎn),那么對(duì)它應(yīng)用從屬掛載將會(huì)導(dǎo)致它變?yōu)樗接袙燧d。
2)對(duì)一個(gè)非共享掛載類型的掛載點(diǎn),應(yīng)用從屬掛載是無(wú)效的。
背景知識(shí)講到這里,其中掛載點(diǎn)的傳播類型比較不好理解,但很重要,可以參考上面mount namespace的Linux Programmer’s Manual里面的例子(搜索MS_XXX example)進(jìn)行學(xué)習(xí):http://man7.org/linux/man-pages/man7/mount_namespaces.7.html
c. 實(shí)現(xiàn)原理
概括多用戶的外部存儲(chǔ)隔離實(shí)現(xiàn):應(yīng)用進(jìn)程在創(chuàng)建時(shí),創(chuàng)建了新的掛載命名空間,然后通過(guò)綁定掛載對(duì)應(yīng)用暴露當(dāng)前用戶的外部存儲(chǔ)空間。
以Android 4.2代碼為例【mountEmulatedStorage(dalvik_system_Zygote.cpp)】:
● 首先獲取用戶id。在多用戶下,用戶id為應(yīng)用uid/100000。
● 通過(guò)unshare方法創(chuàng)建新的掛載命名空間。
● 獲取外部存儲(chǔ)相關(guān)的環(huán)境變量。EXTERNAL_STORAGE環(huán)境變量是從舊版本沿襲下來(lái)的環(huán)境變量,記錄了外部存儲(chǔ)的傳統(tǒng)路徑。EMULATED_STORAGE_SOURCE環(huán)境變量,記錄綁定掛載的源路徑,注意應(yīng)用是沒(méi)有權(quán)限進(jìn)入這個(gè)目錄的。EMULATED_STORAGE_TARGET記錄綁定掛載的目標(biāo)路徑,應(yīng)用獲取的外部存儲(chǔ)路徑就在這個(gè)目錄下。
● 準(zhǔn)備掛載路徑并進(jìn)行綁定掛載。這里看mountMode為MOUNT_EXTERNAL_MULTIUSER時(shí)的執(zhí)行分支,/mnt/shell/emulated/0將被綁定到/storage/emulated/0。如果是第二個(gè)用戶,則是/mnt/shell/emulated/1綁定到/storage/emulated/1,數(shù)字就是用戶id。注意這里是新的掛載命名空間,所以只有該應(yīng)用看得到/storage/emulated/0下的綁定掛載,從adb shell下是看到的只能是個(gè)空目錄。
● 為了兼容以前的版本,將用戶的外部存儲(chǔ)路徑綁定到EXTERNAL_STORAGE環(huán)境變量指定的路徑。
3. 動(dòng)態(tài)權(quán)限管理
a.背景
Android 6.0引入了運(yùn)行時(shí)權(quán)限,允許用戶對(duì)危險(xiǎn)權(quán)限進(jìn)行動(dòng)態(tài)授權(quán),這部分權(quán)限包含外部存儲(chǔ)訪問(wèn)權(quán)限。
b.實(shí)現(xiàn)原理
外部存儲(chǔ)訪問(wèn)權(quán)限的動(dòng)態(tài)授權(quán),是利用FUSE和掛載命名空間這兩個(gè)技術(shù)配合實(shí)現(xiàn)。
通過(guò)下面這個(gè)提交記錄(https://android.googlesource.com/platform/system/core/+/f38f29c87d97cea45d04b783bddbd969234b1030%5E%21/#F1),我們可以很清楚的了解整個(gè)實(shí)現(xiàn)。
為了達(dá)到不殺死進(jìn)程,就能夠賦予進(jìn)程讀/寫外置存儲(chǔ)的目的,Android利用FUSE對(duì)/data/media模擬了三種訪問(wèn)視圖,分別是default、read、write。
當(dāng)應(yīng)用被授予讀/寫權(quán)限時(shí),vold子進(jìn)程會(huì)切換到應(yīng)用的掛載命名空間,將對(duì)應(yīng)的視圖重新綁定到應(yīng)用的外部存儲(chǔ)路徑上。
切換進(jìn)程的掛載命名空間,需要內(nèi)核版本在3.8及以上,切換函數(shù)為setns,ndk貌似沒(méi)有對(duì)開(kāi)發(fā)者暴露,但可以在源碼里找到arm的實(shí)現(xiàn),有需要直接編入就可以了,也就一個(gè)sys call。
c. 代碼分析
● 源碼版本:Android 6.0.0_r1
● 首先從/xref/system/core/sdcard/sdcard.c開(kāi)始分析,僅摘取部分代碼,并加了些注釋:
● 應(yīng)用進(jìn)程創(chuàng)建時(shí),大致流程如下(/xref/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp):
1)創(chuàng)建新的掛載命名空間;
2)將之前的掛載命名空間在/storage下的掛載全部去除,排除影響;
3)根據(jù)mount_mode,選擇一個(gè)路徑;
4)將選擇的路徑綁定到/storage下。
● 進(jìn)程在運(yùn)行時(shí),當(dāng)外部存儲(chǔ)的訪問(wèn)許可發(fā)生改變(用戶授權(quán))時(shí),基本流程如下(/xref/system/vold/VolumeManager.cpp):
1)獲取init的掛載命名空間,為了對(duì)之后進(jìn)程的掛載命2)名空間進(jìn)行對(duì)比,如果一致,不重新綁定;
3)遍歷/proc下各個(gè)進(jìn)程目錄,根據(jù)uid進(jìn)行篩選;
找到對(duì)應(yīng)的pid后,fork子進(jìn)程進(jìn)行重新掛載,這里用到setns進(jìn)行掛載命名空間的切換;
重新掛載部分的邏輯和應(yīng)用進(jìn)程創(chuàng)建時(shí)基本一致,不難理解。
騰訊WeTest提供上千臺(tái)真實(shí)手機(jī),隨時(shí)隨地進(jìn)行測(cè)試,保障應(yīng)用/手游品質(zhì)。節(jié)省百萬(wàn)硬件費(fèi)用,加速敏捷研發(fā)流程。
同時(shí)騰訊WeTest兼容性測(cè)試團(tuán)隊(duì)積累了10年的手游測(cè)試經(jīng)驗(yàn),旨在通過(guò)制定針對(duì)性的測(cè)試方案,精準(zhǔn)選取目標(biāo)機(jī)型,執(zhí)行專業(yè)、完整的測(cè)試用例,來(lái)提前發(fā)現(xiàn)游戲版本的兼容性問(wèn)題,針對(duì)性地做出修正和優(yōu)化,來(lái)保障手游產(chǎn)品的質(zhì)量。目前該團(tuán)隊(duì)已經(jīng)支持所有騰訊在研和運(yùn)營(yíng)的手游項(xiàng)目
歡迎進(jìn)入:http://wetest.qq.com/product/cloudphone 體驗(yàn)安卓真機(jī)
歡迎進(jìn)入:http://wetest.qq.com/product/expert-compatibility-testing 使用專家兼容測(cè)試服務(wù)。WeTest兼容性測(cè)試團(tuán)隊(duì)期待與您交流!You Create,We Test!
如果對(duì)使用當(dāng)中有任何疑問(wèn),歡迎聯(lián)系騰訊WeTest企業(yè)QQ:800024531返回搜狐,查看更多
責(zé)任編輯:
總結(jié)
以上是生活随笔為你收集整理的android7.1获取存储权限,Android外部存储的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: word-break|overflow-
- 下一篇: Android中的IPC机制