百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Android R如何访问 Androiddata 目录_android 访问文件

toyiye 2024-04-03 23:10 61 浏览 0 评论

作者:GrayMonkey
链接:https://www.jianshu.com/p/d02bc6266198
声明:本文已获GrayMonkey授权发表,转发等请联系原作者授权

前言

Android R上分区存储的限制得到进一步加强,无论APP的targetsdkversion是多少,都将无法访问Android/data和Android/obb这二个应用私有目录。这无疑对会部分APP的业务场景及用户体验造成冲击,典型的如下

  • 文件管理类软件:微信、QQ传输的文件无法展示给用户以便捷使用
  • 垃圾清理类软件:清理缓存功能受阻

“你有你的张良计,我有我的过墙梯”,现市面上文件管理类软件(如MT管理器)已解决上述系统限制,本文将浅析其实现方案,并主要分析以下2个问题:

  • SAF是通过何种方式访问文件系统的,MediaStore API ? File API ? Native Code ?
  • SAF为何能访问Android/data目录

实现方案

其实现方案很简单,就是通过Intent ACTION_OPEN_DOCUMENT_TREE,启动SAF让用户授权访问Android/data目录,属于官方公开的方法。

前提是APP的targetsdkversion要小于30

摘自官方文档

摘自官方文档

文档链接

  • https://developer.android.com/about/versions/11/privacy/storage#file-directory-restrictions
  • https://developer.android.com/training/data-storage/shared/documents-files#grant-access-directory

基本使用

  1. 通过Intent启动SAF授权界面,注意URI的百分号编解码(%3A和%2F),别随意替换,否则SAF无法导航到Android/data目录
     @TargetApi(26)
    private void requestAccessAndroidData(Activity activity){
        try {
            Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3AAndroid%2Fdata");
            Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
            intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
            //flag看实际业务需要可再补充
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                            | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
            activity.startActivityForResult(intent, 6666);
        } catch (Exception e) {
            e.printStackTrace();
        }
    } 

授权申请

  1. 在用户同意授权后,持久化uri权限(否则关机重启或授权界面finish后,APP就无权限访问了),并只能通过DocumentFile进行业务操作,File API操作是无效的,此授权只是授权uri操作,并未授权文件系统,后续章节有说明。
 implementation "androidx.documentfile:documentfile:1.0.1"
  @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case 6666:
                if (resultCode == Activity.RESULT_OK) {
                    //persist uri 
                    getContentResolver().takePersistableUriPermission(data.getData(),
                            Intent.FLAG_GRANT_READ_URI_PERMISSION
                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

                    //now use DocumentFile to do some file op
                    DocumentFile documentFile = DocumentFile
                            .fromTreeUri(this, data.getData());
                    DocumentFile[] files = documentFile.listFiles();
                    ......
                }
                break;
            default:
                break;
        }
    }
  1. 注意这个授权用户是可以撤回的,通过点击应用信息界面的存储,就会看到撤回界面,所以业务需要去动态判断
 public boolean isGrantAndroidData(Context context) {
        for (UriPermission persistedUriPermission : context.getContentResolver().getPersistedUriPermissions()) {
            if (persistedUriPermission.getUri().toString().
                    equals("content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata")) {
                return true;
            }
        }
        return false;
    }

授权撤回

拓展

通过前面二个章节,已经介绍了实现方案的基本使用,下面就该分析本文的亮点内容了

  • SAF是通过何种方式访问文件系统的,MediaStore API ? File API ? Native Code ?
  • SAF为何能访问Android/data目录

存储访问框架(SAF)简介

为方便后续讲解,先简单回顾下SAF

SAF架构

APP

com.example.photos就是我们自己的APP

System UI

com.google.android.documentsui,一般称作DoucmentUI,就是上文中启动的授权界面APP,它只是个UI壳子

DocumentProvider:

DocumentUI中数据的提供者,这个Provider可以有很多 com.android.externalstorage,是本地文件系统的Provider

关于SAF更详细介绍,请参考官方存储访问框架 经过SAF的简单介绍,分析目标很明确,那就是com.android.externalstorage

SAF是通过何种方式访问文件系统的

先安利几个AOSP源码查看网址:

  • https://cs.android.com/android/platform/superproject/
  • http://aospxref.com/

PS:后文源码链接都用的是XREF,方便国内查看

从DocumentFile#listFile入手,经过源码跟踪会发现最终会调用 DocumentsProvider#queryChildDocuments方法

public abstract class DocumentsProvider extends ContentProvider {
 .......
 @Override
    public final Cursor query(
            Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
       switch (mMatcher.match(uri)) {
                ......
                case MATCH_CHILDREN:
                case MATCH_CHILDREN_TREE:
                        .......
                        return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
                        ......
                default:
                    throw new UnsupportedOperationException("Unsupported Uri " + uri);
            }
        } catch (FileNotFoundException e) {
            Log.w(TAG, "Failed during query", e);
            return null;
        }      
   }
 ......
}

接下来看看com.android.externalstorage中DocumentProvider的实现类 ExternalStorageProvider:frameworks/base/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java

import com.android.internal.content.FileSystemProvider;
public class ExternalStorageProvider extends FileSystemProvider 

queryChildDocuments的实现位于其父类 FileSystemProvider

public abstract class FileSystemProvider extends DocumentsProvider {
  ......
  private Cursor queryChildDocuments(
            String parentDocumentId, String[] projection, String sortOrder,
            @NonNull Predicate<File> filter) throws FileNotFoundException {
        final File parent = getFileForDocId(parentDocumentId);
        final MatrixCursor result = new DirectoryCursor(
                resolveProjection(projection), parentDocumentId, parent);
        if (parent.isDirectory()) {
            //重点是这行
            for (File file : FileUtils.listFilesOrEmpty(parent)) {
                if (filter.test(file)) {
                    includeFile(result, null, file);
                }
            }
        } else {
            Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
        }
        return result;
    }
 ......
}

FileUtils#listFilesOrEmpty

    /** {@hide} */
    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
                : ArrayUtils.EMPTY_FILE;
    }

至此,第一个问题,已经理清:SAF的ExternalStorageProvider最终也是通过File API来访问文件系统的

那么第二个问题,就很自然地来了,都是File API操作,为何我们的APP就不能访问呢?

SAF为何能访问Android/data目录

既然,SAF和我们的APP都是File API操作,那我们就去看看com.android.externalstorage属于哪些用户组。adb shell 查查com.android.externalstorage进程的用户组

#查进程ID generic_x86_arm:/ $ ps -A|grep com.android.external u0_a64 16233 296 1256792 85960 0 0 S com.android.externalstorage #查进程所属的用户组

generic_x86_arm:/ nbsp;cat /proc/16233/status
Name:   externalstorage
Umask:  0077
State:  S (sleeping)
Tgid:   16233
Ngid:   0
Pid:    16233
PPid:   296
TracerPid:      0
Uid:    10064   10064   10064   10064
Gid:    10064   10064   10064   10064
FDSize: 64
#重点关注这行输出
Groups: 1015 1077 1078 1079 9997 20064 50064

拿着这些神秘的GID在前面介绍的网址中一搜,就会很容易地发现GID的定义类 android_filesystem_config.h

#define AID_SDCARD_RW 1015       /* external storage write access */
#define AID_EXTERNAL_STORAGE 1077 /* Full external storage access including USB OTG volumes */
#define AID_EXT_DATA_RW 1078      /* GID for app-private data directories on external storage */
#define AID_EXT_OBB_RW 1079       /* GID for OBB directories on external storage */
#define AID_EVERYBODY 9997        /* shared between all apps in the same profile */

其中1078和1079分别对应Android/data和Android/obb的访问权限 如果我们APP能通过某种方式获取到1078和1079的用户组权限,岂不妙哉?遗憾的是,对于三方APP这是不可能的,除非是手机厂商的预置的系统APP

总结

  • Android R上可通过SAF获得访问Android/data和Android/obb目录的权限,前提是APP targetsdkversion 小于30
  • SAF的底层实现ExternalStorageProvider也是通过File API来访问文件系统的
  • SAF之所以能访问Android/data和Android/obb是因为ExternalStorageProvider

进程具有GID 1078 和1079,三方APP是不可能拥有这些GID的

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码