android数据持久化技术:文件存储、sharedpreferences存储、数据库存储
使用这些持久化技术保存的数据只能再当前的应用中访问
但是对于不同应用之间的可以实现跨程序数据的功能
此时使用的是提供器实现跨程序数据共享
5.1、内容提供器简介
内容提供器主要用于再不同的应用程序之间实现数据共享的功能
提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问的数据的安全性
使用内容提供器是android实现跨程序共享数据的标准方式
不同于文件存储和sharedpreferences存储中的两种全局可读可写操作模式
内容提供器可以选择对那一部分数据进行共享
从而保证程序中隐私数据不会有泄露的风险
权限:
android运行时权限,内容提供器中需要使用运行时权限的功能
5.2、运行时权限
android的权限机制从系统的第一个版本已经开始
之前android的权限机制在保护用户安全和隐私等方面作用比较有限
再android6.0之后系统中引入运行时权限这个功能
从而更好的保护了用户的安茜和隐私
5.2.1、权限机制详解
广播机制时使用过:
https://www.cnblogs.com/mrchengs/p/10678609.html
访问系统的网络状态以及监听开机广播设计了用户设备的安全性
因此必须再androidmanifest.xml中加入权限声明,否则程序会崩溃
加了权限之后,对于用户有什么影响?为什么这样就可以保护设备的安全性?
用户再一下两个方面得到了保护:
1、如果用户低于6.0系统的设备上安装该程序
会弹出安装此应用需要以下权限
这样用户 就可以清楚的知道该程序一共需要那些权限
2、用户可以随时在应用程序管理界面查看任意一个程序的权限申请情况
以保证应用程序不会出现各种滥用权限的情况
这种权限机制的设计思路其实很简单
用户如果认可你所申请的权限
那么就会安装程序
用户不认可所申请的权限
那么就会拒绝安装
限制存在很多的软件普遍纯在滥用权限
不管权限是否用得到
都会进行申请权限
在android 6.0系统中加入了运行时权限功能
用户不需要再安装软件时一次性授权所有的申请权限
而是再软件使用过程中在对某一项权限进行授权
不是所有的权限都需要在运行时申请
对于用户来说,不停的授权也很烦
android现在将所有的权限归成了两类:
1、普通权限
2、危险权限
普通权限指的是那些不会直接危险到用户的安全和隐私的权限
对于这部分权限,系统会自动帮助我们进行授权
危险权限指的是那些可能触及用户隐私,或者对设备安全性造成影响的权限
这部分的权限则有用户手动授权才可
android中共有百种多权限
危险权限总共就那么几个
剩余的均是普通权限
危险权限:(9组24个)
这里是一个参照表
每当使用一个权限时可以查询
如果是属于这张表的权限那么就需要运行时处理
在androidmanifest.xml文件中进行注册即可
表格中每个权限都属于一个权限组
运行时权限处理使用的权限名
一旦用户同意了
那么该权限对应的权限组中其他权限也会被授权
权限列表:https://blog.csdn.net/hardworkingant/article/details/70952583
5.2.2、程序运行时申请权限
测试打电话的权限
首先定义一个按钮用于打电话的call:
在androidmanifest.xml中进行权限注册:
在mainactivity中:
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.first_layout); button call = (button) findviewbyid(r.id.call);
call.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
if (activitycompat.checkselfpermission(mainactivity.this,
manifest.permission.call_phone) != packagemanager.permission_granted) {
activitycompat.requestpermissions(mainactivity.this,
new string[]{manifest.permission.call_phone},);
}else {
try {
intent intent = new intent(intent.action_call);
intent.setdata(uri.parse("tel:10086"));
startactivity(intent);
}catch (exception e){
e.printstacktrace();
}
}
}
});
}
这里的action指定了intent.action_call
这是系统内置的拨打电话动作,在data中设置的协议是tel
这个内置的action时直接拨打电话并不是跳转到拨打电话的页面,因此必须声明权限(上述中以进行申请)
运行时权限爱的核心在程序运行的过程中由用户进行授权去执行相关的操作
程序是不可擅自去执行这些操作
第一步是判断用户是否已经授权借助的是:
activitycompat.checkselfpermission()方法接受两个参数
1、context
2、具体的权限名
使用方法的返回值和packagemanager.permission_granted做比较
相等就说明用户已经授权,否则表示没有授权
如果没有授权则执行 activitycompat.requestpermissions()方法,3个参数
1、要求是activity的实例
2、是一个string数组,把要申请的权限名放在数组中即可
3、请求码,必须是唯一值
在弹出请求的按钮之后
不管用户点击那个按钮都会指定onrequestpermissionsresult()
这个方法的逻辑在下面进行实现
授权的结果会封装在grantresults参数中
只需要判断最后的授权结果
用户同意则拨打电话
否则就会提示一条未授权的信息
下面的代码实现:
此时运行程序点击按钮:
这里会有的一个权限的申请允许则进行操作
不允许则进行操作
在mianactivity中添加代码:
public void onrequestpermissionsresult(int requestcode, string[] permissions,
int[] grantresults) { switch (requestcode){
case :
if (grantresults.length > && grantresults[] == packagemanager.permission_granted){
if (activitycompat.checkselfpermission(mainactivity.this,
manifest.permission.call_phone) != packagemanager.permission_granted) {
activitycompat.requestpermissions(mainactivity.this,new string[]{manifest.permission.call_phone},);
}else {
try {
intent intent = new intent(intent.action_call);
intent.setdata(uri.parse("tel:10086"));
startactivity(intent);
}catch (exception e){
e.printstacktrace();
}
}
}else {
toast.maketext(this,"未授权",toast.length_long).show();
}
break;
default:
} }
此时点击拒绝授权:
系统会进行提示
5.3、访问其他程序的数据
内容提供器的用法一般分为两种:
1、使用 现有的内容提供器来读取和操作相应程序中的数据
2、创建自己的内容提供器给我们程序的数据提供外部访问的接口
如果一个程序通过内容提供器对其数据提供了外部访问接口
那么其他任何应用程序就都可以对这部分数据进行访问
android中的电话本、短信、媒体库等程序都提供了类似的访问的接口
使得第三方应用程序可以充分利用这部分数据来实现更好的功能
5.3.1、contentresolver的基本用法
对于每一个应用程序来说
如果需要访问内容提供器中共享的数据
就一定要借助contentresolver类
可以通过context中的getxontentresolver()方法来获取到该类的实例
contentresolver中提供了一系列的方法用于对数据进行crud
其中insert()方法用于添加数据
update()方法用于更新数据
delete()方法用于删除数据
query()方法在、用于查询数据
同sqlitedatabase中的操作有些类似。
但是在操作过程中方法都是不接收表明参数的
使用uri参数代替
这个参数被称为:内容uri
内容uri给内容提供器中数据建立了唯一标识符
两部分组成:
1、authority
用于对不同的应用程序做区分的
一般为了避免冲突,都会采用包名的方式来命名
如:com.example.app.activity1
2、path
用于对同一个应用程序中不同的表进行区分
通常在authority的后面
如:/table1,/table2
uri就是将authority和path的组合
uri=com.example.app.activity1/table1
标准的写法:
content://com.example.app.activity/table1
此时的uri可以鲜明的表达要访问的程序的数据表
contentresolver中的增删该查方法才都接受uri对象作为参数
如果使用表名的话,系统无法得知需要访问的应用程序
得到了url字符串之后
还需要将器解析成uri对象才可以作为参数传入
uri uri = uri.parse("content://com.example.app.activity/table1");
只需要使用uri.parse()即可将内容uri字符串解析成uri对象
使用这个uri对象查询table边坡中的数据
cursor sursor = getcontentresolver().query(
uri,projection,selection,selectionargs,sortorder);
参数解析:
查询之后返回的仍然是一个cursor对象
这是就可以将数据句从cursor对象中逐个读取出来
读取数据是用过移动光标的位置来遍历cursor:
if( cursor != null){
while(cursor.movetonext()){
string c1 = cursor.getstring(cursor.getcolumnindex("column1"));
string c2 = cursor.getstring(cursor.getcolumnindex("column2"));
}
}
添加:
contentvalues values = new contentvalues();
values.put("c1","v1");
values.put("c2","v2");
getcontentresolver().insert(uri,values)
更新:
contentvalues values = new contentvalues();
values.put("c1","v1");
getcontentresolver().update(uri,values,"c1 = ? and c2 = ?",new string [] {"v1","v2"})
使用了selection和selectionargs参数对其进行约束
以防止所有的行动都会受影响
删除:
getcontentresolver().delete(uri,"c1 = ? ",new string [] {"1"});
5.3.2、读取联系人的测试
使用listview进行对联系人的好么进行显示;
android:id="@ id/list_view_phone"
android:layout_width="match_parent"
android:layout_height="match_parent">
在mainactivity中:
public class mainactivity extends appcompatactivity { arrayadapteradapter;
listcontactlist = new arraylist<>(); @override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.first_layout); listview contactsview = (listview) findviewbyid(r.id.list_view_phone);
adapter= new arrayadapter(this,android.r.layout.simple_list_item_1,contactlist);
contactsview.setadapter(adapter); if (activitycompat.checkselfpermission(mainactivity.this,
manifest.permission.read_contacts) != packagemanager.permission_granted) {
activitycompat.requestpermissions(mainactivity.this,
new string[]{manifest.permission.read_contacts},);
}else {
readcontacts();
}
} public void readcontacts(){
cursor cursor = null;
try{
cursor = getcontentresolver().query(contactscontract.commondatakinds.phone.content_uri,
null,null,null,null);
if (cursor != null){
while (cursor.movetonext()){
string name =
cursor.getstring(cursor.getcolumnindex(contactscontract.commondatakinds.phone.display_name));
string number =
cursor.getstring(cursor.getcolumnindex(contactscontract.commondatakinds.phone.number));
contactlist.add(name "\n" number);
}
adapter.notifydatasetchanged();
}
}catch (exception e){
e.printstacktrace();
}finally {
if(cursor != null){
cursor.close();
}
}
} public void onrequestpermissionsresult(int requestcode, string[] permissions,
int[] grantresults) {
switch (requestcode){
case :
if (grantresults.length > && grantresults[]==packagemanager.permission_granted){
readcontacts();
}else {
toast.maketext(this,"未授权",toast.length_long).show();
}
break;
default:
}
} }
在oncreate()方法中
首先获取listview控件,并且设置适配器
然后运行调用运行时权限的处理read_contacts权限属于危险权限
这里使用readcontacts()方法来处理读取联系人的信息
关于readcontacts()方法:
使用contentresolver的query()方法来查询系统的联系人数据
这里传入的contactscontract.commondatakinds.phone.content_uri是已经封装好的uri字符串
在查询之后在对cursor对象进行遍历
将数据逐个取出
此时还需要注册权限:
此时运行程序:
显示之前会进行权限的授权
不授权的同时还会进行提示未授权的字样
5.4、创建自己的内容提供器
参考:https://juejin.im/post/5af812966fb9a07ac85a8188#heading-9
5.4.1、创建内容提供器的步骤
实现跨程序共享数据功能
官方推荐使用内容提供器
可以创建一类继承contentprovider的方式创建一个自己的内容提供器
mycontentprovider.java
public class mycontentprovider extends contentprovider {
@override
public boolean oncreate() {
return false;
}
@nullable
@override
public cursor query(uri uri, string[] projection, string selection, string[] selectionargs, string sortorder) {
return null;
}
@nullable
@override
public string gettype(uri uri) {
return null;
}
@nullable
@override
public uri insert(uri uri, contentvalues values) {
return null;
}
@override
public int delete(uri uri, string selection, string[] selectionargs) {
return ;
}
@override
public int update(uri uri, contentvalues values, string selection, string[] selectionargs) {
return ;
}
}
6个方法:
1、oncreate()
初始化内容提供器时候调用,通常会在这里完成对数据库的创建和升级操作
返回未true表示内容提供器初始化成功
返回为false表示失败
只有当存在contentresolver尝试访问程序中的数据,内容提供器才会初始化
2、query()
从内容提供器中查询数据,使用uri参数确定查询那张表,projection参数用于确定查询那些列
selection和selectionargs参数用于约束那些行,sortorder参数用于对结果进行排序
查询的结果存放在cursor对象中
3、insert()
向内容提供器添加一天数据
使用uri参数来确定要添加的表
待添加的数据保存在values参数中
添加完成之后,返回一个用于表示这条新纪录的uri
4、update()
更新内容提供器中已有的数据
使用uri参数代表来确定更新那张数据表
新数据保存在values参数中
selection和selectionargs参数用于约束更新那些行
受影响的行数将作为返回值返回
5、delete()
从内容提供器中删除数据
使用uri参数来确定删除那一张数据表的数据
selection和selectionargs参数用于约束删除那些行,被删除的行数将作为返回值返回
6、gettype()
根据传入的内容uri返回相应的mime类型
几乎每一个方法都带有uri这个参数
这个参数正是调用contentresolver的增删改查方法时传递过来的
上文中的uri:
content://com.example.app.activity/table1
可以在uri后面加上一个id
content://com.example.app.activity/table1/
表示调用放期望访问的是:
com.example.app.activity的程序的table1数据表中的id为1的数据
注意:
* :表示匹配任意长度的字符
# :表示匹配任意长度的数字
com.example.app.activity/table/*
com.example.app.activity/table/#
在借助urimatcher这个类可以轻松匹配内容uri的功能
提供了adduri()方法接受三个参数
分别可以把authority、path、自定义的代码传进去
当调用urimatcher的match()方法时,就可以把一个uri对象传入
返回值是某个匹配这个uri对象所对应的自定义的代码
在其中新增4个整形常量
table_dir表示访问table1表中所有数据
table_item表示访问table1中的单条数据
在静态代码块中创建了urimatcher的实例
调用adduri()方法,将期望匹配的内容传递进入,这里传递的路径参数可以使用通配符的
在query()方法调用的时候就会通过urimatcher的match()方法传入的uri对象进行匹配
发现urimatcher中某个uri格式成功匹配了该uri对象
则会返回自定义的代码
在对数据表中进行相应的操作即可
gettype()方法
是所有的内容提供器必须提供的一个方法
用于获取uri对象所对应的mime类型
一个内容uri所对应的mime字符串由3部分组成:
此时可以进行完善:
此时一个完整的内容提供器就完成了现在任何一个应用程序都可以使用contentresolver来访问程序中的数据
关于:如何保存隐私数据不会泄露的问题?
内容提供器提供了良好的机制
所有的crud操作都一定要匹配到相应的uri格式才能进行的
不能向urimatcher中添加隐私数据uri
所以这部分数据根本无法被外部程序访问到,安全性问题也就不存在了
5.4.2、实现跨程序数据共享
数据持久化过程中的数据进行实现
通过内容提供器来给他加入外部访问接口
注意:
跨程序访问时不能直接使用toast
右键--->new --->other--->content provider
databasetprovider.java
public class databasetprovider extends contentprovider { public static final int book_dir = ;
public static final int book_item = ; public static final string authority = "com.example.ccrr.applicationtwo";
private mydatabasehelper mydatabasehelper; public static urimatcher urimatcher; static {
urimatcher = new urimatcher(urimatcher.no_match);
urimatcher.adduri(authority,"book",book_dir);
urimatcher.adduri(authority,"book/#",book_item); } public databasetprovider() {
}
@override
public boolean oncreate() {
mydatabasehelper = new mydatabasehelper(getcontext(),"bookstore.db",null,);
return true;
} @override
public int delete(uri uri, string selection, string[] selectionargs) {
sqlitedatabase db = mydatabasehelper.getwritabledatabase();
int deleterows = ;
switch (urimatcher.match(uri)){
case book_dir:
deleterows = db.delete("book",selection,selectionargs);
break;
case book_item:
string bookid = uri.getpathsegments().get();
deleterows=db.delete("book","id = ?",new string[]{bookid});
}
return deleterows;
} @override
public string gettype(uri uri) { switch (urimatcher.match(uri)){
case book_dir:
return "vnd.android.cursor.dir/vnd.com.example.ccrr.applicationtwo.book";
case book_item:
return "vnd.android.cursor.dir/vnd.com.example.ccrr.applicationtwo.book";
}
return null;
} @override
public uri insert(uri uri, contentvalues values) { sqlitedatabase db = mydatabasehelper.getwritabledatabase();
uri urireturn = null;
switch (urimatcher.match(uri)){
case book_dir:
case book_item:
long newbookid = db.insert("book",null,values);
urireturn = uri.parse("content://" authority "/book" newbookid);
break;
}
return urireturn;
} @override
public cursor query(uri uri, string[] projection, string selection,
string[] selectionargs, string sortorder) {
//查询数据
sqlitedatabase db = mydatabasehelper.getreadabledatabase();
cursor cursor = null;
switch (urimatcher.match(uri)){
case book_dir:
cursor = db.query("book",projection,selection,selectionargs,null,null,sortorder);
break;
case book_item:
string bookid = uri.getpathsegments().get();
cursor = db.query("book",projection,"id = ?",new string[]{bookid},null,null,sortorder);
break;
default:
break;
}
return cursor;
} @override
public int update(uri uri, contentvalues values, string selection,
string[] selectionargs) { sqlitedatabase db = mydatabasehelper.getwritabledatabase();
int updaterows = ;
switch (urimatcher.match(uri)){
case book_dir:
updaterows = db.update("book",values,selection,selectionargs);
break;
case book_item:
string bookid = uri.getpathsegments().get();
updaterows = db.update("book",values,"id = ?" , new string[] {bookid});
break;
}
return updaterows; }
}
布局:
mainactivity:
public class mainactivity extends appcompatactivity { private mydatabasehelper mydatabasehelper;
private string bewid;
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.first_layout); mydatabasehelper=new mydatabasehelper(this,"bookstore.db",null,);
button button = (button) findviewbyid(r.id.qqq);
button.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
uri uri = uri.parse("content://com.example.ccrr.applicationtwo/book");
contentvalues values = new contentvalues();
values.put("author","mr");
values.put("price",12.3);
values.put("name","java");
uri newnui = getcontentresolver().insert(uri,values); }
}); button query = (button) findviewbyid(r.id.query_pro);
query.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
uri uri = uri.parse("content://com.example.ccrr.applicationtwo/book");
cursor cursor = getcontentresolver().query(uri,null,null,null,null);
if (cursor != null){
while (cursor.movetonext()){
string name = cursor.getstring(cursor.getcolumnindex("name"));
string price = cursor.getstring(cursor.getcolumnindex("price"));
string author = cursor.getstring(cursor.getcolumnindex("author")); log.d("name",name );
log.d("price",price );
log.d("author", author);
}
cursor.close();
} }
}); final button updata = (button) findviewbyid(r.id.update);
updata.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
uri uri = uri.parse("content://com.example.ccrr.applicationtwo/book");
contentvalues values = new contentvalues();
values.put("name","android");
values.put("price",1.2);
getcontentresolver().update(uri,values,null,null);
}
}); button delete = (button) findviewbyid(r.id.delete);
delete.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
uri uri = uri.parse("content://com.example.ccrr.applicationtwo/book");
getcontentresolver().delete(uri,null,null);
}
});
}
}
android:name=".databasetprovider"
android:authorities="com.example.ccrr.applicationtwo"
android:enabled="true"
android:exported="true">
小小的测试: