Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)
本模块共有四篇文章,参考郭神的《第一行代码》,对Content Provider的学习做一个详细的笔记,大家可以一起交流一下:
- 跨程序共享数据——Content Provider 之 运行时权限解析以及申请的实现(可完美解决java.lang.SecurityException:Permission Denial 问题)
- 跨程序共享数据——Content Provider 之 ContentResolver基本用法 & 一个读取系统联系人的Demo
- 跨程序共享数据——Content Provider 之 创建自己的内容提供器
- Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)(即本文)
简单起见,我们还是在之前的DatabaseTest项目(点击前往码云地址)的基础上继续开发。
需求是:通过内容提供器来给它加人外部访问接口。
程序设计的步骤:
1.在A程序中注册内容提供器,写好接口处理方法; 具体的,全局变量:定义自定义代码常量,定义authority常量,声明uriMatcher和DatabaseHelper对象; 1.1 内容提供器中增删改查的程序设计步骤为: 1.1.1 调用实例化DatabaseHelper的get方法获得SQLiteDatabase实例化对象; (get方法即getWritableDatabase或者getReadableDatabase) 1.1.2 接着,query需定义一个Cursor对象(cursor)用于接收返回结果; insert需定义一个Uri对象(urireturn)用于接收insert新增的数据行的uri update需定义一个int对象(updatedRows)用于接收受影响的行数; delete需定义一个int对象(deletedRows)用于接收受影响的行数(被删除的行数); 1.1.3 使用switch语句进行对uri的判断及判断结果的处理; 2.在需要访问A程序的内容提供器的程序中,构建对应的Uri,通过getContentResolver调用增删改查即可;
下面开始详细解析:
打开DatabaseTest项目(点击前往码云地址),首先将MyDatabaseHelper中使用Toast弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用Toast(!!!!!)。然后创建一个内容提供器,右击com.example.databasetest包—New—Other—ContentProvider,
会弹出如图所示:
这里我们将内容提供器命名为DatabaseProvider,
authority指定为com.example.databasetest.provider,
Exported属性表示是否允许外部程序访问我们的内容提供器,
Enabled属性表示是否启用这个内容提供器。
将两个属性都勾中,点击Finish完成创建。
接着我们修改DatabaseProvider:
package com.example.databasetest; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; public class DatabaseProvider extends ContentProvider { public static final int BOOK_DIR = 0; public static final int BOOK_ITEM = 1; public static final int CATEGORY_DIR = 2; public static final int CATEGORY_ITEM = 3; public static final String AUTHORITY = "com.example.databasetest.provider"; private static UriMatcher uriMatcher; private MyDatabaseHelper dbHelper; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR); uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM); uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR); uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM); } @Override public boolean onCreate() { dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 查询数据 SQLiteDatabase db = dbHelper.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(1); cursor = db.query("Book", projection, "id = ?", new String[] { bookId }, null, null, sortOrder); break; case CATEGORY_DIR: cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); cursor = db.query("Category", projection, "id = ?", new String[] { categoryId }, null, null, sortOrder); break; default: break; } return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { // 添加数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); Uri uriReturn = null; switch (uriMatcher.match(uri)) { case BOOK_DIR: case BOOK_ITEM: long newBookId = db.insert("Book", null, values); //插入后返回一个id!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);//id构造用于返回的URI!!!!!!!!!!!!!!!!!!!!!! break; case CATEGORY_DIR: case CATEGORY_ITEM: long newCategoryId = db.insert("Category", null, values); uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId); break; default: break; } return uriReturn; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 更新数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); int updatedRows = 0; switch (uriMatcher.match(uri)) { case BOOK_DIR: updatedRows = db.update("Book", values, selection, selectionArgs); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); updatedRows = db.update("Book", values, "id = ?", new String[] { bookId }); break; case CATEGORY_DIR: updatedRows = db.update("Category", values, selection, selectionArgs); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); updatedRows = db.update("Category", values, "id = ?", new String[] { categoryId }); break; default: break; } return updatedRows; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); int deletedRows = 0; switch (uriMatcher.match(uri)) { case BOOK_DIR: deletedRows = db.delete("Book", selection, selectionArgs); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); deletedRows = db.delete("Book", "id = ?", new String[] { bookId }); break; case CATEGORY_DIR: deletedRows = db.delete("Category", selection, selectionArgs); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); deletedRows = db.delete("Category", "id = ?", new String[] { categoryId }); break; default: break; } return deletedRows; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case BOOK_DIR: return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.book"; case BOOK_ITEM: return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.book"; case CATEGORY_DIR: return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.category"; case CATEGORY_ITEM: return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.category"; } return null; } }
代码虽然很长,不过都不难理解,都是上一节学习过的内容。
首先在类的一开始,同样是定义了4个常量,分别用于表示
访问Book表中的所有数据;
访问Book表中的单条数据;
访问Category表中的所有数据;
访问Category表中的单条数据。
然后在静态代码块里对UriMatcher进行了初始化操作,将期望匹配的几种URI格式添加了进去。接下来是onCreate()方法:
创建了一个MyDatabaseHelper的实例,然后返回true表示内容提供器初始化成功,这时数据库就已经完成了创建或升级操作。接着是query()方法:
首先获取到SQLiteDatabase实例,然后根据传入的Uri参数判断出用户想要访问哪张表,
再调用SQLiteDatabase的query()进行查询,并将Cursor对象返回即可。
注意当访问单条数据的时候有一个细节:
这里调用了Uri对象的getpathSegments()
方法,它会将内容URI权限之后的部分以“/“符号进行分割,并把分割后
的结果放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id了。
得到了id之后,再通过selection和selectionArgs参数进行约束,就实现了查询单条数据的功能。
getPathSegments().get(1)的解释参考:
然后是insert()方法:
同样先获取到SQLiteDatabase实例,
然后根据传入的Uri参数判断出用户想要往哪张表里添加数据,
再调用SQLiteDatabase的insert()方法进行添加即可。
注意insert()方法要求返回一个能够表示这条新增数据的URI,则这里还需调用Uri.parse()方法来将一个内容URI解析成Uri对象,当然这个内容URI是以新增数据的id结尾的。接着是update()方法:
先获取SQLiteDatabase实例,
然后根据传入的Uri参数判断出用户想要更新哪张表里的数据,
再调用SQLiteDatabase的update()方法进行更新即可,受影响的行数作为返回值返回。然后是delete()方法:
先获取到SQLiteDatabase的实例,
然后根据传入的Uri参数判断出用户想要删除哪张表里的数据,
再调用SQLiteDatabase的delete()方法进行删除即可,被删除的行数作为返回值返回。最后是getType()方法,这里按照上一节中介绍的格式规则编写即可。
至此内容提供器中的代码便全部编写完了。
另外!!内容提供器一定要在AndroidMamfest.xml文件中注册才可以使用,
不过使用Androidstudio的快捷方式创建内容提供器的话,注册会被自动完成。
打开AndroidManifest.xmI文件看一下:
可以看到<application>标签内出现了一个新的标签<provider>,我们使用它来注册内容提供器DatabaseProvider。
其中android:name指定DatabaseProvider的类名,
android:authorities指定了DatabaseProvider的authority,
enabled和exported属性则是根据我们刚才勾选的状态自动生成的。
现在DatabaseTest这个项目便具备跨程序共享数据的功能了。
可以调试一下:
首先将DatabaseTest程序从模拟器中删除掉,防止遗留数据的造成干扰。
然后运行一下项目,将DatabaseTest程序重新安装在模拟器上。
接着关闭掉DatabaseTest这个项目,并创建一个新项目ProviderTest,
接着通过这个程序去访问DatabaseTest中的数据,
先编写布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/add_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Add To Book" /> <Button android:id="@+id/query_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Query From Book" /> <Button android:id="@+id/update_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Update Book" /> <Button android:id="@+id/delete_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Delete From Book" /> </LinearLayout>
MainActivity.java:
package com.example.providertest; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { private String newId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button addData = (Button) findViewById(R.id.add_data); addData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 添加数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book"); ContentValues values = new ContentValues(); values.put("name", "A Clash of Kings"); values.put("author", "George Martin"); values.put("pages", 1040); values.put("price", 55.55); Uri newUri = getContentResolver().insert(uri, values); newId = newUri.getPathSegments().get(1); } }); Button queryData = (Button) findViewById(R.id.query_data); queryData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 查询数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book"); Cursor cursor = getContentResolver().query(uri, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { String name = cursor.getString(cursor. getColumnIndex("name")); String author = cursor.getString(cursor. getColumnIndex("author")); int pages = cursor.getInt(cursor.getColumnIndex ("pages")); double price = cursor.getDouble(cursor. getColumnIndex("price")); Log.d("MainActivity", "book name is " + name); Log.d("MainActivity", "book author is " + author); Log.d("MainActivity", "book pages is " + pages); Log.d("MainActivity", "book price is " + price); } cursor.close(); } } }); Button updateData = (Button) findViewById(R.id.update_data); updateData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 更新数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId); ContentValues values = new ContentValues(); values.put("name", "A Storm of Swords"); values.put("pages", 1216); values.put("price", 24.05); getContentResolver().update(uri, values, null, null); } }); Button deleteData = (Button) findViewById(R.id.delete_data); deleteData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 删除数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId); getContentResolver().delete(uri, null, null); } }); } }
其中值得注意的是:
从以上代码中,我们可以看到
DIR
类型常量匹配的,selection, selectionArgs参数位是由调用本内容提供器的时候由调用方程序提供的,
而ITEM
则不同,uri中已经包含了ID,我们可以使用getPathSegments将之get出来再使用,即ITEM
类型常量匹配的,调用方程序无需提供selection, selectionArgs参数位(如下方的代码截图),uri中已经包含了信息,处理方法也在内容提供器中写好了。
(这一点在调用getContentResolver().update()以及getContentResolver().delete()的时候都一样)
下面进行代码的简析:
分别在这4个按钮的点击事件里面处理了增删改查的逻辑:
添加数据的时候:
首先调用Uri.parse()将内容URI解析成Uri对象,
把要添加的数据存放到ContentValues对象中,
调用ContentResolver的insert()方法执行添加操作即可。
注意insert()会返回一个Uri对象,这个对象中包含了新增数据的id,这里用getPathSegments()将这个id取出,稍后会用到它;查询数据的时候:
调用Uri.parse()将内容URI解析成Uri对象,
调用ContentResolver的query()方法去查询数据,
查询的结果存放在Cursor对象中,
对Cursor进行遍历,从中取出查询结果,并一一打印出来;更新数据的时候:
调用Uri.parse()将内容URI解析成Uri对象,
把想要更新的数据存放到ContentValues对象中,
调用ContentResolver的update()方法执行更新操作即可;
注意这里为了不让Book表中的其他行受到影响,
在调用Uri.parse()方法时,给内容URI的尾部增加了一个id,而这个id正是添加数据时所返回的。
也就是说这里只更新刚刚添加的那条数据,不受影响Book表中的其他行。删除数据的时候,
解析一个以id结尾的内容URI,
调用ContentResolver的delete()方法执行删除操作就可以了,
由于我们在内容URI里指定了一个id,因此只会删掉拥有相应id的那行数据,不会影响Book表中的其他数据。
现在运行一下ProviderTest项目,效果图如下:
点击一下AddToBook按钮,此时数据便已经添加到DatabaseTest程序的数据库中了,
可以点击QueryFromBook按钮来检查一下,打印日志如图:
这里可以看到DatabaseTest程序中只有我们刚刚添加的一条数据,
databaseTest的SQLite数据库是我们在点击Add To Book的时候,试图访问DatabaseTest的内容提供器,由此DatabaseTest的内容提供器(DatabaseProvider)会触发DatabaseProvider.java中的onCreate()方法,如下,
由此创建了数据库,并返回True,这一点在 跨程序共享数据——Content Provider 之 创建自己的内容提供器中曾经有提及;
创建了数据库之后,便添加了添加了一条数据,由此DatabaseTest程序中只有我们刚刚添加的那一条数据而已。
点击一下Update Book按钮来更新数据,再点击一下Query From Book按钮进行检查,结果如图:
最后点击Delete From Book按钮删除数据,此时再点击Query From Book按钮就查询不到数据了。
至此跨程序共享数据功能便成功实现了。
现在不仅是ProviderTest程序,任何一个程序都可以轻松访问DatabaseTest中的数据,同时丝毫不用担心隐私数据泄漏的问题。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
6-VI--ListView琐碎小知识点汇总
1、ListView不显示蓝色阴影: 强迫症的你有没有很讨厌这个阴影,反正我是不喜欢,去除方法: 阴影.png listview.setOverScrollMode(ListView.OVER_SCROLL_NEVER); 2.去除分割线的方式: 去除分割线.png android:divider="@null" 或 android:divider="#00000000"或 listview.setDividerHeight(0); 3.设置分割线:也可在xml中设置,记得设高度 //设置listview的item分割线:能设drawable,就任由发挥了 ListView.setDivider(getResources().getDrawable(R.drawable.XXX)); //设置分割线的高度 ListView.setDividerHeight(10); 分割线样式及长度.png 4.注意点: ListView宽高尽量不用包裹内容,否则由于计算控件高度而多次调用getView 5.添加头布局:一定要在设置适配器之前 添加头脚.png ListView.addHeaderVi...
- 下一篇
又一名程序员倒下,内心悲哀:我们只是新时代农民工
就在这个月的25号,一位程序员倒在了自己的工位上。让很多人感慨万千,瞬间也引发了我作为程序员的思考。 原文是这样的: 原知乎链接: https://zhuanlan.zhihu.com/p/43044382 我们程序员作为互联网行业的编造者与执行者,工作的压力也是巨大的。于是就有一名程序员发帖感叹道:生命只有一次,有感于猝死的IT同行,20多岁的年龄,父母的希望,却陨落在加班的岗位上......所谓的阿里,京东...等企业与当年的血汗工厂没有什么差别。唯一的差别就是体力劳动换成了脑力劳动而已。 于是下面的评论也火了 对于同行倒在工作岗位上, 我想我们每一个码农程序员都应该敲响警钟。据每年猝死人群人数显示,程序员是猝死几率是最高的行业,程序员被成为加班狂人,差不多每天都会有加班。 猝死在工作岗位上?是谁之过错? 21世纪, 尤其是今年2018年,房租 ,房贷,车附带给了我们无形的生活压力。时代在进步,生活质量在提高,相对应的人们也需要更加努力学习新知识,才能保证自己不被甩在人群的后面。生活的焦虑随之而来,大家只能努力往前奔跑着。 到底是生活的压力还是社会的摧残? 作为程序员,我的建议 不...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8编译安装MySQL8.0.19
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7,CentOS8安装Elasticsearch6.8.6
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果