• IPC 之 ContentProvider 的使用


    一、概述

      ContentProvider 是 Android 中提供的专门用于不同应用间进行数据共享的方式。和 Messenger 一样,ContentProvider 的底层实现同样也是 Binder 。ContentProvider 的数据源不止包括 SQLite 数据库,还可以是文件数据。通过将数据储存层和应用层分离,ContentProvider 为各种数据源提供了一个通用的接口。系统预置了许多 ContentProvider ,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过 ContentResolver 的 query 、update 、insert 和 delete 方法即可。ContentProvider 主要以表格的形式来组织数据,并且可以包含多个表。对于表的定位示例工程也会展示。

      下面内容:

        ① 创建、注册以及访问 ContentProvider 

        ② 如何定位到具体的数据?

        ③ 通过 openFile() 方法实现文件数据的共享

        ④ ContentProvider 安全防范

        ⑤ getType() 方法说明

        ⑥ 示例工程代码(双应用)------》 数据源 SQLite 、实现文件共享、多表操作

    1、创建、注册以及访问 ContentProvider

      创建一个 SLProvider 继承 ContentProvider , 并实现 onCreate()、query()、insert()、update()、delete()、getType()六个抽象方法 。其中 onCreate 方法是在 UI 线程中执行,所以不能在该方法中做耗时操作。

    public class SLProvider extends ContentProvider {
        @Override
        public boolean onCreate() {
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
            return null;
        }
    
        @Nullable
        @Override
        public String getType(@NonNull Uri uri) {
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
            return null;
        }
    
        @Override
        public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
            return 0;
        }
    
        @Override
        public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
            return 0;
        }
    }

      注册 SLProvider :为了访问安全,用到了自定义权限 com.sl.Provider

    <provider
           android:name=".SLProvider"
           android:authorities="com.sl.provider"
           android:exported="true"
           android:permission="com.sl.Provider" />

      访问 ContentProvider :(注意如果声明自定义权限,那么访问端就需要声明该权限)

    Uri uri = Uri.parse("content://com.sl.provider");
    Cursor bookCursor = getContentResolver().query(uri, null, null, null, null);

    2. 如何定位到具体的数据?

      采用Content Uri,一个Content Uri如下所示:

    content://com.sl.provider/book

     

      它的组成一般分为三部分:

     

        (1)content://: 作为 content Uri的特殊标识(必须);

        (2)权(authority):用于唯一标识这个 ContentProvider,外部访问者可以根据这个标识找到它;在AndroidManifest中也配置的有 ----->"com.sl.provider"

        (3)路径(path): 所需要访问数据的路径,根据业务而定。 ------>  "/book"

    • 3. 通过 openFile() 方法实现文件数据的共享

      根据 Uri 提供文件的句柄:

    @Nullable
        @Override
        public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
            Log.i("openFile", uri.getPath());
            if (getContext() != null) {
                String filePath = getFilePath(uri, getContext());
                File file = new File(filePath);
                // ParcelFileDescriptor.MODE_READ_ONLY:只可读
                // ParcelFileDescriptor.MODE_WRITE_ONLY:只可写
                // ParcelFileDescriptor.MODE_READ_WRITE:可读可写
                return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
            }
            return super.openFile(uri, mode);
        }
    
        private String getFilePath(Uri uri, Context context) {
            String path = null;
            switch (matcher.match(uri)) {
                case A_FILE_URI_CODE:
                    path = context.getFilesDir().getParent() + "/a.txt";
                    break;
                case B_FILE_URI_CODE:
                    path = context.getFilesDir().getParent() + "/b.txt";
                    break;
            }
            return path;
        }

      在访问端访问文件数据:在访问的时候就会调用 ContentProvider 的 openFile 方法获取 文件句柄,进行访问文件。

                String uri = "content://com.sl.provider/b";
                try {  
                    // 处理方式1:直接通过uri读文件  
                    InputStream inputStream = context.getContentResolver()
                            .openInputStream(Uri.parse(uri));
                    BufferedReader bufferedReader = new BufferedReader(
                            new InputStreamReader(inputStream));
                    String lineStr = null;  
                    while ((lineStr = bufferedReader.readLine()) != null) {  
                        System.out.println("lineStr:" + lineStr);  
                    }  
                    bufferedReader.close();  
      
                    // 处理方式2:直接通过uri写文件  
                    context.getContentResolver().openOutputStream(Uri.parse(uri),  
                            "w");  
      
                    // 处理方式3:把文件拷贝到本地处理  
                    AssetFileDescriptor descriptor = context.getContentResolver()
                            .openAssetFileDescriptor(Uri.parse(uri), "r");  
                    InputStream is = descriptor.createInputStream();  
                    // 根据uri解析文件名  
                    int start = uri.lastIndexOf('/');  
                    String fileName = uri.substring(start + 1);  
                    // 把文件拷贝到本地data/data/包名/files 目录下  
                    fileName = context.getFilesDir().getAbsolutePath() + "/"  
                            + fileName;  
                    OutputStream os = new FileOutputStream(new File(fileName));
                    byte[] b = new byte[1024];  
                    int len;  
                    while ((len = is.read(b)) != -1) {  
                        os.write(b, 0, len);  
                    }  
                    os.flush();  
                    is.close();  
                    os.close();  
                    // 读取文件  
                    File file = new File(fileName);  
                    if (file.isFile() && file.exists()) {  
                        InputStreamReader reader = new InputStreamReader(  
                                new FileInputStream(file));
                        BufferedReader buffered = new BufferedReader(reader);  
                        String lineTxt = null;  
                        while ((lineTxt = buffered.readLine()) != null) {  
                            System.out.println("lineTxt:" + lineTxt);  
                        }  
                        reader.close();  
                        buffered.close();  
                    }  
      
                } catch (Exception e) {  
                    e.printStackTrace();  
                }      

    4. ContentProvider 安全防范

      (1)正确定义私有权限,设置权限等级,自定义权限可以看这里

      (2)防止本地SQL注入注意:一定不要使用拼接来组装SQL语句。 如果Content Provider的数据源是 SQLite 数据库,如果使用拼接字符串的形式组成原始SQL语句执行,则会导致SQL注入。

      (3)防止目录遍历

          a、去除Content Provider中没有必要的openFile()接口。

          b、过滤限制跨域访问,对访问的目标文件的路径进行有效判断:
      (4)通过检测签名来授权合作方应用访问

        如果必须给合作方的APP提供Provider的访问权限,而合作方的APP签名证书又于自己公司的不同,可将合作方的APP的签名哈希值预埋在提供Provider的APP中,提供Provider的APP要检查请求访问此Provider的APP的签名,签名匹配通过才让访问。

    5. getType() 方法说明

      getType 的作用应该是这样的,以指定的两种方式开头,android可以顺利识别出这是单条数据还是多条数据,query 方法查询结果是一个 Cursor,我们可以根据 getType 方法中传进来的 Uri 判断出query 方法要返回的 Cursor 中只有一条数据还是有多条数据,这个有什么用呢?如果我们在 getType 方法中返回一个 null 或者是返回一个自定义的 android 不能识别的MIME类型,那么当我们在 query 方法中返回 Cursor 的时候,系统要对 Cursor 进行分析,进而得出结论,知道该Cursor只有一条数据还是有多条数据,但是如果我们按照 Google 的建议,手动的返回了相应的 MIME ,那么系统就不会自己去分析了,这样可以提高一丢点的系统性能。基于此,我们自定义的 ContentProvider 中的 getType 方法可以这么写:

        @Override  
        public String getType(Uri uri) {  
            int code = matcher.match(uri);  
            switch (code) {  
            case 1:  
                // 查询多条数据  
                return "vnd.android.cursor.dir/multi";  
            case 2:  
            case 3:  
                // 根据id或者姓名查询一条数据  
                return "vnd.android.cursor.item/single";  
            }  
            return null;  
        }  

      MIME 前面的一部分我们按照 Google 的要求来写,后面一部分就可以根据我们自己的实际需要来写。还有一种我们可能会很少遇到的情况,我们有可能不知道 ContentProvider 返回给我们的是什么,这个时候我们可以先调用 ContentProvider 的 getType,根据 getType 的不同返回值做相应的处理。

    6. 示例工程

      链接: https://pan.baidu.com/s/1TU5-NzTxRo0NW6hbW1Mu5A  密码: 25pp

    参考:

    Android之通过ContentProvider共享文件

    Android安全开发之Provider组件安全

    对ContentProvider中getType方法的一点理解

  • 相关阅读:
    ESB数据发布思路
    Mongo客户端MongoVUE的基本使用
    ESB数据采集思路
    css3学习笔记之按钮
    发布Qt程序时别忘了带上plugins(codecs等)
    [Qt]Cannot retrieve debugging output
    Linux下编译opencv库[转]
    OpenCV库Windows下QT编译及使用
    ipad4自动下载了ios8的安装包,好几个G啊,不想更新,怎么删了呢?
    VC: error LNK2019:unresolved external symbol *** referenced in function ***的解决方案
  • 原文地址:https://www.cnblogs.com/aimqqroad-13/p/8986987.html
Copyright © 2020-2023  润新知