Android9.0中应用如何通过SAF框架写入外置SD卡
背景介绍 Overview
基于SAF框架写入外置SD卡网上相关资料比较少,现整理一下具体实现方法,如果是访问主存储,弹出授权后即可正常写入,如果是副卡,在Android9.0上必须要使用SAF框架。
本文档详细介绍了应用如何使用Storage Access Framework (SAF框架)访问External SDcard的方法,使得第三方APP或者应用开发者快速集成写入sd卡方法。本文将采用Android原生ScreenShot截图功能集成SAF框架为例,介绍如何打造可以写入外部SD卡方法
申请权限
首先申请写入外部SD卡的权限需要在Activity中,由于Screenshot中无Activity,因此需要创建一个透明Activity来获取Activity的上下文Context
<activity android:name=".screenshot.ScreenshotPermissionsActivity"android:theme="@android:style/Theme.Translucent.NoTitleBar"android:finishOnCloseSystemDialogs="true"android:excludeFromRecents="true"><intent-filter><action android:name="com.android.intent.action.REQUEST_SCREENSHOT_STORAGE_PERMISSION" /></intent-filter>
</activity>
在需要写入T卡的位置先判断是否有写入SD卡权限,如果没有,则启动权限申请的ScreenshotPermissionsActivity
String rootPath = WriteSDFileUtil.getRootPath(mContext);
if (SaveImageInBackgroundTask.SAVE_SPRD_EXTERNAL_STORAGE && !TextUtils.isEmpty(rootPath)) {if (WriteSDFileUtil.hasWriteSDPermission(mContext)) {saveScreenshot2SD(mScreenBitmap);} else {WriteSDFileUtil.startPermissionActivity(mContext);permissonHandler.sendEmptyMessageDelayed(CHECK_PERMISSION, CHECK_DURATION);}
}
getRootPath方法如下:
public static String getRootPath(Context context) {String rootPath = null;StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);List<StorageVolume> volumes = storageManager.getStorageVolumes();for (StorageVolume volume : volumes) {File volumePath = volume.getPathFile();if (!volume.isPrimary() && volumePath != null &&Environment.getExternalStorageState(volumePath).equals(Environment.MEDIA_MOUNTED)&& !volumePath.toString().contains(STORAGE_PATH_EMULATED)) {rootPath = volumePath.toString();}}Log.i(TAG, "getRootPath rootPath: " + rootPath);return rootPath;
}
hasWriteSDPermission方法如下:
public static boolean hasWriteSDPermission(Context context) {return StorageUtil.getInstance().getCurrentAccessUri(context.getContentResolver()) != null;
}
通过getCurrentAccessUri来判断是否有写SD卡的权限,如果getCurrentAccessUri获取的URI为null则无写入SD卡的权限:
public Uri getCurrentAccessUri(ContentResolver contentResolver) {List<UriPermission> uriPermissions = contentResolver.getPersistedUriPermissions();Log.i(TAG, "getCurrentAccessUri exactStorageName ");for (UriPermission permission : uriPermissions) {Log.i(TAG, "getCurrentAccessUri permission: " + permission.toString());return permission.getUri();}Log.i(TAG, "getCurrentAccessUri return null");return null;
}
ScreenshotPermissionsActivity中权限申请代码:
// for external storage access permission
private void requestScopedDirectoryAccess() {int requestCode = -1;StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);List<StorageVolume> volumes = storageManager.getStorageVolumes();Log.i(TAG, "requestScopedDirectoryAccess storagePath: " + WriteSDFileUtil.STORAGE_PATH_EMULATED);for (StorageVolume volume : volumes) {File volumePath = volume.getPathFile();Log.i(TAG, "requestScopedDirectoryAccess volumePath: " + volumePath);if (!volume.isPrimary() && volumePath != null &&Environment.getExternalStorageState(volumePath).equals(Environment.MEDIA_MOUNTED)&& !volumePath.toString().contains(WriteSDFileUtil.STORAGE_PATH_EMULATED)) {mRootPath = volumePath.toString();Log.i(TAG, "really createAccessIntent for mRootPath: " + mRootPath);final Intent intent = volume.createAccessIntent(null);Log.i(TAG, "really createAccessIntent for intent: " + intent);if (intent != null) {intent.putExtra(Intent.EXTRA_PACKAGE_NAME, "com.android.systemui");intent.putExtra("screenshot", true);startActivityForResult(intent, SCOPED_REQUEST_CODE);Log.i(TAG, "really createAccessIntent for intent: " + intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));}}}
}
申请后会弹出权限框
此时如果用户点击授权按钮,则会回调ScreenshotPermissionsActivity中的onActivityResult方法:
public void onActivityResult(int requestCode, int resultCode, Intent data) {Log.d(TAG, " requestCode = " + requestCode + " resultCode = " + resultCode+ " data = " + data);if (requestCode == SCOPED_REQUEST_CODE) {if (resultCode == Activity.RESULT_CANCELED) {Log.d("huasong", "RESULT_CANCELED:");sendBroadcastAsUser(new Intent("action.screenshot.permissin.deny"), UserHandle.ALL);ScreenshotPermissionsActivity.this.finish();} else if (resultCode == Activity.RESULT_OK) {Log.d(TAG, "yeah!!!!");Uri uri = data != null ? data.getData() : null;final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);getContentResolver().takePersistableUriPermission(uri, takeFlags);ScreenshotPermissionsActivity.this.finish();}}
}
在获取RESULT_OK用户授权后,需要调用takePersistableUriPermission保存权限uri,否则下次需要重新授权
写入文件
主要采用WriteSDFileUtil的WriteSDFile方法首先传入RootPath
String picName = String.format(SaveImageInBackgroundTask.SCREENSHOT_FILE_NAME_TEMPLATE,new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(System.currentTimeMillis())));
String rootPath = WriteSDFileUtil.getRootPath(mContext);
WriteSDFileUtil.WriteSDFile(mContext, bitmap, rootPath, picName);
WriteSDFile方法:
public static void WriteSDFile(Context context, Bitmap bitmap, String rootPath, String saveName) {Uri root = StorageUtil.getInstance().getCurrentAccessUri(context.getContentResolver());Uri folder = root;try {String pictureDir = rootPath + File.separator + PICTURE_DIR;String screenshotDir = pictureDir + File.separator + SCREENSHOT_DIR;String saveFileName = screenshotDir + File.separator + saveName;if (new File(pictureDir).exists()) {folder = SafTools.getDocumentFileByPath(context, root, pictureDir).getUri();} else {folder = DocumentsContract.createDocument(context.getContentResolver(), root,DocumentsContract.Document.MIME_TYPE_DIR, PICTURE_DIR);}if (new File(screenshotDir).exists()) {folder = SafTools.getDocumentFileByPath(context, folder, screenshotDir).getUri();} else {folder = DocumentsContract.createDocument(context.getContentResolver(), folder,DocumentsContract.Document.MIME_TYPE_DIR, SCREENSHOT_DIR);}Uri file = SafTools.createDocument(context.getContentResolver(), folder, new File(saveFileName), MIME_IMAGE);try {ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(file, "w");FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());fileOutputStream.write(getBytesByBitmaps(bitmap));fileOutputStream.close();pfd.close();} catch (Exception e) {Log.d(TAG, "exception:", e);}} catch (Exception e) {}
}
SAFTools中createDocument方法:
/**
* Use tarParUri to create this target file. If need to create lots of documents, not suggest.
* @param resolver
* @param tarParUri
* @param target
* @param mimeType
* @return
*/
public static Uri createDocument(ContentResolver resolver, Uri tarParUri, File target, String mimeType){Uri result = null;if(tarParUri != null){try{result = DocumentsContract.createDocument(resolver, tarParUri, mimeType, target.getName());} catch (Exception e) {result = null;Log.e(TAG, "createDocument failed! Exception:"+ e);}}if(result == null){Log.d(TAG,"createDocument failed!");}return result;
}
修改DocumentUI代码
由于权限框拒绝后,下次需要重新弹出,且不可被用户选择“拒绝后不再提示”功能,需要修改DocumentUI弹出框的“不再提示”功能不可见,修改方式如下:
packages\apps\DocumentsUI\com\android\documentsui\ScopedAccessActivity.java
private static boolean mIsForScreenshot;
mIsForScreenshot = intent.getBooleanExtra("screenshot", false);
Log.d(TAG, "isForScreenshot:" + mIsForScreenshot);
if (getScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),mVolumeUuid, directoryName) == PERMISSION_ASK_AGAIN) {mDontAskAgain.setVisibility(View.VISIBLE);mDontAskAgain.setOnCheckedChangeListener(new OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!isChecked);}});
}
if (mIsForScreenshot) {mDontAskAgain.setVisibility(View.GONE);mDialog.setCanceledOnTouchOutside(false);
}
Android9.0中应用如何通过SAF框架写入外置SD卡相关推荐
- android 使用SAF框架操作外置sd卡
android 使用SAF框架操作外置sd卡 在 Android 4.4中,Google 对 SD卡 的访问已经做了严格的限制,在 Android 5.0中,开发者可以使用 新API 要求用户对某个指 ...
- saf java_[原创]Android Storage Access Framework(SAF)框架实现外置SD卡的写入(JAVA层与JNI层HOOK)...
1. 前言 之前折腾了了一下MINE模拟器,发现SDL全是在JNI层fopen操作的,而安卓的SAF则是JAVA层通过DocumentFile和docUri来实现写入的.一种方法是通过去的File D ...
- saf java_Android SAF实现外置SD卡的写入(JAVA层与JNI层hook)
1. 前言 之前折腾了了一下MINE模拟器,发现SDL全是在JNI层fopen操作的,而安卓的SAF则是JAVA层通过DocumentFile和docUri来实现写入的.一种方法是通过去的File D ...
- saf java_Android SAF实现外置SD卡的写入JAVA层与JNI层hook
JAVA层SAF核心代码 通过DocumentFile来实现写入,Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)发送请求docUri,然后onActivityResu ...
- rt-thread SDIO驱动框架分析(SD卡驱动\SD Nand驱动)
rt-thread SDIO驱动框架分析之SD卡驱动 文章目录 rt-thread SDIO驱动框架分析之SD卡驱动 1. 前言 2. SDIO通用驱动框架介绍 3. 文件架构分析 4. SDIO设备 ...
- 安卓 7.0 无法获取外置SD卡问题解决方案 | Failed to find configured root that contains
好久没更新了嘿嘿 最近懒 项目要收尾 一直懒懒的测试 看RXJava什么的 手机系统更新7.0 无意中发现调用相机报错Failed to find configured root that conta ...
- android saf写sd卡,使用SAF(存储访问框架)的Android SD卡写权限
关于如何在SD卡( android 5及以上版本)中编写(和重命名)文件的大量调查结果后,我认为android提供的新SAF需要获得用户写入SD卡文件的许可. 我在这个文件管理器应用程序ES文件资源管 ...
- android的访问存储权限,使用SAF(存储访问框架)的Android SD卡写权限
关于如何在SD卡(android 5及以上版本)中编写(和重命名)文件的大量调查结果后,我认为android提供的新SAF需要获得用户写入SD卡文件的许可. 我在这个文件管理器应用程序ES文件资源管理 ...
- android中拷贝assets下的资源文件到SD卡中(可以超过1M)
很多手机游戏,在安装APK之后都得需要下载相应的资源包,然后才能进入游戏. 有这样一个需求:就是游戏中需要的资源包打在APK内,随apk一起进行安装到手机中. 这样就不需要,在安装APK之后,去下载资 ...
最新文章
- C# 格式串(收藏)
- UIButton状态探索和自定义
- phpcms开启、关闭在线编辑模板的方法
- linux动态库文件.so为什么有多个版本号?(多个名字)(小版本升级)
- scp: /usr/java: Permission denied
- 采用java信号量(semaphore)让线程轮流打印
- OpenCV3学习(4.3)——图像形态学(膨胀,腐蚀)
- 转:消息队列的使用场景
- 渲染器跑分_碾压图灵43!NVIDIA安培第一个跑分震撼出炉
- 第十章:SpringCloud Zuul路由器和过滤器
- 文献阅读:《Generative Adversarial Active Learning for Unsupervised Outlier Detection》-2020 trans
- 5g pdu session_设备 | NEC被选为NTT DOCOMO独立5G移动核心的供应商
- 魔法门之英雄无敌3 android,魔法门之英雄无敌3 v0.86.04
- Vue3 + TS(一)- 邂逅Vue
- yolov5的anchors及bbox的编解码原理
- webshell多种方法免杀
- 2.电调(ESC)-XP7A刷BLHeli固件(四轴专用,更快响应)
- 《高质量读研:教你如何写论文、做科研》- 张军平
- STM32标准外设库
- 如何规划好自己的读博生涯