赞
踩
一、什么是ContentProvider
ContentProvider是Android的四大组件之一,主要用于给不同应用程序提供接口,实现数据共享,并且可以保证数据的安全性。在手机的联系人、短信等应用都会创建ContentProvider提供接口将应用内数据提供给其他应用使用。ContentProvider的底层实现还是使用Binder,主要是以表格的形式操作存储数据,并且可以包含多张表格;也支持文件类型的数据储存操作,如图片、视频等。
二、ContentProvider的基础知识
BroadCast需要使用BroadCast Receive接收广播,而Content Provider数据存储需要借助ContentResolver类提供的一系列方法进行增删改查操作。ContentResolver可以通过Context的getContentResolver()方法获取实例。ContentResolver实例访问对应的数据,使用CRUD方法对数据进行增删改查。
1.内容 URI
内容 URI 给内容提供器中的数据建立了唯一标识符,内容URI的定义和Intentfilter中的data标签下的Uri基本是一致的,它由三部分组成,协议声明(content)、权限(authority)和路径(path)。权限(authority)是对不同应用的区分主要是“应用的包名”,路径(path)是对同一个应用“不同表格”的区分,总结起来就是访问“哪个应用/哪张表”,例如:
content://com.example.app.provider/table1
content://com.example.app.provider/table2/1
内容 URI有两种格式,以路径结尾的格式代表访问“整个表”;以数字id结束的表示访问表中的“某一列”,可以使用通配符的方式区分。例如:
//匹配所有的表
content://com.example.app.provider/*
//匹配table2表中的某一列
content://com.example.app.provider/table2/#
将字符串解析为Uri对象
Uri uri = Uri.parse("content://com.example.app.provider/table1);
2.getType()方法
ContentProvider必须实现的一个方法,是为了获取Uri对象对应的MIME类型。一个内容Uri对应的MIME类型主要分为3个部分组成与它的格式有关系。格式为:
//路径对应dir,匹配表,标准:"vnd."+"android.cursor.dir/"+"vnd."+"authority.path"
vnd.android.cursor.dir/vnd.com.example.app.provider/table1
//id对应item,匹配列,标准:"vnd."+"android.cursor.item/"+"vnd."+"authority.path",注意路径后面未带id
vnd.android.cursor.item/vnd.com.example.app.provider/table1
3.创建SQLite数据库
创建数据库是供ContentProvider进行增删改查的操作的,创建SQLite需要注意建表语句的书写注意空格;必须要有一个构造方法;必须要实现onCreat、update方法。先创建数据库,在调用onCreat方法时创建表,后通过数据库的实例对表进行操作。
package com.mrdouya.mycontenprovidertest; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; public class MyDatabaseHelper extends SQLiteOpenHelper { private Context mContext; private final String TAG = "MrDouYa-DatabaseHelper"; //建表语句,注意空格问题 public static final String CREAT_TABLE = "create table Category (" + " id integer primary key autoincrement" + ", category_name text" + ", category_code integer)"; public static final String CREAT_CATTEGORY = "create table Book (" + " id integer primary key autoincrement" + ", author text" + ", prices real" + ", pages integer" + ", name text)"; /** * 必须实现父类的构造方法 * @param context * @param name * @param factory * @param version */ public MyDatabaseHelper(Context context,String name,SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); mContext = context; } /** * 必须重写父类的onCreat方法 * @param sqLiteDatabase */ @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { //创建两张表 sqLiteDatabase.execSQL(CREAT_TABLE); sqLiteDatabase.execSQL(CREAT_CATTEGORY); Log.d(TAG,"table book have create!"); } /** * 必须重写父类的onUpgrade方法 * @param sqLiteDatabase * @param i * @param i1 */ @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { //比较强盗,查询如果存在表就删除 sqLiteDatabase.execSQL("drop table if exists Book"); sqLiteDatabase.execSQL("drop table if exists Category"); //onCreate创建表 onCreate(sqLiteDatabase); Log.d(TAG,"onUpgrade Book have update!"); } }
4.六个必须实现的父类方法
package com.mrdouya.mycontenprovidertest; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.util.Log; public class MyProvider extends ContentProvider { private final String TAG = "MrDouYa"; /** * 初始化内容提供器使用 * 完成对数据库的创建和升级等操作 */ @Override public boolean onCreate() { Log.d(TAG,"MyProvider--->onCreate"); return false; } /** * 从内容提供器中查询数据,返回Cursor对象,uri为查询哪张表 * @param uri * @param strings * @param s * @param strings1 * @param s1 * @return */ @Override public Cursor query(Uri uri,String[] strings,String s,String[] strings1,String s1) { return null; } /** * 更新已有的数据,更据参数约束,返回值为受影响的行数 * @param uri * @param contentValues * @param s * @param strings * @return */ @Override public int update(Uri uri,ContentValues contentValues,String s,String[] strings) { return 0; } /** * 向内容提供器添加一条数据,返回一个用于表示这条新记录的URI * @param uri * @param contentValues * @return */ @Override public Uri insert(Uri uri,ContentValues contentValues) { return null; } /** * 删除一条数据,返回值为被删除的行数 * @param uri * @param s * @param strings * @return */ @Override public int delete(Uri uri,String s,String[] strings) { return 0; } /** * 更具传入的Uri的返回对应的MIME类型 * @param uri * @return */ @Override public String getType(Uri uri) { return null; } }
三、实际使用ContentProvider
1.访问其他程序的ContentProvider
非常简单可以分为3个步骤:
注意:访问权限声明如获取联系人权限、ContentProvider重要方法的“返回值”
package com.mrdouya.mycontenprovidertest; import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; public class MyProviderTest extends Activity implements View.OnClickListener { public static final String TAG = "MrDouYa-MyProviderTest"; private String newId; private Button queryButton; private Button updateButton; private Button deleteButton; private Button insertButton; private Uri uri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_provider); queryButton = findViewById(R.id.bt_provider_query_book); updateButton = findViewById(R.id.bt_provider_update_book); deleteButton = findViewById(R.id.bt_provider_delete_book); insertButton = findViewById(R.id.bt_provider_add_book); queryButton.setOnClickListener(this); updateButton.setOnClickListener(this); deleteButton.setOnClickListener(this); insertButton.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.bt_provider_add_book: //Uri唯一标志,访问com.mrdouya.mycontenprovidertest应用下的Book表 uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book"); //一般使用ContentValues将数据封装 ContentValues contentValues = new ContentValues(); contentValues.put("name","A Clash of Kings"); contentValues.put("author","Geoge Martin"); contentValues.put("pages",1040); contentValues.put("prices",22.85); //获取getContentResolver()实例调用insert方法将uri和contentValues传入,返回一个“被改变的Uri地址” Uri newUri = getContentResolver().insert(uri,contentValues); //获取newUri地址中被改变的id,用于后续删除、更新Book表中现在插入的这条数据 newId = newUri.getPathSegments().get(1); Log.d(TAG,"insert book over!,newId = "+newId); break; case R.id.bt_provider_query_book: //Uri唯一标志 uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book"); //查询,返回cursor对象 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 prices = cursor.getDouble(cursor.getColumnIndex("prices")); Log.d(TAG,"query book name:"+name+" ,author : "+author+" ,pages :"+pages+" ,prices :"+prices); } cursor.close(); } Log.d(TAG,"query book over!"); break; case R.id.bt_provider_update_book: //Uri唯一标志+newId上面插入的一条记录的id uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book/"+newId); //添加数据 ContentValues values = new ContentValues(); values.put("name","A Storm of Swords"); values.put("pages",1216); values.put("prices",24.05); //更新 getContentResolver().update(uri,values,null,null); Log.d(TAG,"update book over!"); break; case R.id.bt_provider_delete_book: //删除 uri= Uri.parse("content://com.mrdouya.mycontenprovidertest/Book/"+newId); getContentResolver().delete(uri,null,null); Log.d(TAG,"delete book over!"); break; } } }
执行插入、查询、更新、删除、更新的Log如下:
2019-09-04 13:58:56.990 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: insert book over!,newId = 1
2019-09-04 13:59:04.050 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book name:A Clash of Kings ,author : Geoge Martin ,pages :1040 ,prices :22.85
2019-09-04 13:59:04.055 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2019-09-04 13:59:15.941 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: update book over!
2019-09-04 13:59:19.158 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book name:A Storm of Swords ,author : Geoge Martin ,pages :1216 ,prices :24.05
2019-09-04 13:59:19.162 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2019-09-04 13:59:31.015 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: delete book over!
2019-09-04 13:59:34.697 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2.创建ContentProvider提供接口
1)需要创建数据库,提供可操作的表或数据,如第二节第4点
2)创建ContentProvider,提供可访问的Uri,如下:
在Androidmanifest.xml中声明,ContentProvider是四大组件之一,需要注意android:authorities属性表明该内容提供器的权限
<provider
android:name="com.mrdouya.mycontenprovidertest.MyProvider"
android:authorities="com.mrdouya.mycontenprovidertest"
/>
自定义的ContentProvider:
package com.mrdouya.mycontenprovidertest; 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; import android.util.Log; public class MyProvider extends ContentProvider { private final String TAG = "MrDouYa-MyProvider"; //定义int用于区分是访问哪张表的路径或者item 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; //与android:authorities一致,Uri唯一标志中的author public static final String AUTHOR = "com.mrdouya.mycontenprovidertest"; //数据库实例 private MyDatabaseHelper myDatabaseHelper; //UriMatcher可以实现Uri的匹配,先定义 private static UriMatcher uriMatcher; /** *初始化uriMatcher,并添加能访问这个ContentProvider的Uri唯一标志 *addURI()传入的是AUTHOR、path、自定义代码 */ static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHOR,"Book",BOOK_DIR);//表 uriMatcher.addURI(AUTHOR,"Book/#",BOOK_ITEM);//item uriMatcher.addURI(AUTHOR,"Category",CATEGORY_DIR); uriMatcher.addURI(AUTHOR,"Category/#",CATEGORY_ITEM); } /** * 初始化内容提供器使用 * 完成对数据库的创建和升级等操作 */ @Override public boolean onCreate() { myDatabaseHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,3); Log.d(TAG,"onCreate"); return false; } /** * 从内容提供器中查询数据,返回Cursor对象,uri为查询哪张表 * @param uri * @param strings * @param s * @param strings1 * @param s1 * @return */ @Override public Cursor query(Uri uri,String[] strings,String s,String[] strings1,String s1) { Log.d(TAG,"query"); //getReadableDatabase()方法获取“读取SQLite”的实例,实际通过SQLiteDatabase的query方法去查询数据 SQLiteDatabase db = myDatabaseHelper.getReadableDatabase(); Cursor cursor = null; //match()方法返回值用于匹配当前ContentProvider的Uri唯一标识 switch (uriMatcher.match(uri)){ case BOOK_DIR : cursor = db.query("Book",strings,s,strings1,null,null,s1); break; case BOOK_ITEM : //如果访问item,就获取对应item的id //getPathSegments()会获取Uri中author之后的部分,并以“/”分割字符放入字符串列表,列表第一列“0”为路径,第二列“1”为id String bookId = uri.getPathSegments().get(1); cursor = db.query("Book",strings,"id = ?",new String[]{ bookId },null,null,s1); break; case CATEGORY_DIR : cursor = db.query("Category",strings,s,strings1,null,null,s1); break; case CATEGORY_ITEM : String categoryId = uri.getPathSegments().get(1); cursor = db.query("Category",strings,"id = ?",new String[]{ categoryId },null,null,s1); break; default: break; } return cursor; } /** * 更新已有的数据,更据参数约束,返回值为受影响的行数 * @param uri * @param contentValues * @param s * @param strings * @return */ @Override public int update(Uri uri,ContentValues contentValues,String s,String[] strings) { Log.d(TAG,"update"); SQLiteDatabase db = myDatabaseHelper.getWritableDatabase(); int updatedRows = 0; switch (uriMatcher.match(uri)){ case BOOK_DIR : updatedRows = db.update("Book",contentValues,s,strings); break; case BOOK_ITEM : String bookId = uri.getPathSegments().get(1); updatedRows = db.update("Book",contentValues,"id = ?",new String[]{ bookId }); break; case CATEGORY_DIR : updatedRows = db.update("Category",contentValues,s,strings); break; case CATEGORY_ITEM : String categoryId = uri.getPathSegments().get(1); updatedRows = db.update("Category",contentValues,"id = ?",new String[]{ categoryId }); break; default: break; } return updatedRows; } /** * 向内容提供器添加一条数据,返回一个用于表示这条新记录的URI * @param uri * @param contentValues * @return */ @Override public Uri insert(Uri uri,ContentValues contentValues) { Log.d(TAG,"insert"); SQLiteDatabase db = myDatabaseHelper.getWritableDatabase(); Uri uriReturn = null; switch (uriMatcher.match(uri)){ case BOOK_DIR: case BOOK_ITEM: //获取SQLite增加数据后返回item的id long newBookId = db.insert("Book",null,contentValues); //将id添加到Uri唯一标志上,返回一个新Uri唯一标志 uriReturn = Uri.parse("content://"+AUTHOR+"/Book/"+newBookId); break; case CATEGORY_DIR: case CATEGORY_ITEM: long newCategoryId = db.insert("Category",null,contentValues); uriReturn = Uri.parse("content://"+AUTHOR+"/Category/"+newCategoryId); break; } return uriReturn; } /** * 删除一条数据,返回值为被删除的行数 * @param uri * @param s * @param strings * @return */ @Override public int delete(Uri uri,String s,String[] strings) { Log.d(TAG,"delete"); SQLiteDatabase db = myDatabaseHelper.getWritableDatabase(); int deleteRows = 0; switch (uriMatcher.match(uri)){ case BOOK_DIR : deleteRows = db.delete("Book",s,strings); break; case BOOK_ITEM : String bookId = uri.getPathSegments().get(1); deleteRows = db.delete("Book","id = ?",new String[]{ bookId }); break; case CATEGORY_DIR : deleteRows = db.delete("Category",s,strings); break; case CATEGORY_ITEM : String categoryId = uri.getPathSegments().get(1); deleteRows = db.delete("Category","id = ?",new String[]{ categoryId }); break; default: break; } return deleteRows; } /** * 更具传入的Uri的返回对应的MIME类型 * @param uri * @return */ @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)){ case BOOK_DIR : return "vnd.android.cursor.dir/vnd.com.mrdouya.mycontenprovidertest.Book"; case BOOK_ITEM : return "vnd.android.cursor.item/vnd.com.mrdouya.mycontenprovidertest.Book"; case CATEGORY_DIR : return "vnd.android.cursor.dir/vnd.com.mrdouya.mycontenprovidertest.Category"; case CATEGORY_ITEM : return "vnd.android.cursor.item/vnd.com.mrdouya.mycontenprovidertest.Category"; } return null; } }
3)访问验证
其实在本节第一小点访问的就是自定义的contentProvider,我将测试类运行在单独的线程中,去访问自定义的contentProvider这与两个应用资源共享效果是一致的,声明如下:
<activity android:name=".MyProviderTest"
android:process=":providertest">
</activity>
注意:四大组件均支持process属性,但不能轻易使用的时候,主要有一下几点权衡
优点:
缺点:
应用私有进程:
<activity android:name=".MyProviderTest"
android:process=":providertest">
</activity>
应用公共进程:
<!--process的属性字段必须包含符号“.”-->
<activity android:name=".MyProviderTest"
android:process="com.providertest">
</activity>
参考:
书籍:《第一行代码》、《Android开发与艺术探索》
Process属性:https://blog.csdn.net/lixpjita39/article/details/77435156
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。