使用ContentProvider 实现数据共享

使用ContentProvider 实现数据共享


一 ContentProvider简介

        ContentProvider(数据提供者)是在应用程序间共享数据的一种接口机制。 它提供了更为高级的数据共享方法,应用程序可以指定需要共享的数据,而其他应用程序则可以在不知数据来源、路径的情况下,对共享数据进行查询、添加、删除和更新等操作。许多Android系统的内置数据也通过ContentProvider提供给用户使用,例如通讯录、音视频文件和图像文件等。

        在创建ContentProvider时,需要首先使用数据库、文件系统或网络实现底层存储功能,然后在继承ContentProvider的类中实现基本数据操作的接口函数,包括添加、删除、查找和更新等功能。调用者不能够直接调用ContentProvider的接口函数,而是通过使用ContentResolver对象,使用URI间接调用ContentProvider。

       使用ContentProvider可以在不同的应用程序之间共享数据。 它为存储和获取数据提供了统一的接口;ContentProvide对数据进行封装,不用关心数据存储的细节。ContentProvider不管底层数据的实际存储方式,对外统一使用表的形式来组织数据 。


二 Uri简介

由于ContentProvider的使用,离不开URI,因此单独找一个小节,介绍一下URI。

Android平台,URI主要分三个部分:scheme, authority and path。其中authority又分为host和port。

格式如下:

scheme://host:port/path
举个实际的例子:
content://com.example.project:200/folder/subfolder/etc
\---------/  \---------------------------/ \---/ \--------------------------/
scheme                 host               port        path
                \--------------------------------/
                          authority   

其中,content:// 这个部分是android的contentprovider 规定的,就像上网的协议默认是http:// 一样。

com.example.project:200 这个部分就是contentProvider的authority。系统就是由这个部分摘到要操作哪一个contentProvider。

folder/subfolder/etc  资源部分,当需要访问不同的资源时,这个部分是动态改变的。


        我们在程序中一般是不直接用URI来标识contentProvider的;我们通常见到的用定义的常量来标识。例如standard contentProvider中的Contacts,我们就用Contacts.People.CONTENT_URI来标识Contacts contentProvider中People这个表。那么要标识某个具体的人怎么办呢? 这就用到了ContentUris.withAppendedId() 和 Uri.withAppendedPath()。例如我们要表示content://contacts/people/20,那么我们就可以用如下语句:

Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, 20); 或者

Uri uri = Uri.withAppendedPath(People.CONTENT_URI, "20");


三  开发ContentProvider

1. ContentProvider与 ContentResolver的关系

    从ContentProvider与 ContentResolver, url 三者的关系看;uri 是 ContentProvider与 ContentResolver 进行数据交换的标示。ContentResolver 通过uri 操作数据会委托给相应的ContentProvider 来实现。

2. 开发ContentProvider

开发ContentProider 只需要两步:

(1)开发ContentProider的子类,该子类需要实现增删改查的方法。

(2)在AndroidManifest.java 文件中注册该 ContentProider,指定 android:authorities 属性。

注意:上面实现的ContentProider 的 query() , insert(), update(), delete() 方法,不是给应用本身使用的,而是给其他应用使用的。至于这四个方法如何实现,完全由开发者本身决定。一般是数据库。

3.配置 ContentProider

android 要求必须为 四大组件显示配置(activity,service ,ContentProider,BroadcastReceiver)。

ContentProider 的配置一般如下:


        <!-- 注册contentProvider  -->
        <provider 
            android:name=".InfoProvider"
            android:authorities="com.example.infos.infoprovider"
            android:exported="true" />

通常的属性如下:

name: 指定该 ContentProider 的实现类的类名;

authorities: 指定该ContentProider 对应的uri,

exported:指定该 ContentProider 是否允许其他应用调用。一般设置为true。


4. ContentResolver 的使用介绍

Context 提供了 getContentResolver() 方法,这表示 activity 以及 service 等组件都可以通过getContentResolver() 方法获取ContentResolver。


获取了ContentResolver 之后,就可以调用ContentResolver 的 query() , insert(), update(), delete() 方法了----实际上是调用的 对应的ContentProider 的query() , insert(), update(), delete() 方法。


5. 创建 ContentProider 的注意事项

因为经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris。

UriMatcher:用于匹配Uri,它的用法如下:
1.首先把你需要匹配Uri路径全部给注册上,如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
UriMatcher  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

//如果match()方法匹配content://com.ex.sqlite.provider.contactprovider/contact,返回匹配码为1
uriMatcher.addURI(“com.ex.sqlite.provider.contactprovider”, “contact”, 1);

//如果match()方法匹配 content://com.ex.sqlite.provider.contactprovider/contact/12,返回匹配码为2
uriMatcher.addURI(“com.ex.sqlite.provider.contactprovider”, “contact/#”, 2);  //其中#号为通配符

2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,
如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,

ContentUris:用于获取Uri路径后面的ID部分,它有两个比较实用的方法:
 (1)withAppendedId(uri, id)用于为路径加上ID部分
 (2)parseId(uri)方法用于从路径中获取ID部分


四 示例ContentProider 的使用

我们这里设计了一个记录个人信息的ContentProider,可以通过其他的进程来添加个人信息,包括姓名,年龄。

也可以通过名字来查询当前姓名的年龄。

1. 设计了一个工具类,用来把 ContentProider 的 Uri,以及数据列等信息以常量的形式公布出来,便于访问。其实它的作用就是告诉其他的应用程序怎么访问该ContentProider。


public class Infos {
	//define the authority of the contentProvider
	public static final String AUTHORITY= "com.example.infos.infoprovider";
	
	
	//define the inner class ,which contents the data and name of columns
	public static final class Info implements BaseColumns{
		
		//define the name of columns that will be operate
		public static final String _ID = "id";
		public static final String NAME = "name";
		public static final String AGE = "age";
		
		public static final Uri INFOS_URI = Uri.parse(
				"content://" + AUTHORITY + "/infos"); 
		
		public static final Uri INFO_URI = Uri.parse(
				"content://" + AUTHORITY + "/info");
	}
	
	
}

2.接下来,设计了一个 ContentProider 的子类,重写它的增删改查方法,代码如下:

public class InfoProvider extends ContentProvider {

	private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
	private InfoDatabaseHelper mInfoDatabaseHelper = null;
	
	private static final int NAMES = 1;
	private static final int NAME = 2;
	
	static{
		//为uriMatcher 注册两个uri
		mUriMatcher.addURI(Infos.AUTHORITY, "/infos", 1);
		mUriMatcher.addURI(Infos.AUTHORITY, "/info/#", 2);
		
	}
	
	
	@Override
	public int delete(Uri uri, String where, String[] whereArgs) {
		SQLiteDatabase db = mInfoDatabaseHelper.getWritableDatabase();
		//记录删除的数目
		int num = 0;
		switch (mUriMatcher.match(uri)) {
		case NAMES:
			num = db.delete("info", where, whereArgs);
			break;
		case NAME:
			long id = ContentUris.parseId(uri);
			String wheretemp = Infos.Info._ID + id;
			if (where != null && "".equals(where)) {
				where = wheretemp + " and " + where;
			}
			num = db.delete("info", where, whereArgs);
		default:
			break;
		}
		getContext().getContentResolver().notifyChange(uri, null);
		return num;
	}

	@Override
	public String getType(Uri arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Uri insert(Uri uri, ContentValues contentValues) {
		SQLiteDatabase db = mInfoDatabaseHelper.getReadableDatabase();

		switch (mUriMatcher.match(uri)) {
		case NAMES:
			long id = db.insert("info", Info._ID, contentValues);
			//如果插入成功,返回uri
			if (id > 0) {
				//在已有的uri后面添加id
				Uri infoUri = ContentUris.withAppendedId(uri, id);
				getContext().getContentResolver().notifyChange(infoUri, null);
				return infoUri;
			}
			break;
			
		default:
			break;
		}
		return null;
	}

	@Override
	public boolean onCreate() {
		
		mInfoDatabaseHelper = new InfoDatabaseHelper(this.getContext(), "peopleinfo.db", null, 1);
		if (mInfoDatabaseHelper != null) {
			return true;
		}
		return false;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String where, String[] whereArgs,
			String sortorder) {
			
		SQLiteDatabase db = mInfoDatabaseHelper.getReadableDatabase();
		switch (mUriMatcher.match(uri)) {
		case NAMES:
			return db.query("info", projection, where, whereArgs, null, null, sortorder);

			
		case NAME:
			//解析出想要查询的ID
			long id = ContentUris.parseId(uri);
			String wheretemp = Infos.Info._ID + id;
			if (where != null && "".equals(where)) {
				where = wheretemp + " and " + where;
			}
			return db.query("info", projection, where, whereArgs, null, null, sortorder);

		default:
			break;
		}
		return null;
	}

	@Override
	public int update(Uri uri, ContentValues contentValues, String where, String[] whereArgs) {
		
		SQLiteDatabase db = mInfoDatabaseHelper.getReadableDatabase();
		
		//记录修改的数目
		int num =0;
		switch (mUriMatcher.match(uri)) {
		case NAMES:
			num = db.update("info", contentValues, where, whereArgs);
		case NAME:
			//解析出想要查询的ID
			long id = ContentUris.parseId(uri);
			String wheretemp = Infos.Info._ID + id;
			if (where != null && "".equals(where)) {
				where = wheretemp + " and " + where;
			}
			num = db.update("info", contentValues, where, whereArgs);
			break;

		default:
			break;
		}
		// notify the data has been changed
		getContext().getContentResolver().notifyChange(uri, null);
		return num;
	}

}

它里面用到的 InfoDatabaseHelper 类如下:

public class InfoDatabaseHelper extends SQLiteOpenHelper {

	public InfoDatabaseHelper(Context context, String name,
			CursorFactory factory, int version) {
		super(context, name, factory, version);
	}

	final String CREATE_TABLE_SQL =
			"create table info(_id integer primary key autoincrement ,name , age)";
	
	
	@Override
	public void onCreate(SQLiteDatabase db) {
		// 创建数据库
		db.execSQL(CREATE_TABLE_SQL);
	}

	@Override
	public void onUpgrade(SQLiteDatabase arg0, int oldVersion, int newVersion) {

		System.out.println("--------onUpdate Called--------"
				+ oldVersion + "--->" + newVersion);
	}

}

上面的InfoProvider 类很简单,它除了继承 ContentProvider 之外,还实现了增删改查方法。当其他的应用通过 ContentResolver 来调用InfoProvider 的这四个方法的时候,就可以真正的访问这里面创建的数据库了,从而达到对个人信息的处理。


3.

接下来,注册 InfoProvider,片段如下:

        <provider 
            android:name=".InfoProvider"
            android:authorities="com.example.infos.infoprovider"
            android:exported="true" />


4. 为了测试这个 InfoProvider 可以使用,我们编写了一个简单的测试demo:

这个测试demo 可以添加个人信息,也可以查询个人信息,而且都是通过前面我们创建的 InfoProvider 来完成的。

 代码如下:

public class MainActivity extends Activity {
	
	Button insert = null;
	Button search = null;
	
	ContentResolver mContentResolver = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		insert = (Button)findViewById(R.id.insert);
		search = (Button)findViewById(R.id.search);
		mContentResolver = getContentResolver();
		
		
		insert.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {

				String name = ((EditText)findViewById(R.id.name)).getText().toString();
				
				String age = ((EditText)findViewById(R.id.age)).getText().toString();
				
				ContentValues contentValues = new ContentValues();
				contentValues.put(Info.NAME, name);
				contentValues.put(Info.AGE, age);
				Uri uri = mContentResolver.insert(Infos.Info.INFOS_URI, contentValues);
				
				if (uri != null) {
					Toast.makeText(getApplicationContext(), uri.toString(), Toast.LENGTH_LONG).show();	
				}
			}
		});
		
		search.setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View arg0) {
				String searchString = ((EditText)findViewById(R.id.key)).getText().toString();
				
				
				String selection = "name = ?";
				String[] selectionArgs = {searchString};
				Cursor cursor = mContentResolver.query(Infos.Info.INFOS_URI, null, selection, selectionArgs, null);
				ArrayList<Map<String, String>>  infoList = convertCursorToList(cursor);
				//创建一个Bundle
				Bundle bundle = new Bundle();
				//bundle.putStringArrayList("data", infoList);
				bundle.putSerializable("data", infoList);
				
				//启动显示的activity
				Intent intent = new Intent(getApplicationContext(), ResultActivity.class);
				intent.putExtras(bundle);
				startActivity(intent);
			}
		});
		
		
		
		
	}
	
	private ArrayList<Map<String, String>> convertCursorToList(Cursor cursor) {
		ArrayList<Map<String, String>> result = new ArrayList<Map<String,String>>();
		
		if (cursor == null) {
			return result;
		}
		while (cursor.moveToNext()) {
			//set the info to the list
			Map<String, String> maptmp = new HashMap<String, String>();
			maptmp.put(Info.NAME, cursor.getString(1));
			maptmp.put(Info.AGE, cursor.getString(2));
						
			result.add(maptmp);
		}
		return result;
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}


如果是查询的话,我们会新创建一个actiivty,用来显示查询到的数据,activity实现如下:

public class ResultActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.popup);
		ListView listView = (ListView) findViewById(R.id.show);
		Intent intent = getIntent();

		Bundle data = intent.getExtras();
		
		
		@SuppressWarnings("unchecked")
		List<Map<String, String>> list = (List<Map<String, String>>)
			data.getSerializable("data");

		SimpleAdapter adapter = new SimpleAdapter(ResultActivity.this,
			list, R.layout.line
			, new String[] { Infos.Info.NAME, Infos.Info.AGE }
			, new int[] { R.id.name, R.id.age });

		listView.setAdapter(adapter);
	}

	
}


这个简单的demo能够实现 个人信息(姓名,年龄的添加和查询),说明了ContenterProvider 的一般使用方法和注意事项。


五总结


简单的理解, ContenterProvider 就是android里面实现不同进程间数据共享的一种方法,为app开发者提供了一种开发的行为规范和标准。

严格按照这个规则开发,就能够设计出简单明了的应用程序。






  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值