省流提示:采用android studio工具開發(fā),記錄一次低級的開發(fā),避免以后忘記或者踩坑。
最近有個業(yè)余項目開發(fā)到一小半,過程中需要讀寫 Android/data目錄的文件,采用常規(guī)的文件操作總是提示權(quán)限被拒絕,無奈上網(wǎng)參考了很多資料,終于得到了解決。
無法訪問Android/data 的原因
安卓11谷歌采用了文件沙盒存儲模式,這就導致我的app無法直接訪問android/data目錄,即使我在清單文件中加了所有文件的讀寫權(quán)限、在程序中動態(tài)申請了所有文件的讀寫權(quán)限。當然如果有root權(quán)限那就另當別論了。
作為開發(fā)者該如何訪問Android/data
第一步當然是在清單文件中申請所有文件權(quán)限
<!--讀寫-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
在java中動態(tài)申請:MainActivity.java的onCreate()周期中:
if (Build.VERSION.SDK_INT >= 23) {
//檢測是否有寫的權(quán)限
int permission = ActivityCompat.checkSelfPermission(this,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 沒有寫的權(quán)限,去申請寫的權(quán)限,會彈出對話框,這里就不展示了,可以自己寫個申請權(quán)限的彈窗
}
做完上面兩步,接下來就是申請android/data的權(quán)限了,這里說明一下我的情況:
我的主頁就一個:mainactivity,全部采用了動態(tài)替換Fragment的方法切換頁面,而且需要申請?zhí)厥鈾?quán)限的還是fragment中的子framgent,這就需要在他們所依賴的activity中重寫回調(diào)方法,并設(shè)置給fragment,然后通過這個fragment再次設(shè)置給子fragment。
在子fragment中某個按鈕的點擊事件中去調(diào)用SAF框架(Android Storage Access Framework),這個框架據(jù)說在4.4就引入了,可以自行百度了解。
獲取Android/data的權(quán)限:子fragment中:
//在某個按鈕的點擊事件中:
@Override
public void onClick(View view) {
switch (view.getId()) {
? ? ? ? ? ? case R.id.xxxx:
? ? ? ? ? ? ? ? //把需要授權(quán)的目錄轉(zhuǎn)為Uri
? ? ? ? ? ? Uri uri = DocumentFileUtil.pathToUri("/storage/emulated/0/Android/data");
? ? ? ? ? ? ? ? Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
? ? ? ? ? ? ? intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,uri1);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
? ? ? ? ? ? ? ? //這里的第二個參數(shù)是標志的意思,用來判斷是誰把結(jié)果回調(diào)過來的。
requireActivity().startActivityForResult(intent, 1001);
? ? ? ? ? ? break;
? ? ? ? }
}
這時候會調(diào)出這個頁面:

在點擊授權(quán)后,程序會返回到MainActivity,所以要在這個活動中重寫onActivityResult:
? ? @Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
? ? ? ? //把onActivityResult設(shè)置fragment
Fragment f = getSupportFragmentManager().findFragmentByTag("HOME_FRAGMENT");
assert f != null;
f.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1001:
if (resultCode == Activity.RESULT_OK) {
//固定永久權(quán)限,否則當重啟app時權(quán)限就沒有了
assert data != null;
getContentResolver().takePersistableUriPermission(data.getData(),
Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
break;
default:
break;
}
通過activity設(shè)置onActivityResult給framgent后,如果子fragment也需進行相關(guān)操作,則需要通過父fragment再把onActivityResult設(shè)置給子framgent。這里有一點需注意,當我們的子fragment是父fragment通過viewPager容器添加進去的時候,沒有Tag也沒有ID,如何精準的設(shè)置給需要的這個回調(diào)的子fragment呢?
如果適配器是繼承的FragmentStatePagerAdapter,這就要用到fragment適配器的instantiateItem了。具體做法:在父fragment中尋找沒有tag和id的子fragment:
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
? ? ? ? //實例化適配器
MyFragmentAdapter fragmentAdapter = (MyFragmentAdapter) vp.getAdapter();
assert fragmentAdapter != null;
? ? ? ? //通過子fragment在viewPager的position來定位。
UpDataMsgFragment fragment = (UpDataMsgFragment) fragmentAdapter.instantiateItem(vp, 1);
? ? ? ? //設(shè)置
fragment.onActivityResult(requestCode, resultCode, data);
}
這樣一來子fragment就有了回調(diào),然后看他何如使用:
//回調(diào)事件,可以得到上個活動或fragment返回的結(jié)果
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//判斷文件是否存在 DocumentFile
switch (requestCode) {
case 6666:
if (resultCode == Activity.RESULT_OK) {
//persist uri
//固定永久權(quán)限
assert data != null;
requireActivity().getContentResolver().takePersistableUriPermission(data.getData(),
Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Log.i("回調(diào)事件:", "Access persist uri permission to Android/data");
? ? ? ? ? ? ? ? }
? ? ? ? }
}
此時子fragment就有了回調(diào)結(jié)果,可以通過Document來操作andriod/data的文件了
注意:用戶可以隨時取消你的data訪問權(quán)限,所以避免閃退,還需要在用到Document的地方寄一個判斷有無相關(guān)目錄的權(quán)限。文章來源:http://www.zghlxwxcb.cn/news/detail-647355.html
public boolean isHaveAndroidDataGrant(Context context) {
boolean b = true;
for (UriPermission persistedUriPermission : context.getContentResolver().getPersistedUriPermissions()) {
b = persistedUriPermission.getUri().toString().equals("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata");
? ? ? ? ? ? ? ? //這里的content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata不渴隨意更改里面的%3Ahe %2F字符,可按此格式自行拼接需要的目錄。
}
return b;
}
總結(jié):先在清單文件申請權(quán)限,然后在程序中動態(tài)申請,其次在需要授權(quán)的activity(或fragment)中啟用SFA框架,最后在回調(diào)回來的activity中重寫onActivityResult,如果是多層嵌套的fragment,則通過tag或id或position找到對應(yīng)的fragment,一層一層的傳遞過去。文章來源地址http://www.zghlxwxcb.cn/news/detail-647355.html
到了這里,關(guān)于安卓學習筆記:安卓11訪問/讀寫 Android/data 目錄的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!