androidQ sd卡權(quán)限使用詳解
默認(rèn)情況下,如果應(yīng)用以 Android Q 為目標(biāo)平臺(tái),則在訪問(wèn)外部存儲(chǔ)設(shè)備中的文件時(shí)會(huì)進(jìn)入過(guò)濾視圖。應(yīng)用可以使用 Context.getExternalFilesDir() 將專用于自己的文件存儲(chǔ)在特定于自己的目錄中。
1. 臨時(shí)停用分區(qū)存儲(chǔ)行為:
以 Android 9(API 級(jí)別 28)或更低版本為目標(biāo)平臺(tái)。
如果您以 Android Q 為目標(biāo)平臺(tái),請(qǐng)?jiān)趹?yīng)用的清單文件中將 requestLegacyExternalStorage 的值設(shè)為 true。
<manifest ... > <!-- This attribute is "false" by default on apps targeting Android Q. --> <application android:requestLegacyExternalStorage="true" ... > ... </application> </manifest>
2. 如何實(shí)現(xiàn)隔離存儲(chǔ):
2.1 ApplicationInfo新增PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE標(biāo)記
PackageParser.java:
if (sa.getBoolean(
R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
}
ApplicationInfo.java:
public boolean hasRequestedLegacyExternalStorage() {
return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0;
}
2.2 grantRuntimePermission()重新掛載視圖
apk啟動(dòng)時(shí)默認(rèn)掛載runtime/default視圖,grantRuntimePermission()時(shí)如果是READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE,則會(huì)獲取掛載模式重新掛載對(duì)應(yīng)視圖。
PermissionManagerService.java:
private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
int callingUid, final int userId, PermissionCallback callback) {
......
if (READ_EXTERNAL_STORAGE.equals(permName)
|| WRITE_EXTERNAL_STORAGE.equals(permName)) {
final long token = Binder.clearCallingIdentity();
try {
if (mUserManagerInt.isUserInitialized(userId)) {
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
獲取掛載模式這塊android10有修改,沒(méi)有設(shè)置Legacy標(biāo)志的話,總是獲取default掛載模式,沒(méi)有讀寫(xiě)權(quán)限。
android 10會(huì)設(shè)置屬性[persist.sys.isolated_storage]: [true],因此走到if(ENABLE_ISOLATED_STORAGE)中的getMountMode()。
public static boolean hasIsolatedStorage() {
//[persist.sys.isolated_storage]: [true]
//[sys.isolated_storage_snapshot]: [true]
return SystemProperties.getBoolean("sys.isolated_storage_snapshot",
SystemProperties.getBoolean("persist.sys.isolated_storage", true));
}
private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
public int getExternalStorageMountMode(int uid, String packageName) {
+ //android 10新增邏輯
+ if (ENABLE_ISOLATED_STORAGE) {
+ return getMountMode(uid, packageName);
+ }
......
int mountMode = Integer.MAX_VALUE;
for (ExternalStorageMountPolicy policy : mPolicies) {
final int policyMode = policy.getMountMode(uid, packageName);
if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
mountMode = Math.min(mountMode, policyMode);
}
if (mountMode == Integer.MAX_VALUE) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
return mountMode;
}
正常模式下hasLegacy=false,走到if判斷的DEFAULT分支;legacy模式hasLegacy=true,與之前保持一致,有write權(quán)限就走到WRITE模式分支。
private int getMountModeInternal(int uid, String packageName) {
try {
......
final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE);
......
final boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE,
uid, packageName) == MODE_ALLOWED;
if (hasLegacy && hasWrite) {
return Zygote.MOUNT_EXTERNAL_WRITE;
} else if (hasLegacy && hasRead) {
return Zygote.MOUNT_EXTERNAL_READ;
} else {
return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
} catch (RemoteException e) {
// Should not happen
}
return Zygote.MOUNT_EXTERNAL_NONE;
}
2.3 Legacy Storage屬性對(duì)權(quán)限的影響
安裝apk時(shí),就會(huì)根據(jù)requestLegacyExternalStorage屬性來(lái)對(duì)ops state進(jìn)行設(shè)置,修改OP_LEGACY_STORAGE的默認(rèn)狀態(tài)。
<manifest ... > <application android:requestLegacyExternalStorage="true" ... > </application> </manifest> //Q 正常模式 LEGACY_STORAGE: mode=ignore //Q legacy模式 LEGACY_STORAGE: mode=allow
PermissionPolicyService啟動(dòng)時(shí)首先進(jìn)行權(quán)限變化監(jiān)聽(tīng):
public void onStart() {
permManagerInternal.addOnRuntimePermissionStateChangedListener(
this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
}
private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName,
@UserIdInt int changedUserId) {
if (isStarted(changedUserId)) {
synchronized (mLock) {
if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
PermissionPolicyService
::synchronizePackagePermissionsAndAppOpsForUser,
this, packageName, changedUserId));
}
......
}
}
}
APK安裝時(shí),會(huì)根據(jù)requestLegacyExternalStorage屬性來(lái)通知storage權(quán)限變化,調(diào)用關(guān)系如下:
//調(diào)用關(guān)系:
1.PackageManagerService.java:
installPackagesLI()
commitPackagesLocked()
updateSettingsLI()
updateSettingsInternalLI()
2.PermissionManagerService.java:
mPermissionManager.updatePermissions()
restorePermissionState()
//關(guān)鍵代碼:
private void restorePermissionState(@NonNull PackageParser.Package pkg, boolean replace,
@Nullable String packageOfInterest, @Nullable PermissionCallback callback) {
......
//判斷requestLegacyExternalStorage屬性
updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, updatedUserIds);
......
for (int userId : updatedUserIds) {
notifyRuntimePermissionStateChanged(pkg.packageName, userId);
}
}
最終調(diào)用到PermissionPolicyService的監(jiān)聽(tīng)函數(shù)synchronizePackagePermissionsAndAppOpsForUser(),進(jìn)行默認(rèn)權(quán)限獲取和設(shè)置。
當(dāng)apk安裝時(shí),聲明了requestLegacyExternalStorage="true"屬性,并且聲明了READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE,那么addOpIfRestricted()就會(huì)將LEGACY_STORAGE設(shè)置為allow模式。
//調(diào)用關(guān)系:
synchronizePackagePermissionsAndAppOpsForUser():
synchroniser.addPackage()
addOpIfRestricted()//LEGACY_STORAGE加入到mOpsToAllow
synchroniser.syncPackages()
setUidModeAllowed()
setUidMode()//設(shè)置LEGACY_STORAGE為allow
//關(guān)鍵代碼:
private void addOpIfRestricted(@NonNull PermissionInfo permissionInfo,
@NonNull PackageInfo pkg) {
......
//forPermission()會(huì)根據(jù)requestLegacyExternalStorage的值進(jìn)行返回
final SoftRestrictedPermissionPolicy policy =
SoftRestrictedPermissionPolicy.forPermission(mContext, pkg.applicationInfo,
mContext.getUser(), permission);
final int op = policy.resolveAppOp();
if (op != OP_NONE) {
switch (policy.getDesiredOpMode()) {
case MODE_DEFAULT:
mOpsToDefault.add(new OpToChange(uid, pkg.packageName, op));
break;
case MODE_ALLOWED:
//在聲明READ_EXTERNAL_STORAGE權(quán)限下,會(huì)將LEGACY_STORAGE加入到mOpsToAllow
if (policy.shouldSetAppOpIfNotDefault()) {
mOpsToAllow.add(new OpToChange(uid, pkg.packageName, op));
} else {
mOpsToAllowIfDefault.add(
new OpToChange(uid, pkg.packageName, op));
}
break;
......
}
public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
@Nullable ApplicationInfo appInfo, @Nullable UserHandle user,
@NonNull String permission) {
switch (permission) {
case READ_EXTERNAL_STORAGE: {
if (appInfo != null) {
boolean hasAnyRequestedLegacyExternalStorage =
appInfo.hasRequestedLegacyExternalStorage();
hasRequestedLegacyExternalStorage = hasAnyRequestedLegacyExternalStorage;
}
return new SoftRestrictedPermissionPolicy() {
@Override
public int getDesiredOpMode() {
if (applyRestriction) {
return MODE_DEFAULT;
} else if (hasRequestedLegacyExternalStorage) {
//聲明了requestLegacyExternalStorage就返回allow
return MODE_ALLOWED;
} else {
return MODE_IGNORED;
}
}
@Override
public boolean shouldSetAppOpIfNotDefault() {
return getDesiredOpMode() != MODE_IGNORED;
}
};
}
3. sdcard路徑權(quán)限說(shuō)明:
- rwx:421,umask默認(rèn)為八進(jìn)制022(----w--w-)
- /mnt/runtime/default的gid為1015,也就是sdcard_rw;mask 為6,八進(jìn)制006,group sdcard_rw可讀寫(xiě),也就是other沒(méi)有rw權(quán)限
- /mnt/runtime/read的gid為9997,也就是everybody;mask 為23,八進(jìn)制027,group everybody可讀、不可寫(xiě),other沒(méi)有讀寫(xiě)執(zhí)行權(quán)限
- /mnt/runtime/write的gid為9997,也就是everybody;mask 為7,八進(jìn)制007,group everybody可讀寫(xiě),other沒(méi)有讀寫(xiě)可執(zhí)行權(quán)限
- /data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal)
/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal)
/mnt/runtime/default:
drwxrwx--x 3 root sdcard_rw 4096 2018-12-18 03:41 Android drwxrwx--x 3 root sdcard_rw 4096 2018-12-18 06:11 DCIM
/mnt/runtime/read:
drwxr-x--- 3 root everybody 4096 2018-12-18 03:41 Android drwxr-x--- 3 root everybody 4096 2018-12-18 06:11 DCIM
/mnt/runtime/write:
drwxrwx--- 3 root everybody 4096 2018-12-18 03:41 Android drwxrwx--- 3 root everybody 4096 2018-12-18 06:11 DCIM
/sdcard/Android/data:
drwxrwx--- 4 u0_a64 everybody 4096 2018-12-18 06:11 com.android.camera2 drwxrwx--- 3 u0_a15 everybody 4096 2018-12-18 03:41 com.google.android.gms drwxrwx--- 4 u0_a84 everybody 4096 2018-12-18 03:41 com.google.android.youtube
4. sdcard文件存儲(chǔ)示例:
4.1 getExternalFilesDir()隨卸載而刪除
///storage/emulated/0/Android/data/com.xx.xx/files File file = File(context.getExternalFilesDir(null), "test.txt");
4.2 媒體文件
媒體文件使用MediaStore操作,卸載后不會(huì)刪除。
訪問(wèn)其他應(yīng)用生成的照片、視頻、音頻,需要READ_EXTERNAL_STORAGE權(quán)限。
4.3 存儲(chǔ)訪問(wèn)框架(SAF)
訪問(wèn)其他應(yīng)用創(chuàng)建的文件,例如"Download"目錄,必須使用存儲(chǔ)訪問(wèn)框架,用戶通過(guò)框架選擇特定文件。
4.4 照片中的位置信息
需要ACCESS_MEDIA_LOCATION權(quán)限,才能獲取元數(shù)據(jù)中的位置信息。
<permission android:name="android.permission.ACCESS_MEDIA_LOCATION" android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_mediaLocation" android:description="@string/permdesc_mediaLocation" android:protectionLevel="dangerous" />
到此這篇關(guān)于androidQ sd卡權(quán)限使用詳解的文章就介紹到這了,更多相關(guān)androidQ sd卡權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android開(kāi)發(fā)獲取手機(jī)Mac地址適配所有Android版本
- 詳解Android版本適配:9.0 Pie
- 適配Android 8.0版本更新安裝與通知欄的一些坑
- Android StatusBar 透明化方法(不同的版本適配)
- Android6.0獲取動(dòng)態(tài)權(quán)限代碼示例
- Android6.0 動(dòng)態(tài)權(quán)限機(jī)制深入講解
- AndroidQ分區(qū)存儲(chǔ)權(quán)限變更及適配的實(shí)現(xiàn)
- Android運(yùn)行時(shí)權(quán)限終極方案(PermissionX)
- Android 如何實(shí)現(xiàn)動(dòng)態(tài)申請(qǐng)權(quán)限
- Android普通應(yīng)用升級(jí)為系統(tǒng)應(yīng)用并獲取系統(tǒng)權(quán)限的操作
- Android 實(shí)現(xiàn)永久性開(kāi)啟adb 的root權(quán)限
- Android 拍照選擇圖片并上傳功能的實(shí)現(xiàn)思路(包含權(quán)限動(dòng)態(tài)獲取)
- Android 版本、權(quán)限適配相關(guān)總結(jié)
相關(guān)文章
Java 判斷兩個(gè)字符串是否由相同的字符組成的實(shí)例
今天小編就為大家分享一篇Java 判斷兩個(gè)字符串是否由相同的字符組成的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
Maven如何構(gòu)建可執(zhí)行的jar包(包含依賴jar包)
這篇文章主要介紹了Maven如何構(gòu)建可執(zhí)行的jar包(包含依賴jar包) ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11
Spring MVC實(shí)現(xiàn)一次簡(jiǎn)單的CRUD示例
這篇文章主要介紹了Spring MVC實(shí)現(xiàn)一次簡(jiǎn)單的CRUD示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Spring 事件監(jiān)聽(tīng)機(jī)制實(shí)現(xiàn)跨模塊調(diào)用的思路詳解
之前一個(gè)項(xiàng)目,有兩個(gè)模塊,A 模塊需要依賴 B 模塊,但現(xiàn)在 B 模塊有地方需要調(diào)用 A 模塊的方法,如果直接依賴,又會(huì)產(chǎn)生循環(huán)依賴問(wèn)題,最終選擇使用 spring 的事件監(jiān)聽(tīng)來(lái)解決該問(wèn)題,下面給大家介紹Spring 事件監(jiān)聽(tīng)機(jī)制實(shí)現(xiàn)跨模塊調(diào)用的思路,感興趣的朋友一起看看吧2024-05-05
java實(shí)現(xiàn)mongodb的數(shù)據(jù)庫(kù)連接池
這篇文章主要介紹了基于java實(shí)現(xiàn)mongodb的數(shù)據(jù)庫(kù)連接池,Java通過(guò)使用mongo-2.7.3.jar包實(shí)現(xiàn)mongodb連接池,感興趣的小伙伴們可以參考一下2015-12-12

