Android ContentProvider初探

这几篇将围绕ContentProvider进行安全漏洞分析。 

关于ContentProvider,网上有不少资料,但都零零碎碎,而且样例时间也较老,因此我整合了网上可靠资源,并结合自己实践给出一篇可靠的博客。

这篇将详细介绍ContentProvider的实现原理以及实例。

1. 概述

ContentProvider用于提供数据的统一访问格式,封装底层的具体实现。对于数据使用者来说,无需知晓数据的来源是数据库,文件,或者网络等,只需简单地使用ContentProvider提供的数据操作接口,即增(insert),删(delete),改(update),查(query)四个(下面统称CRUD)方法。

ContentProvider主要用于在不同的应用程序之间实现数据共享的功能【也可以在单应用中用】,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是Android实现跨程序共享数据的标准方式。

1.1  ContentProvider类

ContentProvider是Android四大组件之一,但生命周期简单,只有onCreate过程。ContentProvider是一个抽象类,如果要实现自己的ContentProvider类,只需继承于ContentProvider,并实现以下6个abstract方法:

1.2 ContentResolver类

其他app或者进程想要操作ContentProvider,则需要先获取ContentResolver,再利用ContentResolver类来完成对数据的CRUD。

外部程序通过ContentResolver接口可以访问ContentProvider提供的数据。ContentResolver提供的接口和ContentProvider中需要实现的接口对应,具体method可以查询Android官方 API。

如果在一个Activity中,可以通过以下语句获取ContentResolver对象:

ContentResolver cr = getContentResolver();

然后使用这个ContentResolver的method通过uri和任何ContentProvider交互。

1.3 Uri

上面说到ContentResolver是通过指定Uri对象来和特定ContentProvider对象通信,下面先说下URI。

URI通用格式如下:

[scheme:][//authority][path][?query][#fragment]

在ContentProvider中:

1. scheme为固定“content”

2. authority用于唯一标识这个ContentProvider,外部调用者可根据该标识找到它

3. path:用来表示要操作的数据,路径的构建要根据业务而定

    如contentprovider指定操作数据库,则要操作xxx表中id为10的记录,可以构建路径:/xxx/10

例如content://com.eric.project/android/3

如果要把一个字符串转换成Uri,可以使用Uri类中的parse方法,如下:

Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person")

2. ContentResolver与ContentProvider通信流程

然后看下ContentResolver与ContentProvider是如何用Uri通信的,首先给ContentResolver对象的CRUD方法传入指定Uri对象,然后系统会通过这个Uri的authority查找指定ContentProvider对象,然后通过ContentProvider中的同名CRUD方法调用相应数据操作类,从而执行具体的CRUD操作。整个流程如下:

                   

3. 实例详解

这里给出ContentProvider结合sqlite的四个基本操作CRUD样例。主要实现在一个app(TestCp)中进行CRUD,在另一个app(TestCpCall)中显示的简单功能。

效果如下:

Query:

Insert:

Delete:

Update:

原本的gif失效了,下面是在oppo coloros12.1上新测的

实验环境:实验机Nexus 6P,架构是armv8-aarch64。

首先要建立数据库,这里选用sqlite,但手机没预装sqlite3,参考网上,

法一:源码手动编译,法二:android编码

这里选用法二。

接着给出ContentProvider的6个abstract方法:

  • insert(Uri, ContentValues):插入新数据;
  • delete(Uri, String, String[]):删除已有数据;
  • update(Uri, ContentValues, String, String[]):更新数据;
  • query(Uri, String[], String, String[], String):查询数据;
  • onCreate():执行初始化工作;
  • getType(Uri):获取数据MIME类型。

上述就是要override的method,从而调用sqlite。

3.1 创建sqlite

主要有两种方法:

(1) 继承实现SQLiteOpenHelper:这是Android提供的数据库操作类,需要重写onCreate以及onUpgrade抽象方法,分别在数据库初次创建以及数据库版本更新时用。同时要重构构造函数。

(2) 直接调用SQLiteDataBase类的openOrCreateDatabase():具体见官方API。

这里选用方法一,代码如下:

public class MySQlOpenHelper extends SQLiteOpenHelper {
    /***重载构造***/
    public MySQlOpenHelper(Context context, String name,SQLiteDatabase.CursorFactory factory,int version){
        super(context,name,factory,version);
//        Log.d("cv","openhelper");
    }

    /***重写onCreate***/
    /***创建数据表:stuInfo,数据库初次创建时调用***/
    @Override
    public void onCreate(SQLiteDatabase db){
//        Log.d("cv","create table");
        String createTable="create table "+Constant.tableName +
                "(id integer primary key," +
                "name text)";
        db.execSQL(createTable);
    }

    /***重写onUpgrade**/
    @Override
    public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion){
        Log.i("--------", oldVersion + "------->" + newVersion);
    }
}

3.2 编写ContentProvider实现

有了Sqlite,就要继承ContentProvider类来调用Sqlite方法,针对前面说的6个override method,分别说明。

一般在onCreate中执行创建数据库,这里不赘述。

对Sqlite的操作分别有SQLiteDatabase.execSQL(String SQL语句)  以及 特定的SQLiteDatabase.insert/delete/query/update方法,这里选用特定方法。

3.2.1 insert

在app TestCp中,方法如下:

    @Override
    public Uri insert(Uri uri, ContentValues values){
//        Log.d("cv","insert");
        long newUserId=db.insert(Constant.tableName,null,values);
        return null;
    }

在app TestCpCall中,方法如下:

    public void btnInsert(View v){
        String name=editText.getText().toString();
        ContentValues cv=new ContentValues();
        cv.put("id",idCnt++);   //int
        cv.put("name",name);
        getContentResolver().insert(uri,cv);
//        Log.d("cv","Callinsertfinish");
    }

很简单,就是按key-value形式通过ContentResolver调用ContentProvider进而插入数据到Sqlite。

3.2.2 delete

在app TestCp中,代码如下

@Override
public int delete(Uri uri,String selection,String[] selectionArgs){
    int deleteInt=0;
    deleteInt=db.delete(Constant.tableName,selection,selectionArgs);
    Log.d("cv","deleteLine"+deleteInt);
    return deleteInt;    //操作成功返回行数,失败返回0
}

在app TestCpCall中,代码如下

public void btnDelete(View v){
    String name=editText.getText().toString();
    int deleteInt=getContentResolver().delete(uri,"name=?",new String[]{name});
    if(deleteInt==0) Toast.makeText(this,"no such record",Toast.LENGTH_SHORT).show();  //容错
}

delete方法返回的是操作成功的删除行数,若失败则为0;

3.2.3 update

在app TestCp中,代码如下

@Override
public int update(Uri uri,ContentValues values,String selection,
                  String[] selectionArgs){
   int updateInt=0;
   updateInt=db.update(Constant.tableName,values,selection,selectionArgs);
   return updateInt;
}

在app TestCpCall中,代码如下

public void btnUpdate(View v){
    String updateSrc=editTextSrc.getText().toString();
    String updateTar=editTextTar.getText().toString();
    ContentValues values=new ContentValues();
    values.put("name",updateTar);
    int updateInt=getContentResolver().update(uri,values,"name=?",new String[]{updateSrc});
    if(updateInt==0) Toast.makeText(this,"no such record",Toast.LENGTH_SHORT).show();  //容错
}

其中update的返回值是更新的行数。

3.2.4 query

在app TestCp中,代码如下

@Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs,String sortOrder){
//        Log.d("cv","query");
        Cursor cursor=db.query(Constant.tableName,projection,selection,selectionArgs,null,null,sortOrder);
        return cursor;
    }

在appTestCall中,代码如下

    public void btnQuery(View v){
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        boolean isExist=cursor.moveToFirst();
        try {
            do {
                int id = cursor.getInt(cursor.getColumnIndex("id"));
                String name = cursor.getString(cursor.getColumnIndex("name"));
                Log.d("cv", "Callquery  " + "id:" + String.valueOf(id) + " " + "name:" + name);
            } while (cursor.moveToNext());
        }catch(Exception e){
            Toast.makeText(this,"Table is Empty",Toast.LENGTH_SHORT).show();
//            Log.d("cv","database null");
        }
        cursor.close();
    }

SqliteDataBase的query返回的是查询成功数据的cursor,这里直接查询全部。

3.2.5 getType(Uri)

这个函数按字面意思较难理解,我们先看下它的作用,

参考源码定义

 /**
     * Implement this to handle requests for the MIME type of the data at the
     * given URI.  The returned MIME type should start with
     * <code>vnd.android.cursor.item</code> for a single record,
     * or <code>vnd.android.cursor.dir/</code> for multiple items.
     * This method can be called from multiple threads, as described in
     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
     * and Threads</a>.
     *
     * <p>Note that there are no permissions needed for an application to
     * access this information; if your content provider requires read and/or
     * write permissions, or is not exported, all applications can still call
     * this method regardless of their access permissions.  This allows them
     * to retrieve the MIME type for a URI when dispatching intents.
     *
     * @param uri the URI to query.
     * @return a MIME type string, or {@code null} if there is no type.
     */
    public abstract @Nullable String getType(@NonNull Uri uri);

即根据给定的Uri返回一个MIME类型的数据,

若Uri表单条数据,那MIME返回值应为vnd.android.cursor.item/#格式;

若Uri表多条数据,那MIME返回值应为vnd.android.cursor.dir/#格式。

同时如果应用没有访问该ContentProvider的权限(read/write,or is not exported),getType也是可以被调用的。

上面说到MIME,按照百科的解释,MIME为多用途互联网邮件扩展(MIME,Multipurpose Internet Mail Extensions),是一个互联网标准,简单说浏览器识别不同类型文本(HTML,XML,GIF,FLASH等)就是通过MIME Type,

如html,看Response Header有如下

Javascript类型有如下

其中content-type就表明了数据MIME类型,具体可以看w3c的解释。

然后回过来看getType,参考一些信息,以指定的两种方式开头,android可以识别出是单条数据还是多条数据,即上面所说的单和多数据返回格式,从而在进行其他CRUD操作时,系统能直接识别出是单条还是多条数据,从而不同再去分析了,提高系统性能。

以上代码简单实现了Android中使用ContentProvider与Sqlite来进行CRUD操作。源码见:

ContentProvider简单易用实例,含容错处理,基于AndroidStudio3,gradle-Android代码类资源-CSDN下载

下一章将介绍sqlite中注入,渗透相关技术。

参考:

1】 CRUD:https://zh.wikipedia.org/wiki/%E5%A2%9E%E5%88%AA%E6%9F%A5%E6%94%B9

2】 官网

       1》 ​​​​​​https://developer.android.com/guide/topics/providers/content-providers

       2》 https://developer.android.com/guide/topics/providers/content-provider-basics?hl=zh-cn#ClientProvider

            

3】 第一行代码v2

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值