欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Android面试之ContentProvider

程序员文章站 2022-06-21 20:51:02
1. 内容提供者是什么?内容提供者(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供者是Android实现跨程序共享数据的标准方式。不同于文件存储和 SharedPreferences 存储中的两种全局可读可写操作模式,内容提供者可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会泄露的风险。2. 内容提供者的使用我们一般用内容提供...

1. 内容提供者是什么?

内容提供者(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。

目前,使用内容提供者是Android实现跨程序共享数据的标准方式。

不同于文件存储和 SharedPreferences 存储中的两种全局可读可写操作模式,内容提供者可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会泄露的风险。


2. 内容提供者的使用

我们一般用内容提供者都是用来查询数据的:

Cursor cursor = getContentResolver().query(final Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)

uri,指定查询某一个程序下的某一张表
projection,指定查询的列名
selection,指定查询条件,相当于sql语句中where后面的条件
selectionArgs,给selection中的占位符提供具体的值
orderBy,指定查询结果排序方式
cancellationSignal,取消正在进行操作的信号量

想要访问内容提供者*享的数据,就一定要借助 CotentResolver 类,可以通过 Context 中的 getContentResolver() 方法获取该类的实例。ContentResolver 中提供了一系列的方法用于对数据进行CRUD(增删改查)操作,其中 insert() 方法用于添加数据,update()方法用于数据的更新,delete()方法用于数据的删除,query()方法用于数据的查询。

不同于 SQLiteDatabase , ContentResolver 中的增删改查方法都是 不接收表名参数的,而是使用一个Uri的参数代替,这个参数被称作内容URI。内容URI给内容提供者中的数据建立了唯一的标识符,它主要由两部分组成:authority和path。

authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名为com.example.app,那么该程序对应的authority就可以命名为 com.example.app.provider。

path 则是用于对同一应用程序中不同的表做区分的,通常都会添加到 authority 的后面。比如某个程序的数据库里存在两张表:table1 和 table2 , 这时就可以将path分别命名为 /table1/table2,然后把 authority 和 path 进行组合,内容的URI就变成了 com.example.app.provider/table1com.example.app.provider/table2

不过目前还是很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此,内容URI最标准的格式写法如下:

content://com.example.app.provider/table1
content://com.example.app.provider/table2

在得到内容URI字符串之后,我们还需要将它解析成 Uri 对象才可以作为参数传入。只需要调用 Uri 的静态方法 parse() 就可以把内容 URI 字符串解析成 URI 对象。代码如下所示:

Uri uri = new Uri.parse("content://com.example.app.provider/table1");

现在,我们可以通过这个Uri对象来查询table1表中的数据了。代码如下所示:

Cursor cursor = getContentResolver()
				.query(
					uri,projection,selection,selectionArgs,sortOrder
				);

query() 方法接收的参数跟 SQLiteDatabase 中的 query() 方法接收的参数很像,对内容提供者中的query的接收的参数:

查询完成仍然会返回一个 Cursor 对象,这时我们就可以将数据从 Cursor 对象中逐个读取出来了。读取的思路仍然是对这个Cursor对象进行遍历,然后一条一条的取出数据即可,代码如下:

if(cursor != null){//注意这里一定要进行一次判空,因为有可能你要查询的表根本不存在
	while(cursor.moveToNext()){
		String column1 = cursor.getString(cursor.getColumnIndex("column1"));
		int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
	}
}
//增加数据
ContentValues values = new ContentValues();
values.put("Column1","text");
values.put("Column2","1");
getContextResolver.insert(uri,values);

//删除数据
getContextResolver.delete(uri,"column2 = ?",new String[]{ "1" });

//更新数据
ContentValues values = new ContentValues();
values.put("Column1","改数据");
getContextResolver.update(uri,values,"column1 = ? and column2 = ?",new String[]{"text","1"});

Android面试之ContentProvider

3. 如何创建属于自己应用的内容提供者?

如果要想实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,
新建一个类继承 ContentProvider 类的方式来创建一个自己的内容提供器。ContentProvider 类有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写。代码如下所示:

public class MyProvider extends ContentProvider {

	@Override
	public boolean onCreate() {
		return false;
	}
	//查询
	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
	String[] selectionArgs, String sortOrder) {
		return null;
	}
	//添加
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		return null;
	}
	//更新
	@Override
	public int update(Uri uri, ContentValues values, String selection,
		String[] selectionArgs) {
		return 0;
	}
	//删除
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		return 0;
	}
	
	@Override
	public String getType(Uri uri) {
		return null;
	}
}
注释:
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.provider/table1

这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中的数据。除此之外,我们还可以在这个内容URI的后面加上一个 id, 如下所示:

content://com.example.app.provider/table1/1

这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id为 1 的数据。
内容URI的格式主要有以上两种,以路径结尾就表示期望访问该表中所有的数据,以id结果就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两中格式的内容URI,规则如下:
* :表示匹配任意长度的任意字符。
# :表示匹配任意长度的任意数字。

//一个能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
//一个能够匹配 table 表中任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/table1/#

接着,我们再借助 UriMatcher 这个类就可以轻松地实现匹配内容 URI 的功能。UriMatcher 中提供了一个 addURI() 方法,这个方法接收三个参数,可以分别把 authority , path 和一个自定义代码传进去,这个自定义代码其实就是一个final的int类型的具值。

这样,当调用 UriMatcher 的 match() 方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。修改上述的MyProvider代码如下所示:

public class MyProvider extends ContentProvider {

	public static fianl int TABLE1_DIR = 0;
	
	public static fianl int TABLE1_ITEM = 1;
	
	public static fianl int TABLE2_DIR = 2;
	
	public static fianl int TABLE2_ITEM = 3;
	
	private static UriMatcher uriMatcher;
	
	static{
	
		uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
		uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
		uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
		uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
	}
	
	...
	
	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
	String[] selectionArgs, String sortOrder) {
		switch(uriMatcher.match(uri)){
			case TABLE1_DIR:
				//查询table1中的数据
			break;
			
			case TABLE1_ITEM:
				//查询table1中的单条数据
			break;
			
			case TABLE2_DIR:
				//查询table2中的数据
			break;
			
			case TABLE2_ITEM:
				//查询table2中的单条数据
			break;
		}		
		return null;
	}//查询
	...
}

上述代码以 query() 方法为例做了个示范,其实 insert() 方法,update() , delete() 方法的实现也是跟 query() 方法是差不多的,它们都会携带Uri这个参数,然后同样利用 UriMatcher 的 match() 方法判断出调用方期望访问的是哪张表,再对该表中的数据进行相应的操作就可以了。

有个方法比较陌生,就是 getType() 方法。它是所有的内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定:

  1. 必须以vnd开头
  2. 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接 android.cursor.item/
  3. 最后接 vnd.< authority >.< path >

对于content://com.example.app.provider/table1这个内容URI,它所对应的MIME类型就可以写成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

对于content://com.example.app.provider/table1/1这个内容URI,它所对应的MIME类型就可以写成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

继续完善MyProvider类中的内容,这次实现getType()方法的逻辑,代码如下:

public class MyProvider extends ContentProvider{
	...
	@Override
	public String getType(Uri uri){
		switch(uriMatcher.match(uri)){
			case TABLE1_DIR:
				return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
			break;
			
			case TABLE1_ITEM:
				return "vnd.android.cursor.item/vnd.com.example.app.provider.table1 "
			break;
			
			case TABLE2_DIR:
				return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
			break;
			
			case TABLE2_ITEM:
				return "vnd.android.cursor.item/vnd.com.example.app.provider.table2 "
			break;
		}
	}
	...
}

到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用 ContentResolver 来访问我们程序中的数据。

如何才能保证隐私数据不会泄漏出去呢?

内容提供器的良好机制,这个问题在不知不觉中已经被解决了。因为所有的 CRUD 操作都一定要匹配到相应的内容 URI 格式才能进行的,而我们当然不可能向 UriMatcher 中添加隐私数据的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;//这里一定返回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);
				uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" +
				newBookId);
			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 大组件都需要在 AndroidManifest.xml 文件中进行注册,既然完成的内容提供器写好了,那么下一步就是去AndroidManifest.xml文件中进行注册,然后这个内容提供器就可以使用了。

AndroidManifest.xml 文件中对内容提供器进行注册,标签为 < provider >…< /provider >

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.example.databasetest"
	android:versionCode="1"
	android:versionName="1.0" >
	……
	<application
		android:allowBackup="true"
		android:icon="@drawable/ic_launcher"
		android:label="@string/app_name"
		android:theme="@style/AppTheme" >
		……
		<provider
			android:name="com.example.databasetest.DatabaseProvider"
			android:authorities="com.example.databasetest.provider" >
		</provider>
	</application>
</manifest>

4. ContentPrivoder的原理机制

其实内容提供者可跨程序访问,这可以认为是一种进程间通信的方式,其实它原理核心就是 Binder


声明:本文整理自网络资料,如有侵权请及时联系。

本文地址:https://blog.csdn.net/liusaisaiV1/article/details/107236623

相关标签: 面试准备 java