背景介绍 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卡相关推荐

  1. android 使用SAF框架操作外置sd卡

    android 使用SAF框架操作外置sd卡 在 Android 4.4中,Google 对 SD卡 的访问已经做了严格的限制,在 Android 5.0中,开发者可以使用 新API 要求用户对某个指 ...

  2. saf java_[原创]Android Storage Access Framework(SAF)框架实现外置SD卡的写入(JAVA层与JNI层HOOK)...

    1. 前言 之前折腾了了一下MINE模拟器,发现SDL全是在JNI层fopen操作的,而安卓的SAF则是JAVA层通过DocumentFile和docUri来实现写入的.一种方法是通过去的File D ...

  3. saf java_Android SAF实现外置SD卡的写入(JAVA层与JNI层hook)

    1. 前言 之前折腾了了一下MINE模拟器,发现SDL全是在JNI层fopen操作的,而安卓的SAF则是JAVA层通过DocumentFile和docUri来实现写入的.一种方法是通过去的File D ...

  4. saf java_Android SAF实现外置SD卡的写入JAVA层与JNI层hook

    JAVA层SAF核心代码 通过DocumentFile来实现写入,Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)发送请求docUri,然后onActivityResu ...

  5. rt-thread SDIO驱动框架分析(SD卡驱动\SD Nand驱动)

    rt-thread SDIO驱动框架分析之SD卡驱动 文章目录 rt-thread SDIO驱动框架分析之SD卡驱动 1. 前言 2. SDIO通用驱动框架介绍 3. 文件架构分析 4. SDIO设备 ...

  6. 安卓 7.0 无法获取外置SD卡问题解决方案 | Failed to find configured root that contains

    好久没更新了嘿嘿 最近懒 项目要收尾 一直懒懒的测试 看RXJava什么的 手机系统更新7.0 无意中发现调用相机报错Failed to find configured root that conta ...

  7. android saf写sd卡,使用SAF(存储访问框架)的Android SD卡写权限

    关于如何在SD卡( android 5及以上版本)中编写(和重命名)文件的大量调查结果后,我认为android提供的新SAF需要获得用户写入SD卡文件的许可. 我在这个文件管理器应用程序ES文件资源管 ...

  8. android的访问存储权限,使用SAF(存储访问框架)的Android SD卡写权限

    关于如何在SD卡(android 5及以上版本)中编写(和重命名)文件的大量调查结果后,我认为android提供的新SAF需要获得用户写入SD卡文件的许可. 我在这个文件管理器应用程序ES文件资源管理 ...

  9. android中拷贝assets下的资源文件到SD卡中(可以超过1M)

    很多手机游戏,在安装APK之后都得需要下载相应的资源包,然后才能进入游戏. 有这样一个需求:就是游戏中需要的资源包打在APK内,随apk一起进行安装到手机中. 这样就不需要,在安装APK之后,去下载资 ...

最新文章

  1. C# 格式串(收藏)
  2. UIButton状态探索和自定义
  3. phpcms开启、关闭在线编辑模板的方法
  4. linux动态库文件.so为什么有多个版本号?(多个名字)(小版本升级)
  5. scp: /usr/java: Permission denied
  6. 采用java信号量(semaphore)让线程轮流打印
  7. OpenCV3学习(4.3)——图像形态学(膨胀,腐蚀)
  8. 转:消息队列的使用场景
  9. 渲染器跑分_碾压图灵43!NVIDIA安培第一个跑分震撼出炉
  10. 第十章:SpringCloud Zuul路由器和过滤器
  11. 文献阅读:《Generative Adversarial Active Learning for Unsupervised Outlier Detection》-2020 trans
  12. 5g pdu session_设备 | NEC被选为NTT DOCOMO独立5G移动核心的供应商
  13. 魔法门之英雄无敌3 android,魔法门之英雄无敌3 v0.86.04
  14. Vue3 + TS(一)- 邂逅Vue
  15. yolov5的anchors及bbox的编解码原理
  16. webshell多种方法免杀
  17. 2.电调(ESC)-XP7A刷BLHeli固件(四轴专用,更快响应)
  18. 《高质量读研:教你如何写论文、做科研》- 张军平
  19. STM32标准外设库
  20. 如何规划好自己的读博生涯

热门文章

  1. flex datagrid组件中添加别的组件
  2. RSA加解密算法原理
  3. 易拉罐WiFi收集器
  4. 玩转STM32F0 Value Line Discovery 之 点亮LED
  5. iphone4s改装 linux,iPhone4S降级教程(支持iOS5.1.1)可实现完美越狱
  6. 《Non-contact Eye Gaze Tracking System by Mapping of Corneal Reflections》论文阅读
  7. 程序员应该掌握的统计学公式
  8. 最清晰的进制转换讲解 - java实现
  9. 美国SIG声学相机G100主要功能
  10. LaTex案例——制作三线表