Android四大组件之Content Provider

Android四大组件之Content Provider


Content Provider是Android提供的第三方应用数据的访问方案。在Android中,对数据的保护是很严密的,除了放在SD卡中的数据,一个应用所持有的数据库、文件等内容,都是不允许其它程序直接访问的。Android当然不会真的把每个应用都做成一座孤岛,它为所有应用都准备了一扇窗,这就是Content Provider。应用向对外提供的数据,可以通过派生Content Provider类,封装成一枚Content Provider,每个Content Provider都用一个uri作为独立的标识,形如:content://com.xxxx。
所有东西看着像REST的样子,但实际上,它比REST更为灵活。和REST类似,uri也可以有两种类型,一种是带id的,另一种是列表的,但实现者不需要按照这个模式来做,给你id的uri你也可以返回列表类型的数据,只要调用者明白就无妨,不用苛求所谓的REST。
另外,Content Provider不和REST一样只有uri可用,它还可以接受Projection、Selection、OrderBy等参数,这样,就可以像数据库那样进行投影、选择和排序。查询到的结果,以Cursor的形式进行返回,调用者可以移动Cursor来访问各列的数据。
Content Provider屏蔽了内部数据的存储细节,向外提供了上述的统一接口模型,这样的抽象层次,大大简化了上层应用的编写,也对数据的整合提供了更方便的途径。Content Provider内部常用数据库来实现,Android提供了强大的Sqlite支持,但很多时候你也可以封装文件或其它混合的数据。

在Android中,ContentResolver是用来发起Content Provider的定位和访问的。但通常,Content Provider需要访问的可能是数据库等大数据源,效率上不足够快,会导致调用线程的拥塞。因此Android提供了一个AsyncQueryHandler,帮助进行异步访问Content Provider。
在各大组件中,Service和Content Provider都是需要持续访问的。Service如果是一个耗时的场景,往往会提供异步访问的接口,而Content Provider不论效率如何,都提供的是约定的同步访问接口。
下面提供两个实例------一个是ContentProvider所在的应用,一个是使用ContentProvider的应用,来说明如何使用ContentProvider。
我们先新建一个工程MyContentProvider,在这个工程中我们要实现ContentProvider,项目结构如下图所示。
Android四大组件之Content Provider_第1张图片
MyContentProvider目录结构

在MyUser.java中,MyUser这个类主要用来存放一些常量,比如这个ContentProvider的Uri以及使用的数据库表的列,如下所示:

public class MyUser {
	public static final String AUTHORITY = "com.veione.MyContentProvider";

	public static final class User implements BaseColumns {
		// 定义Uri
		public static final Uri CONTENT_URI = Uri.parse("content://"+ AUTHORITY);
		// 定义数据表列
		public static final String USER_NAME = "USER_NAME";
	}
}	
接下来我们在MyContentProvider.java中实现这个Content Provider,在MyContentProvider中我们主要构造了一个继承于SQLiteOpenHelper的类DatabaseHelper,然后在这个SQLiteOpenHelper类中实现数据的增、删、改、查,如下所示:
/**
 * MyContentProvider继承ContentProvider类,实现其insert,update,delete,
 * getType,onCreate等方法
 * 
 * @author veione
 * 
 */
public class MyContentProvider extends ContentProvider {
	//定义一个SQLiteDatabase变量
	private SQLiteDatabase sqlDB;
	//定义一个DatabaseHelper变量
	private DatabaseHelper dbHelper;
	//数据库名
	private static final String DATABASE_NAME="Users.db";
	//数据库版本
	private static final int DATABASE_VERSION=1;
	//表名
	private static final String TABLE_NAME="User";
	/**
	 * 定义一个内部类
	 * 这个内部类继承了SQLiteOpenHelper类,重写其方法
	 */
	public static class DatabaseHelper extends SQLiteOpenHelper{
		//构造方法
		public DatabaseHelper(Context context) {
			//父类构造方法
			super(context,DATABASE_NAME,null,DATABASE_VERSION);
		}

		//当第一次创建数据库的时候调用该方法,可以为数据库增加一些表和初始化一些数据
		@Override
		public void onCreate(SQLiteDatabase db) {
			//在数据库里生成一张表
			db.execSQL("CREATE TABLE "+TABLE_NAME
					+" (_id INTEGER PRIMARY KEY AUTOINCREMENT,USER_NAME TEXT);");
		}

		//当更新数据库版本的时候,调用该方法。可以删除、修改表的一些信息
		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL("DROP TABLE IF EXISTS "+TABLE_NAME);
			onCreate(db);
		}
		
	}
	
	//这是一个回调函数,当生成所在类的对象时,这个方法被调用,创建一个数据库
	@Override
	public boolean onCreate() {
		dbHelper=new DatabaseHelper(getContext());
		return (dbHelper==null)?false:true;
	}

	//查询
	@Override
	public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {
		//新建数据库查询类
		SQLiteQueryBuilder qb=new SQLiteQueryBuilder();
		SQLiteDatabase db=dbHelper.getReadableDatabase();
		qb.setTables(TABLE_NAME);
		//取得查询结果的游标
		Cursor c=qb.query(db, projection,selection, null, null, null,sortOrder);
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}
	
	//取得类型
	@Override
	public String getType(Uri uri) {
		return null;
	}

	//插入数据
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		sqlDB=dbHelper.getWritableDatabase();
		long rowId=sqlDB.insert(TABLE_NAME, "", values);
		if(rowId>0){
			Uri rowUri=ContentUris.appendId(MyUser.User.CONTENT_URI.buildUpon(), rowId).build();
			//通知更改
			getContext().getContentResolver().notifyChange(rowUri, null);
			return rowUri;
		}
		throw new SQLException("Failed to insert row into "+uri);
	}
	
	//删除数据
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		return 0;
	}

	//更新数据
	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		return 0;
	}

}

另外我们新建一个MyContentProvider的界面,代码如下所示,在这个界面中简单地往数据库中插入两条记录Test和Guo作为实例数据,主要是验证客户端中是否能正确读取到这些数据。插入数据之后,我们还通过Toast的方式将数据库中的所有数据显示一遍,表明我们已经将数据正确存储到了数据库中。

  
public class MyContentActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// 插入两条记录
		insertRecord("Test");
		insertRecord("Guo");
		// 显示记录
		displayRecord();
	}

	private void insertRecord(String userName) {
		ContentValues values=new ContentValues();
		values.put(MyUser.User.USER_NAME, userName);
		getContentResolver().insert(MyUser.User.CONTENT_URI, values);
	}

	private void displayRecord() {
		//构建一个字符串数组用于存放用户的记录
		String[] columns=new String[]{MyUser.User._ID,MyUser.User.USER_NAME};
		//设定ContentProvider的Uri
		Uri myUri=MyUser.User.CONTENT_URI;
		Cursor cur=getContentResolver().query(myUri, columns, null, null, null);
		if(cur.moveToFirst()){
			String id=null;
			String userName=null;
			do{
				id=cur.getString(cur.getColumnIndex(MyUser.User._ID));
				userName=cur.getString(cur.getColumnIndex(MyUser.User.USER_NAME));
				//显示数据表中的数据
				Toast.makeText(this, id+" "+userName, Toast.LENGTH_SHORT).show();
			}while(cur.moveToNext());
		}
	}
}
这里我们没有用到任何的布局文件,因此我们直接来看看AndroidManifest.xml,如下所示。与其它AndroidManifest.xml不同点主要在于加了声明provider的部分,如代码所示。核心的两个属性分别是android:name和android:authorities。


    

    

        
        

        
            
                

                
            
        
    

运行之后效果如图所示,显示了添加的用户Test和Guo。

Android四大组件之Content Provider_第2张图片

 MyContentProvider运行效果图


  接下来我们新建另外一个程序MyContentClient,在这个程序中使用我们刚才建立的ContentProvider,对该数据表进行插入和查询操作。
如下所示,是MyContentClientActivity的源代码,代码中关键的地方在于要正确设置访问的Uri地址,如图所示。这个Uri是由"content://"加上我们在ContentProvider定义的authority组成。然后就是使用getContentResolver()函数得到数据表的所有数据,并保存到一个游标变量中。接着,通过移动这个游标我们可以得到数据表中的所有数据,并将它们保存到一个StringBuffer中,最后将它们输出到一个TextView控件中。
同时为了试验插入数据的功能,我们创建了一个EditText的文本输入框,在文本框中输入信息,并单击Button,可以将数据写入到数据库中。数据写入过程也要采用特有的格式:新建一个ContentResolver对象和一个ContentValue的对象,再往这个ContentValue对象中输入数据,最后通过这个ContentResolver对象将数据插入到指定的ContentProvider中。当然,这里没有做一个可以实时更新的界面,不过没关系,重新打开程序,就可以在TextView控件里看到刚才输入的数据。

package com.veione.mycontentprovider;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MyContentClientActivity extends Activity{
	public static final String AUTHORITY="com.veione.MyContentProvider";
	private Button insertBtn=null;
	//访问ContentProvider的Uri
	Uri CONTENT_URI=Uri.parse("content://"+AUTHORITY);
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		TextView show=(TextView)findViewById(R.id.show);
		StringBuffer sb=new StringBuffer("");
		//得到ContentProvider对应表的所有数据,以游标格式保存
		Cursor c=getContentResolver().query(CONTENT_URI, new String[]{"_id","USER_NAME"}, null, null, null);
		//循环输出ContentProvider的数据
		if(c.moveToFirst()){
			String _id=null;
			String userName=null;
			do{
				_id=c.getString(c.getColumnIndex("USER_NAME"));
				userName=c.getString(c.getColumnIndex("USER_NAME"));
				sb.append("_id="+_id+",userName="+userName+"\n");
			}while(c.moveToNext());
		}
		show.setText(sb);
		//根据Id得到控件对象
		insertBtn=(Button)findViewById(R.id.insertBtn);
		//给按钮添加监听器
		insertBtn.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				//得到EditText输入的数据
				String username=((EditText)findViewById(R.id.sername)).getText().toString();
				//生成一个ContentResolver对象
				ContentResolver cr=getContentResolver();
				//生成一个ContentValues对象
				ContentValues values=new ContentValues();
				values.put("USER_NAME", username);
				//插入数据
				cr.insert(CONTENT_URI, values);
			}
		});
	}
}

activity_main.xml布局文件如下:




    

    

    

最后运行就OK了,OMG,快1点了,睡觉!!!

你可能感兴趣的:(android)