ContentProvider原理及实例

1、  ContentProvider是个啥?

    ContentProvider——内容提供者。它是一个类,这个类主要是对Android系统中进行共享的数据进行包装,并提供了一组统一的访问接口供其他程序调用。这些被共享的数据,可以使系统自己的也可以使我们个人应用程序中的数据。


2、  为什么要有ContentProvider这个类?

    在Android中,数据的存储有很多种方式,最常用的就是SQLite和XML文件方式。在不同的应用程序间,其实数据是不能直接被相互访问和操作的,在这种情况下,ContentProvider很好的被用来解决了不同应用程序间数据共享的问题。

    其实在Android系统中,已经为我们提供了许多ContentProvider,如:Contacts、Browser、CallLog、Settings等等。那么,Android系统中提供了这么多的ContentProvider,另外还有我们自己公开的共享数据,我们在写程序的时候,怎么才能让我们的应用程序知道去哪儿取、如何取这些数据呢?我们自然的会想到URI。


3、  URI是个啥?在ContentProvider中有什么用处?URI中的几个方法。

    URI(Uniform Resource Identifier)——统一资源定位符,URI在ContentProvider中代表了要操做的数据。

    在Android系统中通常的URI格式为:content://LiB.cprovider.myprovider.Users/User/21

    在万维网访问时通常用的URI格式为:http://www.XXXX.com/AAA/123

    • content://——schema,这个是Android中已经定义好的一个标准。我个人一直认为这和我们的http://有异曲同工之妙,都是代表的协议。ContentProvider(内容提供者)的scheme已经由Android所规定为:content://
    • LiB.cprovider.myprovider.Users——authority(主机名),用于唯一标识这个ContentProvider,外部调用者通过这个authority来找到它。相当于www.XXXX.com,代表的是我们ContentProvider所在的”域名”,这个”域名”在我们Android中一定要是唯一的,否则系统怎么能知道该找哪一个Provider呢?所以一般情况下,建议采用完整的包名加类名来标识这个ContentProvider的authority。
    • /User/21——路径,用来标识我们要操作的数据。/user/21表示的意思是——找到User中id为21的记录。其实这个相当于/AAA/123。

    综上所述,content://LiB.cprovider.myprovider.Users/User/21所代表的URI的意思为:标识LiB.cprovider.myprovider中Users表中_ID为21的User项。

4、 一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。
 也就是说,一个程序可以通过实现一个
Content Provider的抽象接口将自己的数据暴露出去。
 外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,
 重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据


5.我们来看看Android系统中为我们提供了些有关操作URI的方法:

UriMatcher:用于匹配Uri,它的用法如下:
       1.首先把你需要匹配Uri路径全部给注册上,如下:
       //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。
       UriMatcher  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
       //如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回匹配码为1
       uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1);//添加需要匹配uri,如果匹配就会返回匹配码
       //如果match()方法匹配   content://com.changcheng.sqlite.provider.contactprovider/contact/230路径,返回匹配码为2
       uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2);//#号为通配符
      
       2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回的匹配码为1。

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

       ContentResolver:当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。


6. ContentProvider中公开的几个方法

    • public boolean onCreate():该方法在ContentProvider创建后就会被调用,Android系统运行后,ContentProvider只有在被第一次使用它时才会被创建。
    • public Uri insert(Uri uri, ContentValues values):外部应用程序通过这个方法向 ContentProvider添加数据。
      • uri——标识操作数据的URI
      • values——需要添加数据的键值对
    • public int delete(Uri uri, String selection, String[] selectionArgs):外部应用程序通过这个方法从 ContentProvider中删除数据。
      • uri——标识操作数据的URI
      • selection——构成筛选添加的语句,如"id=1" 或者 "id=?"
      • selectionArgs——对应selection的两种情况可以传入null 或者 new String[]{"1"}
    • public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):外部应用程序通过这个方法对 ContentProvider中的数据进行更新。
      • values——对应需要更新的键值对,键为对应共享数据中的字段,值为对应的修改值
      • 其余参数同delete方法
    • public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):外部应用程序通过这个方法从ContentProvider中获取数据,并返回一个Cursor对象。
      • projection——需要从Contentprovider中选择的字段,如果为空,则返回的Cursor将包含所有的字段。
      • sortOrder——默认的排序规则
      • 其余参数同delete方法    


7.实现步骤:

想要自己的程序里面的数据能够被其他程序所访问到,有以下步骤:

第一:首先生成一个继承contentprovider的类.

第二:在androidMainfest.xml里面添加一个provider的标签就可以了.

    <provider  android:name="zuoyeSQLlite.hall.MyContentProvider"
                android:authorities="zuoyeSQLite.hall.MyContentProvider"/>

是不是很简单?其他程序访问的时候只要按以下步骤就可以访问到了:

Uri uri=Uri.Uri.parse("content://"+AUTHORY+"/userinfo1");

AUTHORY其实就是 android:authorities的值.,注意.这里必须一样..否则系统是找不到的.也是就是

String AUTHORY="zuoyeSQLite.hall.MyContentProvider"

然后获取一个

ContentResolver mContentResolver=getContentResolver();

这样就其他程序就可以反问我们的数据了..

ContentResolver对应的几个方法:

  • query(Uri, String[], String, String[], String) which returns data to the caller
  • insert(Uri, ContentValues) which inserts new data into the content provider
  • update(Uri, ContentValues, String, String[]) which updates existing data in the content provider
  • delete(Uri, String, String[]) which deletes data from the content provider

    其实和contentprovider里面的方法是一样的..他们所对应的数据,最终是会被传到我们在之前程序里面定义的那个contentprovider类的方法,至于你想要在这几个方法里面做什么事,那个就随你意了..


    8.实例

    Manifest.xml的<application>和</application>之间加入:

    <provider android:name="MyProvider" android:authorities="com.snowdream.provider" />

    MyProvider代表contentprovider的实现类,com.snowdream.provider代表uri的authority部分,访问MyProvider的uri的authority部分必须是com.snowdream.provider


    定义一个类存放各种常量

    package com.snowdream.contentprovider;
    
    import android.net.Uri;
    
    public class Profile {
    	
    	/**
    	 * 表格名称
    	 */
    	public static final String TABLE_NAME = "profile";
    	
    	/**
    	 * 列表一,_ID,自动增加
    	 */
    	public static final String COLUMN_ID = "_id";
    	
    	/**
    	 * 列表二,名称
    	 */
    	public static final String COLUMN_NAME = "name";
         
         
        public static final String AUTOHORITY = "com.snowdream.provider";
        public static final int ITEM = 1;
        public static final int ITEM_ID = 2;
         
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.snowdream.profile";
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.snowdream.profile";
         
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/profile");
    }
    

    定义一个数据库类,创建的数据表中必须含有"_id"这个字段,这个字段是自增长的,插入的时候不用管这个字段,数据库会自己递增地加上

    package com.snowdream.contentprovider;
    
    import android.content.Context;
    import android.database.SQLException;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    
    public class DBHelper extends SQLiteOpenHelper {
    
    	/**
    	 * 数据库名称
    	 */
    	private static final String DATABASE_NAME = "test.db";  
    	
    	/**
    	 * 数据库版本
    	 */
    	private static final int DATABASE_VERSION = 1;  
    
    	public DBHelper(Context context) {
    		super(context, DATABASE_NAME, null, DATABASE_VERSION);
    	}
    
    	@Override
    	public void onCreate(SQLiteDatabase db)  throws SQLException {
    		//创建表格
    		db.execSQL("CREATE TABLE IF NOT EXISTS "+ Profile.TABLE_NAME + "("+ Profile.COLUMN_ID +" INTEGER PRIMARY KEY AUTOINCREMENT," + Profile.COLUMN_NAME +" VARCHAR NOT NULL);");
    	}
    
    	@Override
    	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)  throws SQLException {
    		//删除并创建表格
    		db.execSQL("DROP TABLE IF EXISTS "+ Profile.TABLE_NAME+";");
    		onCreate(db);
    	}
    }
    

  • 定义ContentProvider的子类
  • 1contentprovider的调用者有可能是Activity,Service,Application这3种context,被谁调用,getContext就是谁

    2ContentResolver是属于context的,通过getContentResolver获取,ContentResolver可以通过registerContentObserver注册
    观察者(观察者是ContentObserver 的派生类),一旦ContentProvider操作的数据变化后,调用ContentResolver
    的notifyChange方法即可通知到观察者(回调观察者的onChange方法),注册观察者不是必须的,所有
    notifyChange不是必须调用的

    3ContentValues 一次只能放入一行数据(可以使多个字段,即多个名值对)

    4onCreate只在ContentProvider第一次被调用的时候调用,多次调用共享的是一个ContentProvider

    5Cursor即数据库查询结果的操作游标,用户随机访问查询结果,获取查询结果的数目等

    6在Content Resolver中有几个需要注意的接口:
    notifyChange (Uri uri, ContentObserver observer, boolean syncToNetwork);
    registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserver observer);
    unregisterContentObserver (ContentObserver observer);

    在Cursor中也有几个类似的接口:
    setNotificationUri (ContentResolver cr, Uri uri);
    registerContentObserver (ContentObserver observer);
    unregisterContentObserver (ContentObserver observer);

    7.要在query中调用
    setNotificationUri(ContentResolver cr, Uri notifyUri)
    从而在ContentService中注册contentservice的观察者,这个观察者是cursor的内部成员(cursor是一个接口,此处真正的cursor是sqlitecursor),这样每个查询返回的cursor都能在contentprovider对应数据改变时得到通知,因为这些cursor都有一个成员注册成了contentservice的观察者
    那么数据改变时,是怎么通知这些cursor的观察者成员呢?
    这就需要在delete,update,insert这些改变数据的方法中调用contentresolver的notifychange方法,这个notifychange实际调用的是contentservice的notifychange,在这个notifychange方法里,contentservice查找所有在其中注册的观察者,找出对这次更新数据感兴趣的观察者(通过uri),然后通知它们数据改变
    contentservice有很多观察者,它是系统服务,管理系统中所有contentprovider,通过uri匹配查找观察者,通知符合要求的观察者(实际就是通知对应的cursor)
    cursor得到通知以后做些什么呢?
    事实上,cursor也可以有很多观察者,因为一个查询出来的结果集可能会被多个地方使用(比如多个listview使用一个cursor),cursor对应的数据改变的时候,它也会通知到所有关注它的观察者(调用它们的onchange)
    那么,cursor的观察者是怎么注册进去的呢?是通过cursor的registerContentObserver这个方法注册进去的
    以下例作为解析,在simplecursoradapter(继承自cursoradapter)的构造函数中,调用父类cursoradapter的构造函数,在这个构造函数里,
    调用了cursor的registerContentObserver,把一个继承自contentobserver的成员对象作为观察者注册进了cursor的观察者里。这样cursor变化了,就会通知simplecursoradapter,simplecursoradapter里就可以重新查询结果并显示在listview中

    以上其实就是两个观察者模式,cursor观察contentservice,同时cursor又被cursoradapter观察(都是通过其成员变量观察,不是直接观察),我们也可以通过contentservice和cursoradapter提供的接口注册我们自己的观察者,也就是说contentservice的观察者可以不是cursor,cursor的观察者可以不是cursoradapter

  • package com.snowdream.contentprovider;  
      
    import android.content.ContentProvider;  
    import android.content.ContentUris;  
    import android.content.ContentValues;  
    import android.content.UriMatcher;  
    import android.database.Cursor;  
    import android.database.SQLException;  
    import android.database.sqlite.SQLiteDatabase;  
    import android.net.Uri;  
      
    public class MyProvider extends ContentProvider {  
      
        DBHelper mDbHelper = null;  
        SQLiteDatabase db = null;  
      
        private static final UriMatcher mMatcher;  
        static{  
            mMatcher = new UriMatcher(UriMatcher.NO_MATCH);  
            mMatcher.addURI(Profile.AUTOHORITY,Profile.TABLE_NAME, Profile.ITEM);  
            mMatcher.addURI(Profile.AUTOHORITY, Profile.TABLE_NAME+"/#", Profile.ITEM_ID);  
        }  
      
        @Override  
        public int delete(Uri uri, String selection, String[] selectionArgs) {  
            // TODO Auto-generated method stub  
            return 0;  
        }  
      
        @Override  
        public String getType(Uri uri) {  
            switch (mMatcher.match(uri)) {  
            case Profile.ITEM:  
                return Profile.CONTENT_TYPE;  
            case Profile.ITEM_ID:  
                return Profile.CONTENT_ITEM_TYPE;  
            default:  
                throw new IllegalArgumentException("Unknown URI"+uri);  
            }  
        }  
      
        @Override  
        public Uri insert(Uri uri, ContentValues values) {  
            // TODO Auto-generated method stub  
            long rowId;  
            if(mMatcher.match(uri)!=Profile.ITEM){  
                throw new IllegalArgumentException("Unknown URI"+uri);  
            }  
            rowId = db.insert(Profile.TABLE_NAME,null,values);  
            if(rowId>0){  
                Uri noteUri=ContentUris.withAppendedId(Profile.CONTENT_URI, rowId);  
                getContext().getContentResolver().notifyChange(noteUri, null);  
                return noteUri;  
            }  
      
            throw new SQLException("Failed to insert row into " + uri);  
        }  
      
        @Override  
        public boolean onCreate() {  
            // TODO Auto-generated method stub  
            mDbHelper = new DBHelper(getContext());  
      
            db = mDbHelper.getReadableDatabase();  
      
            return true;  
        }  
      
        @Override  
        public Cursor query(Uri uri, String[] projection, String selection,  
                String[] selectionArgs, String sortOrder) {  
            // TODO Auto-generated method stub  
            Cursor c = null;  
            switch (mMatcher.match(uri)) {  
            case Profile.ITEM:  
                c =  db.query(Profile.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);  
                break;  
            case Profile.ITEM_ID:  
                c = db.query(Profile.TABLE_NAME, projection,Profile.COLUMN_ID + "="+uri.getLastPathSegment(), selectionArgs, null, null, sortOrder);  
                break;  
            default:  
                throw new IllegalArgumentException("Unknown URI"+uri);  
            }  
      
            c.setNotificationUri(getContext().getContentResolver(), uri);  
            return c;  
        }  
      
        @Override  
        public int update(Uri uri, ContentValues values, String selection,  
                String[] selectionArgs) {  
            // TODO Auto-generated method stub  
            return 0;  
        }  
      
    }  

    补充一下gettype的作用:
    一个provider可能给你提供多种不同类型的数据,然后你并不知道给你返回的是什么,因为他可能给你返回音乐,视频,或者图片。这时候你就用这个方法获得类型,再进行相应处理
    这个方法是不会自动调用的,自己需要的时候调用

    特别是不同类型的数据需要使用隐式intent让不同的activity处理时,这些activity在mainefest的过滤器中配置了data的mimetype,比如:
    <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
    这样通过intent.addData(conentProvider.getType());可以让其自动去匹配跳转对应的 组件



    如果该Uri对应数据可能包括多条记录,那么MIME类型字符串应该以vnd.android.cursor.dir/开头;如果该Uri对应的数据只包含一条记录,那么返回MIME类型字符串应该以vnd.android.cursor.item/开头


    package com.snowdream.contentprovider;
    
    import com.snowdream.contentprovider.R;
    
    import android.app.ListActivity;
    import android.content.ContentResolver;
    import android.content.ContentUris;
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.database.SQLException;
    import android.database.sqlite.SQLiteDatabase;
    import android.net.Uri;
    import android.os.Bundle;
    import android.view.Menu;
    import android.widget.SimpleCursorAdapter;
    
    
    public class MainActivity extends ListActivity {
    	private SimpleCursorAdapter adapter= null;
    	private Cursor mCursor = null;
    	private ContentResolver mContentResolver = null;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		initData();
    		initAdapter();
    	}
    
    	public void initData(){
    		mContentResolver = getContentResolver();
    
    		//填充数据
    		for (int i = 0; i < 100; i++) {
    			ContentValues values = new ContentValues();
    			values.put(Profile.COLUMN_NAME, "张三"+i);
    			mContentResolver.insert(Profile.CONTENT_URI, values);
    		}
    	}
    
    
    	public void initAdapter(){
    		//查询表格,并获得Cursor
    		//查询全部数据
    		mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, null, null, null);
    
    		//查询部分数据
    		//String selection = Profile.COLUMN_ID + " LIKE '%1'";
    		//mCursor = mContentResolver.query(Profile.CONTENT_URI, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, selection, null, null);
    
    
    		//查询一个数据
    		//Uri uri = ContentUris.withAppendedId(Profile.CONTENT_URI, 50);
    		//mCursor = mContentResolver.query(uri, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, null, null, null);
    
    		startManagingCursor(mCursor);
    
    		//设置adapter
    		adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2, mCursor, new String[]{Profile.COLUMN_ID,Profile.COLUMN_NAME}, new int[]{android.R.id.text1,android.R.id.text2});
    		setListAdapter(adapter);
    	}
    
    
    	@Override
    	public boolean onCreateOptionsMenu(Menu menu) {
    		getMenuInflater().inflate(R.menu.activity_main, menu);
    		return true;
    	}
    
    
    }
    

    效果:
     
  • ContentProvider原理及实例

    参考文章:

    观察者模式和contentprovider数据更新

     
     
     
     
     
     
     
     
     
     
     
  • 你可能感兴趣的:(ContentProvider)