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,项目结构如下图所示。
MyContentProvider目录结构
在MyUser.java中,MyUser这个类主要用来存放一些常量,比如这个ContentProvider的Uri以及使用的数据库表的列,如下所示:
public class MyUser {public static final String AUTHORITY = "com.veione.MyContentProvider";public static final class User implements BaseColumns {// 定义Uripublic 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);}//当第一次创建数据库的时候调用该方法,可以为数据库增加一些表和初始化一些数据@Overridepublic void onCreate(SQLiteDatabase db) {//在数据库里生成一张表db.execSQL("CREATE TABLE "+TABLE_NAME+" (_id INTEGER PRIMARY KEY AUTOINCREMENT,USER_NAME TEXT);");}//当更新数据库版本的时候,调用该方法。可以删除、修改表的一些信息@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS "+TABLE_NAME);onCreate(db);}}//这是一个回调函数,当生成所在类的对象时,这个方法被调用,创建一个数据库@Overridepublic boolean onCreate() {dbHelper=new DatabaseHelper(getContext());return (dbHelper==null)?false:true;}//查询@Overridepublic 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;}//取得类型@Overridepublic String getType(Uri uri) {return null;}//插入数据@Overridepublic 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);}//删除数据@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {return 0;}//更新数据@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {return 0;}}

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


 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的UriUri CONTENT_URI=Uri.parse("content://"+AUTHORITY);@Overrideprotected 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() {@Overridepublic 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布局文件如下:
<?xml version="1.0" encoding="utf-8"?>            

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

更多相关文章

  1. [转]Android的Handler总结
  2. Android提高篇之SQLite分页读取
  3. Android中Activity之间的数据传递和Intent使用
  4. Android(安卓)cursor用法
  5. Android——SQLite实现面向对象CRUD
  6. Android(安卓)JSON原生解析(JSONObject)解析教程
  7. android activity之间传值
  8. Android(安卓)数据Parcel序列化过程源码分析
  9. mybatisplus的坑 insert标签insert into select无参数问题的解决

随机推荐

  1. 开源阅读器FBReader Android版本的编译
  2. android瀑布流
  3. android bionic缺失pthread_cancel的解决
  4. Android四大组件之Activity---生命周期那
  5. Android(安卓)3D旋转动画之Camera 和 Mat
  6. Android中的动画有哪几类?各自的特点和区
  7. 终于松了口气,说说这周我调试Sate210 andr
  8. Android(安卓)Material Design 详解(使用s
  9. 移动端开发新趋势Flutter
  10. Android(安卓)Layout 之TableLayout