安卓开发之内容提供器

在恶意软件静态代码分析时,常常会看见的恶意操作就是窃取用户通讯录信息,这个恶意操作是通过内容提供器来实现的,并且底层是通过进程间通信实现的,这次就系统学习一下四大组件之一的内容提供器。

前面所说的文件存储都只能用于当前的应用程序,不能实现应用间程序共享,而有的时候我们想要获取通讯录信息或者短信内容的话,就得实现程序间的数据共享,android中数据共享是通过内容提供者实现的。

内容提供器有两种写法,一是使用现有的内容提供器来读取或操作应用程序中的数据,例如恶意软件中的读取联系人的恶意操作,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口,例如我们读取的短信或通讯录应用就实现了这样的接口。分别学习下这两种方法,当然平常用的较多的是我们去读取应用程序中的数据。

使用现有的内容提供器来读取或操作应用程序中的数据

内容提供器的原理如下图所示(图片引自https://www.runoob.com/w3cnote/android-tutorial-contentprovider.html):

A程序想允许自己的数据能被别的应用进行读取操作,那么就得实现ContentProvider类,同时注册个Uri,然后其他应用B只要使用ContentResolver并根据注册的Uri就可以操作A程序中的数据了。数据可以是数据库、文件、xml或者其他。这里A程序就像是通讯录应用,B程序就像是恶意软件。
在这里插入图片描述
1)先看下A程序注册的Uri是什么:
URI通识资源标识符,和URL类似,都是用来标识资源的,形式如下:
在这里插入图片描述
content为协议,之后为包名.provider,接着是表名。通过Uri.parse的方式将字符串转换为URI。

2)B程序使用的ContentResolver对象提供了增删改查的操作,通过该对象的增删改查方法就可以操作A程序中ContentProvider中的数据。
各方法的参数如下图所示,和sqlite数据库用法相似(图片引自https://www.jianshu.com/p/9048b47bb267):
在这里插入图片描述

写个小Demo验证一下:
使用ContentResolver的query()方法来查询系统的联系人数据。这里发现传入的 Uri 参数是一个常量ContactsContract.CommonDataKinds.Phone.CONTENT_URI不是Uri对象,这是因为ContactsContract.CommonDataKinds.Phone类已经帮我们做好了封装,提供了一个常量CONTENT_URI,而这个常量就是使用 Uri.parse()方法解析出来的结果。接着用Cursor 对象进行遍历,先取出联系人姓名,这一列对应的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,再取出电话号码,这一列对应的常量是ContactsContract.CommonDataKinds.Phone.NUMBER,接着将数据动态添加到list中,并调用notifyDataSetChanged通知ListView已经更新。
在这里插入图片描述
并添加动态权限:
在这里插入图片描述
效果如下:
在这里插入图片描述
当然这里使用的安卓模拟器版本是4.4的,可以这样直接查询,而在6.0版本之上的,需要使用程序运行时权限申请,这里就不进行概述了。

创建自己的内容提供器

这里就拿前面测试sqlite时的应用为例,目标是在其他的应用程序中通过MyProvider去操作Book.db中的数据。
首先修改MyDataBaseHelper,去除Toast,因为跨程序访问时不能直接使用 Toast。
在这里插入图片描述
再新建MyProvider类继承自ContentProvider并重写其6个方法:
在这里插入图片描述
其中onCreate方法是初始化内容提供器的时候会调用,通常在这里完成对数据库的创建和升级等操作,返回true则代表内容提供器初始化成功。并且,只有当ContentResolver尝试访问程序中数据的时候,内容提供器才会被初始化。之后就是增删改查的操作,最后getType方法中根据传入的内容Uri返回相应的MIME类型。

接着我们需要借助UriMater类实现匹配内容URI的功能,UriMater类提供了一个addURI的方法,它接收三个参数(权限,路径,一个自定义代码),这样当调用UriMater类的match方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个自定义代码,我们就可以判断出用户期望访问的是哪张表中的数据了。

修改MyProvider类:
首先一开始定义了四个常量,分别表示访问Book表中的所有数据、访问Book表中的单条数据(Book/#用于表示Book表中任意一行记录)、访问student表中的所有数据和访问student表中的单条数据。然后在静态代码块里对UriMatcher进行了初始化操作,将期望匹配的几种URI格式添加了进去。
在这里插入图片描述
重写onCreate方法,在onCreate方法中新建数据库:
在这里插入图片描述
重写getType()方法,需要返回一个MIME字符串。一个内容URI所对应的MIME字符串主要由三部分组分,Android对这三个部分做了以下格式规定:必须以vnd开头;如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后android.cursor.item/;最后接上vnd.< authority>.< path>。
在这里插入图片描述
重写insert()方法,首先获取到SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要往哪张表里添加数据,再调用SQLiteDatabase实例的insert()方法进行添加。insert()方法要求返回一个能够表示这条新增数据的 URI,所以还需要调用Uri.parse()方法将一个以新增数据的id结尾的内容URI解析成Uri对象。
在这里插入图片描述
重写query方法,首先获取SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要查询哪张表。当访问的是表中的单条数据时调用了Uri对象的getPathSegments()方法,它会将内容URI以“/”进行分割,通过get方法得到分割后的某部分数据,第1个位置存放的就是id。最后要返回一个Cursor对象。
在这里插入图片描述
重写删除方法:和query方法差不多,最后要返回删除的行数
在这里插入图片描述
重写更新方法:和删除差不多,最后返回更新行数
在这里插入图片描述

这样就提供了外部访问接口,新建另一个程序SqliteTest,在这个程序里面实现操纵上面这个程序的数据库:
在这里插入图片描述
效果如下:
在这里插入图片描述
其他的操作和SQLite数据库操作相似,这里就不在展开了。

猜你喜欢

转载自blog.csdn.net/weixin_42011443/article/details/106852231
今日推荐