(转)Android开发之ContentProvider详解


转自Android开发之内容提供者——创建自己的ContentProvider(详解)

苦心人天不负卧薪尝胆三千越甲可吞吴,有志者天不负釜底抽薪百二秦川终属楚。这是一对非常励志的名言,每当读这句话都会被震撼一下,然后接着颓废,哈哈,最近的工作比较忙,也在这里提醒自己,一定要坚持下去,一定要坚持一件对自己有益的事情。

装逼到此进入正题,今天要讨论的主要内容是ContentProvider(内容提供者),ContentProvider也是Android的四大组件之一,可见其在android中的重要性,可能大家用ContentProvider比其他三个组件用的少一点,但是ContentProvider同样的非常重要,有的人知道怎么使用ContentProvider,但是对于ContentProvider的原理等,并没有搞清楚,没关系,通过本篇博客相信你会对ContentProvider有一个全新的认识。
通过本篇博客你将学到以下知识
①什么是内容提供者
②为什么会有内容提供者
③怎样使用内容提供者
④ContentProvider中的Uri的详细介绍
⑤ContentResolver讲解
⑥UriMatch用法介绍
⑦ContentObserver用法详解
⑧通过一个案例来讲解自定义ContentProvider的执行过程(下一篇将给大家带来调用系统的ContentProvider)

1. 什么是内容提供者?

首先我们必须要明白的是ContentProvider(内容提供者)是android中的四大组件之一,但是在一般的开发中,可能使用比较少。ContentProvider为不同的软件之间数据共享,提供统一的接口而且ContentProvider是以类似数据库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URI来表示外界需要访问的“数据库”。至于如何从URI中识别出外界需要的是哪个“数据库”这就是Android底层需要做的事情了,也就是说,如果我们想让其他的应用使用我们自己程序内的数据,就可以使用ContentProvider定义一个对外开放的接口,从而使得其他的应用可以使用我们自己应用中的文件、数据库内存储的信息。当然,自己开发的应用需要给其他应用共享信息的需求可能比较少见,但是在Android系统中,很多数据如:联系人信息、短信信息、图片库、音频库等,这些信息在开发中还是经常用到的,这些信息谷歌工程师已经帮我们封装好了,我们可以使用谷歌给我的Uri去直接访问这些数据。所以对于ContentProvider我们还是需要认真的学习的,在遇到获取联系人信息,图片库,音视频库等需求的时候,才能更好的实现功能。

2.为什么会有内容提供者?

当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式,这也是为什么会有内容提供者的原因。

3.怎么使用内容提供者?

ContentProvider是android应用程序的基本构建块之一,它们封装数据并将封装的数据通过单一的ContentResolver接口提供给应用程序。当你需要在多个应用之间共享数据的时候就需要用到内容提供者。例如,手机中的联系人数据会被多个应用所用到所以必须要用ContentProvider存储起来。如果你不需要在多个应用之间共享数据,你可以使用一个数据库,直接通过SQLite数据库。 当通过content resolver发送一个请求时,系统会检查给定的URI并把请求传给有注册授权的Contentprovider。 UriMatcher类有助于解析uri。

需要实现的主要方法是:

  1. public boolean onCreate() 在创建ContentProvider时调用

  2. public Cursor query(Uri, String[], String, String[], String) 用于查询指定Uri的ContentProvider,返回一个Cursor

  3. public Uri insert(Uri, ContentValues) 用于添加数据到指定Uri的ContentProvider中,(外部应用向ContentProvider中添加数据)

  4. public int update(Uri, ContentValues, String, String[]) 用于更新指定Uri的ContentProvider中的数据

  5. public int delete(Uri, String, String[]) 用于从指定Uri的ContentProvider中删除数据

  6. public String getType(Uri) 用于返回指定的Uri中的数据的MIME类型

    数据访问的方法(如:insert(Uri, ContentValues) and update(Uri, ContentValues, String, String[]))可能被多个线程同时调用,此时必须是线程安全的。其他方法(如: onCreate())只能被应用的主线程调用,它应当避免冗长的操作。ContentResolver(内容解析者)请求被自动转发到合适的内容提供者实例,所以子类不需要担心跨进程调用的细节。

4.Uri详解

在上面的翻译中如果你认真看的话你会发现在谷歌的官方文档中提到了ContentResolver,外界可以通过ContentResolver接口来访问ContentProvider中的数据。但是在详细了解ContentResolver之前有一项工作是必须要做的,那就是先理解Uri,在谷歌文档中也有介绍,接下来我们就来详细的学习下Uri这个类
Uri 通用资源标志符(Universal Resource Identifier)Uri代表要操作的数据,Android中可用的每种资源 - 图像、视频片段等都可以用Uri来表示。Uri的结构由以下几个部分组成
scheme、authority、path、query和fragment组成。其中authority又分为host和port。它的格式根据划分的详细程度可以分为三种
如下:

[scheme:][scheme-specific-part][#fragment]
[scheme:][//authority][path][?query][#fragment]
[scheme:][//host:port][path][?query][#fragment]——最详细的划分形式

看到这里肯定有人糊里糊涂的,接着我们就来举一个例子来帮助大家详细的理解Uri这个类的结构
假如有这么一个Uri:http://www.baidu.com:8080/yourpath/fileName.html?id=15&name=du#dmk
你能将上述Uri进行提取吗?接着我们就比着标准的格式[scheme:][//host:port][path][?query][#fragment]来将这个Uri各个部分提取出来

scheme:根据标准格式可以看出这里的scheme就是Uri前面//前面的部分这里也就是http:。

fragment:dmk这个也是比较容易找到的,在#后面

query:id=15&name=du#dmk。从标准格式可以看到在”#”之前”?”之后的部分是query,在这里当然就是id=15&name=du#dmk了。

authority:从格式二中可以看到authority是在//后的部分,它的终点就是在path之前所以这里的authority就是www.baidu.com:8080

path:path就是?之前,主机之后的部分那就是yourpath/fileName.html

host和port:因为主机可以分为host和port所以这里的host和port分别为:www.baidu.com和8080

这里要提醒大家注意的是:在Uri中并不是上述所有的字段都必须有的除了scheme、authority是必须要有的,其它的几个path、query、fragment,它们每一个可以选择性的要或不要,但顺序不能变,比方说在上述Uri中没有path那它的格式就为:http://www.baidu.com:8080/?id=15&name=du#dmk
在理解了Uri的格式之后,有的人可能会说Uri的各个字段能否用代码获取?答案是肯定的.

这里我们同样以http://www.baidu.com:8080/yourpath/fileName.html?id=15&name=du#dmk为例

  • getScheme() :获取Uri中的scheme字符串部分,在这里是http

  • getSchemeSpecificPart():获取Uri中的scheme-specific-part:部分,这里是:http://www.baidu.com:8080/yourpath/fileName.html?

  • getFragment():获取Uri中的fragment部分,即dmk

  • getAuthority():获取Uri中Authority部分,即www.baidu.com:8080

  • getPath():获取Uri中path部分,即/yourpath/fileName.html

  • getQuery():获取Uri中的query部分,即id=15&name=du

  • getHost():获取Authority中的Host字符串,即www.baidu.com

  • getPort():获取Authority中的Port字符串,即8080

到这里关于Uri的介绍就完了.

5.ContentResolver讲解

在了解了Uri之后就可以来学习学习ContentResolver了,前面我们说到ContentProvider共享数据是通过定义一个对外开放的统一的接口来实现的。然而,应用程序并不直接调用这些方法,而是使用一个 ContentResolver 对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,要获取ContentResolver对象,可以使用Context提供的getContentResolver()方法。ContentResolver cr = getContentResolver();在上面我们提到ContentProvider可以向其他应用程序提供数据,与之对应的ContentResolver则负责获取ContentProvider提供的数据,修改、添加、删除更新数据等;

ContentResolver 类也提供了与ContentProvider类相对应的四个方法:

  • public Uri insert(Uri uri, ContentValues values) 该方法用于往ContentProvider添加数据。

  • public int delete(Uri uri, String selection, String[] selectionArgs) 该方法用于从ContentProvider删除数据。

  • public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 该方法用于更新ContentProvider中的数据

  • public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于从ContentProvider中获取数据。

这些方法的第一个参数为Uri,代表要操作的是哪个ContentProvider和对其中的什么数据进行操作,假设给定的是 Uri.parse(“content://com.qstingda.provider.personprovider/contact/15”),那么将会对主机名为com.qstingda.provider.personprovider的ContentProvider进行操作,path为contact/15的数据,看到这如果你之前没有接触过ContentProvider肯定一头雾水,没有关系,这很正常,等我们把理论知识讲完后会有实例,相信你看过实例后就会很明白了。

6.UriMatch

UriMatcher 类主要用于匹配Uri.这里的匹配是发生在ContentProvider中的,假如我们向ContentProvider中插入一条数据,不可能为所欲为的想怎么干就怎么干,在ContentProvider肯定要做一个判断,只有在符合条件下才会去执行你想要执行的操作,这里的判断就是用UriMatch进行匹配,假如是系统的ContentProvider如联系人、图库、视频库等,这些系统都提供了Uri我们可以根据系统提供的Uri来操作相应的数据。其实UriMatch的用法非常简单,查阅谷歌官方文档你会发现有这么几个方法

①public UriMatcher(int code) 它的作用就是创建一个UriMatch对象

②public void addURI(String authority,String path, int code)
它的作用是在ContentProvider添加一个用于匹配的Uri,当匹配成功时返回code。

Uri可以是精确的字符串,Uri中带有*表示可匹配任意text,#表示只能匹配数字

③public int match(Uri uri) 这里的Uri就是传过来的要进行验证,匹配的Uri假如传过来的是:content://com.example.test/student/#,则content://com.example.test/student/10可以匹配成功,这里的10可以使任意的数字。

7.ContentObserver用法

ContentObserver——内容观察者,从其名字我们可以看出它的作用就是观察,观察什么?观察指定的Uri引起的数据库的变化,然后通知主线程,根据需求做我们想要做的处理。这样说大家可能理解的不是特别透彻,这样跟大家说它可以实现类似于Adapter的notifyDataSetChanged()这个方法的作用,比方说当观察到ContentProvider的数据变化时会自动调用谷歌工程师给我们提供的好的方法,可以在此方法中通知主线程数据改变等等。那么问题来了,应该怎样实现这样的功能呢?首先要做的就是注册这个观察者,这里的注册是在需要监测ContentProvider的应用中进行注册并不是在ContentProvider中而在ContentProvider中要做的就是当数据变化时进行通知,这里的通知的方法谷歌已经帮我们写好,直接调用就行了,查看谷歌文档你会发现在ContentResolver中有这样的介绍:

public final void registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserver observer)
注册一个观察者实例,当指定的Uri发生改变时,这个实例会回调实例对象做相应处理。
参数:uri:需要观察的Uri
notifyForDescendents:如果为true表示以这个Uri为开头的所有Uri都会被匹配到,
如果为false表示精确匹配,即只会匹配这个给定的Uri。

举个例子,假如有这么几个Uri:

①content://com.example.studentProvider/student
②content://com.example.studentProvider/student/#
③content://com.example.studentProvider/student/10
④content://com.example.studentProvider/student/teacher
假如观察的Uri为content://com.example.studentProvider/student,当notifyForDescendents为true时则以这个Uri开头的Uri的数据变化时都会被捕捉到,在这里也就是①②③④的Uri的数据的变化都能被捕捉到,当notifyForDescendents为false时则只有①中Uri变化时才能被捕捉到。

看到registerContentObserver 这个方法,根据语言基础我想大家能够想到ContentResolver中的另一个方法
public final void unregisterContentObserver(ContentObserverobserver)它的作用就是取消对注册的那个Uri的观察,这里传进去的就是在registerContentObserver中传递进去的ContentObserver对象。到这关于注册和解除注册的ContentObserver可能大家都比较清楚了,那么问题来了,怎么去写一个ContentObserver呢?其实它的实现很简单,直接创建一个类继承ContentObserver需要注意的是这里必须要实现它的构造方法
public ContentObserver(Handlerhandler)
这里传进去的是一个Handler对象,这个Handler对象的作用一般要依赖于ContentObserver的另一个方法即
public void onChange(boolean selfChange)
这个方法的作用就是当指定的Uri的数据发生变化时会回调该方法,此时可以借助构造方法中的Handler对象将这个变化的消息发送给主线程,当主线程接收到这个消息之后就可以按照我们的需求来完成相应的操作,比如上面提到的类似于Adapter的notifyDataSetChanged()的作用,下面的案例也是完成了这个功能,准备工作完成之后来看一个案例,相信这个案例会让你对以上知识了解的更加深入。

8.案例(自定义ContentProvider)

在真正的开发中我们很少去自定义一个ContentProvider因为ContentProvider是为了更好的去共享数据,我们在开发中很少会遇到这种情况,而遇到更多的则是访问系统的ContentProvider,系统的ContentProvider谷歌工程师已经帮我们写好了,我们直接使用就可以了,这里为了让大家能够理解ContentProvider更加彻底,我们自定义一个ContentProvider然后在其它应用中来访问自定义的ContentProvider的数据这个案例的运行效果如下:
此处输入图片的描述

这里的插入数据,是在一个项目中向另一个项目中的ContentProvider中插入一条数据,其他的操作也是,接下来就来看看怎么实现上述的效果。
在上面我们提到在自定义ContentProvider时需要继承ContentProvider并实现3中所述的那几个方法(系统会自动帮你将要复写的方法罗列出来),那么我们自定义的PeopleContentProvider的代码如下:
//自定义ContentProvider
public class PeopleContentProvider extends ContentProvider {

     //这里的AUTHORITY就是我们在AndroidManifest.xml中配置的authorities,这里的authorities可以随便写  ,定义主机名
    private static final String AUTHORITY = "com.example.studentProvider";  
    //匹配成功后的匹配码  
    private static final int MATCH_ALL_CODE = 100;  
    private static final int MATCH_ONE_CODE = 101;  
    private static UriMatcher uriMatcher;  
    private SQLiteDatabase db;  
    private DBOpenHelper openHelper;  
    private Cursor cursor = null;  
    //数据改变后指定通知的Uri  
    private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY + "/student");  


    static {
        //匹配不成功返回NO_MATCH(-1)  
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        /** 
         * uriMatcher.addURI(authority, path, code); 其中 
         * authority:主机名(用于唯一标示一个ContentProvider,这个需要和清单文件中的authorities属性相同) 
         * path:路径路径(可以用来表示我们要操作的数据,路径的构建应根据业务而定) 
         * code:返回值(用于匹配uri的时候,作为匹配成功的返回值) 
         */  
        uriMatcher.addURI(AUTHORITY, "student", MATCH_ALL_CODE);
        uriMatcher.addURI(AUTHORITY, "student/#", MATCH_ONE_CODE);
    }


    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
    synchronized (PeopleContentProvider.class) {


        System.out.println("uri:"+uri.toString());

        switch(uriMatcher.match(uri)){
         /** 
         * 这里如果匹配是uriMatcher.addURI(AUTHORITY, "student", 
         * MATCH_SUCCESS_CODE);中的Uri,则我们可以在这里对这个ContentProvider中的数据库 
         * 进行删除等操作。这里如果匹配成功,我们将删除所有的数据 
         */  
        case MATCH_ALL_CODE:
            //如果匹配整個student,清空表personData,然后提示
            int  count = db.delete("personData", null, null);
            if(count>0){
                notifyDataChanged();
                return count;
            }
            break;
        case MATCH_ONE_CODE:
            //这里可以做删除单条数据的操作。  
            break;
            default:
                throw new IllegalArgumentException(" unkown uri:"+uri);
        }

        return 0;
            }
    }

    @Override
    public String getType(Uri uri) {

        return null;
    }

     /** 
     * 插入 使用UriMatch的实例中的match方法对传过来的 Uri进行匹配。 这里通过ContentResolver传过来一个Uri, 
     * 用这个传过来的Uri跟在ContentProvider中静态代码块中uriMatcher.addURI加入的Uri进行匹配 
     * 根据匹配的是否成功会返回相应的值,在上述静态代码块中调用uriMatcher.addURI(AUTHORITY, 
     * "student",MATCH_CODE)这里的MATCH_CODE 
     * 就是匹配成功的返回值,也就是说假如返回了MATCH_CODE就表示这个Uri匹配成功了 
     * ,我们就可以按照我们的需求就行操作了,这里uriMatcher.addURI(AUTHORITY, 
     * "person/data",MATCH_CODE)加入的Uri为: 
     * content://com.example.studentProvider/student 
     * ,如果传过来的Uri跟这个Uri能够匹配成功,就会按照我们设定的步骤去执行相应的操作 
     */  
    @Override
    public Uri insert(Uri uri, ContentValues values) {
    synchronized (this) {
        System.out.println("uri:"+uri.toString());
        int match = uriMatcher.match(uri);
        if(match!=MATCH_ALL_CODE){
            throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
        }
        //数据库插入数据
        long rawId = db.insert("personData", null, values);
        //把uri和rawId拼接成新的uri
        //例如:content://" + AUTHORITY + "/student,rawId = 100;
        //新的uri: newUri = content://" + AUTHORITY + "/student/100,
        //parseId(uri),就是把后面的id解析出来,比如parseId(newUri) = 100;
        Uri insertUri = ContentUris.withAppendedId(uri, rawId);
        if(rawId>0){
            notifyDataChanged();
            return insertUri;
        }
        return null;
        }
    }

    @Override
    public boolean onCreate() {
        System.out.println("on create...");
        openHelper = new DBOpenHelper(getContext());
        db = openHelper.getWritableDatabase();
        return false;
    }

      /** 
     * 查询 如果uri为 
     * content://com.example.studentProvider/student则能匹配成功,然后我们可以按照需求执行匹配成功的操作 
     */  
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    synchronized (this) {
        System.out.println("uri:"+uri.toString());
        switch(uriMatcher.match(uri)){
        /** 
         * 如果匹配成功,就根据条件查询数据并将查询出的cursor返回 ,查询整个表
         */  
        case MATCH_ALL_CODE:
              cursor = db.query("personData", null, null, null, null, null, null);  
            break;
        case MATCH_ONE_CODE:
            //查询一条数据
            break;
            default:
                throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
        }
        return cursor;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    synchronized (this) {
        System.out.println("uri:"+uri.toString());
        switch(uriMatcher.match(uri)){
        //更新一条数据,这里是把后面的id作为age,进行更新数据库,在下面看到appendWithId(,26);其实是更新age=26的数据
        case MATCH_ONE_CODE:
            long age = ContentUris.parseId(uri);
            selection = "age=?";
            selectionArgs = new String[]{String.valueOf(age)};
            int count = db.update("personData", values, selection, selectionArgs);
            if(count>0){
                notifyDataChanged();
            }
            break;
        case MATCH_ALL_CODE :
             // 如果有需求的话,可以对整个表进行操作  
            break;
            default:
                throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());

        }
        return 0;
        }
    }
      //通知指定URI数据已改变    
    private void notifyDataChanged(){
        getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
    }

}

看下用到的DataHelper,除了建数据库,建表什么都没有干

public class DBOpenHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "person.db"; //数据库名称  
    private static final int version = 1; //数据库版本  

    public DBOpenHelper(Context context) {  
        super(context, DB_NAME, null, version);  
    }  

    @Override  
    public void onCreate(SQLiteDatabase db) { //此方法只有在调用getWritableDatabase()或getReadableDatabase()方法时才会执行  
        String sql = "create table personData("+  
                 "_id        integer primary key," +       
                 "name      varchar(20) not null," +       
                 "age       Integer," +       
                 "introduce varchar(20) not null)";           
        db.execSQL(sql);  
    }  
    //如果DATABASE_VERSION值被改为2,系统发现现有数据库版本不同,即会调用onUpgrade    
    @Override  
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}

记得在Manifest.xml中声明自定义的ContentProvider

         <provider 
             android:name=".provider.PeopleContentProvider" 
             [!--这个就是我们定义的主机名--]
             android:authorities="com.example.studentProvider" 
             [!-- 允许其它应用访问 --]
             android:exported="true"/>

注册好之后运行到手机上,此时其它的应用就可以通过ContentResolver来访问这个PeopleContentProvider了,具体怎么操作呢?我们再新建一个项目在MainActivity的代码如下:

public class ProviderActivity extends Activity implements OnClickListener{
    private ContentResolver contentResolver;
    private ListView lvShowInfo;
    private MyAdapter adapter;
    private Button btnInit;
    private Button btnInsert;
    private Button btnDelete;
    private Button btnUpdate;
    private Button btnQuery;
    private Cursor cursor;

    private static final String AUTHORITY = "com.example.studentProvider";  
    private static final Uri STUDENT_ALL_URI = Uri.parse("content://" + AUTHORITY + "/student");
    protected static final String TAG = "MainActivity"; 

    private Handler handler=new Handler(){
        public void handleMessage(android.os.Message msg) {
            //在此我们可以针对数据改变后做一些操作,比方说Adapter.notifyDataSetChanged()等,根据业务需求来定。。
            cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);

            adapter.changeCursor(cursor);
        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_1);
        lvShowInfo=(ListView) findViewById(R.id.lv_show_info);
        initData();
    }

    private void initData() {
        btnInit=(Button) findViewById(R.id.btn_init);
        btnInsert=(Button) findViewById(R.id.btn_insert);
        btnDelete=(Button) findViewById(R.id.btn_delete);
        btnUpdate=(Button) findViewById(R.id.btn_update);
        btnQuery=(Button) findViewById(R.id.btn_query);

        btnInit.setOnClickListener(this);
        btnInsert.setOnClickListener(this);
        btnDelete.setOnClickListener(this);
        btnUpdate.setOnClickListener(this);
        btnQuery.setOnClickListener(this);

        contentResolver = getContentResolver();
        //注册内容观察者,全部以这个主机名开头的都可以观察
        contentResolver.registerContentObserver(STUDENT_ALL_URI,true,new PersonOberserver(handler));

        adapter=new MyAdapter(this,cursor);
        lvShowInfo.setAdapter(adapter);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        //初始化
        case R.id.btn_init:
            ArrayList<Student> students = new ArrayList<Student>();  

            Student student1 = new Student("苍老师",25,"一个会教学的好老师");  
            Student student2 = new Student("柳岩",26,"大方");  
            Student student3 = new Student("杨幂",27,"漂亮");  
            Student student4 = new Student("张馨予",28,"不知道怎么评价");  
            Student student5 = new Student("范冰冰",29,"。。。");

            students.add(student1);  
            students.add(student2);  
            students.add(student3);  
            students.add(student4); 
            students.add(student5); 

            for (Student s : students) {  
                ContentValues values = new ContentValues();  
                values.put("name", s.getName());  
                values.put("age", s.getAge());  
                values.put("introduce", s.getIntroduce());  
                contentResolver.insert(STUDENT_ALL_URI, values);
            }
            break;

        //增
        case R.id.btn_insert:

            Student student = new Student("小明", 26, "帅气男人");  

            //实例化一个ContentValues对象
            ContentValues insertContentValues = new ContentValues();  
            insertContentValues.put("name",student.getName());  
            insertContentValues.put("age",student.getAge());  
            insertContentValues.put("introduce",student.getIntroduce());  

            //这里的uri和ContentValues对象经过一系列处理之后会传到ContentProvider中的insert方法中,
            //在我们自定义的ContentProvider中进行匹配操作
            Uri u = contentResolver.insert(STUDENT_ALL_URI,insertContentValues);
            break;

        //删
        case R.id.btn_delete:

            //删除所有条目
            contentResolver.delete(STUDENT_ALL_URI, null, null); 
            //删除_id为1的记录  
            Uri delUri = ContentUris.withAppendedId(STUDENT_ALL_URI,1);  
            contentResolver.delete(delUri, null, null);
            break;

        //改
        case R.id.btn_update:

            ContentValues contentValues = new ContentValues();
            contentValues.put("introduce","性感");
            //更新数据,将age=26的条目的introduce更新为"性感",原来age=26的introduce为"大方".
            //生成的Uri为:content://com.example.studentProvider/student/26
            Uri updateUri = ContentUris.withAppendedId(STUDENT_ALL_URI,26);
            System.out.println("update uri:"+updateUri.toString());
            contentResolver.update(updateUri,contentValues, null, null);

            break;

        //查
        case R.id.btn_query:
            //通过ContentResolver获得一个调用ContentProvider对象
            Cursor cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);
            adapter=new MyAdapter(this,cursor);
            lvShowInfo.setAdapter(adapter);
            cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);
            adapter.changeCursor(cursor);
            break;
        }
    }
}

可以看出若想操作我们想操作的ContentProvider,必须要知道内容提供者的Uri,再正确得到Uri之后,就可以通过ContentResolver对象来操作ContentProvider中的数据了,假如你需要插入数据只需要调用contentResolver.insert(uri, contentValues);把正确的uri和ContentValues键值对传过去就行了。执行这句话系统就会根据我们提供的uri找到对应的ContentProvider,因为我们的uri中包含了authority(主机等各种信息),得到对应的ContentProvider后将调用ContentResolver的与之对应的增删改查方法,并将参数通过ContentResolver的增删改查方法传递到ContentProvider中.

看下adapter和ContentObserver

public class MyAdapter extends CursorAdapter {

    public MyAdapter(Context context, Cursor c) {
        super(context, c);
    }

    public MyAdapter(Context context, Cursor c, boolean autoRequery) {
        super(context, c, autoRequery);
    }

    public MyAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
    }

    private Context mContext;

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ViewHolder viewHolder = (ViewHolder) view.getTag();
        String name = cursor.getString(cursor.getColumnIndex("name"));
        String phoneNumber = cursor.getString(cursor.getColumnIndex("age"));
        String introduce = cursor.getString(cursor.getColumnIndex("introduce"));

        viewHolder.tvName.setText(name);
        viewHolder.tvAge.setText(phoneNumber);
        viewHolder.tvIntroduce.setText(introduce);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {

        ViewHolder viewHolder = new ViewHolder();
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.item_information, null);

        viewHolder.tvName = (TextView) view.findViewById(R.id.tv_name_content);
        viewHolder.tvAge = (TextView) view.findViewById(R.id.tv_age_content);
        viewHolder.tvIntroduce = (TextView) view.findViewById(R.id.tv_introduce_content);
        view.setTag(viewHolder);
        return view;
    }

    class ViewHolder {
        TextView tvName;
        TextView tvAge;
        TextView tvIntroduce;
    }

}

//看我们自定义的ContentObserver;

public class PersonOberserver extends ContentObserver {

    private Handler handler;

    public PersonOberserver(Handler handler) {
        super(handler);
        this.handler=handler;
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        Message msg = Message.obtain();  
        handler.sendMessage(msg);
    }

}

可以看到,在构造方法中接收了Handler然后当监听到指定的Uri的数据变化时就会通过Handler消息机制发送一条消息,然后的操作就由我们自行完成了。

到这里我们来理一理整个操作的运行流程:首先有两个项目,一个是有ContentProvider的,在这个ContentProvider中初始化了一个数据库,我们的目的就是在另一个项目中来操作这个项目中ContentProvider中的数据,例如插入一条数据,查询等。对于怎么在另一个项目中操作ContentProvider中的数据,是通过ContentResolver对象来操作的,假如我们要进行insert操作,那么需要调用ContentResolver的insert(uri, ContentValues);将Uri和ContentValues对象经过一系列操作传递到ContentProvider的中,然后在ContentProvider会对这个Uri进行匹配,如果匹配成功则按照我们的需求去执行相应的操作,如:插入数据、查询数据等。如果想进一步理解ContentProvider和ContentResolver之间的关系
此处输入图片的描述

从图中可以看出,ContentProvider对外界提供了一套统一的数据库访问接口,外界不能直接操作数据库,而且ContentProvider要做好多线程处理的准备,确保线程安全。
在OtherApplication中注册了ContentObserver之后,当Application1中的数据库发生了变化时,只需要在ContentProvider中调用ContentResolver的notifyChange(Uri,ContentObserver observer),由于在OtherApplication中注册了ContentObserver(注册时用的Uri和ContentProvider中发生变化的Uri一样)因此在ContentObserver中会收到这个变化信息,它就可以将这个消息通过Handler发送给OtherApplication。
好了,本篇关于ContentProvider的介绍主要是ContentProvider的基础知识,以及自定义一个ContentProvider并操作它的执行过程。

源码案例下载

猜你喜欢

转载自blog.csdn.net/baidu_17508977/article/details/53468846