数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑。它只是负责为应用提供数据访问的接口。Android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)。如果把第三方应用比作一个黑盒子的话,ContentProvider就像是从里面延伸出来的管道,从这个管道,应用可以把一些数据共享出来,我们也可以往里面输送数据。但是里面怎么处理数据我们看不到,也管不着。并且这个管道是有规范标准的,不是它规定的数据你塞不进这个管道。

一、ContentProvider的特征

  1. 我们为什么使用ContentProvider?像上几篇写的博客中就有好几种方法可以跨应用来读取数据,但ContentProvider的特点不仅仅如此。首先它作为Android中四大组件之一,(我们都知道组件的信息会被android统一管理),提供数据的跨进程无缝隙访问,并会在进程中提供本地缓存。跨进程调用是需要时间和资源消耗的,而通过缓存可以有效的提高效率。再则ContentProvider规定了数据访问结构,严谨不容易发生错误。然后,应用调用接口进行操作时,是一个同步的过程,也就是说,所有对数据源组件对象中的数据操作都是在消息队列中串行执行的,我们开发者就不需要考虑复杂的并发情形。最后,数据源组件中数据存储的方式没有任何的限制,可以通过数据库、文件等任意方式实现。
  2. 通过什么方式找到想要的ContentProvider?它是通过URI进行定位。URI,就是全局统一定位标志,通过一个结构化的字符串,唯一标识数据源的地址信息,而每个数据源组件都有一个唯一的URI标识。

    ContentProvider的scheme已经由Android所规定, scheme为:content://
    主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
    路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
    要操作person表中id为10的记录,可以构建这样的路径:/person/10
    要操作person表中id为10的记录的name字段, person/10/name
    要操作person表中的所有记录,可以构建这样的路径:/person
    要操作xxx表中的记录,可以构建这样的路径:/xxx
    当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
    要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name
    如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
    Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person")

二、ContentProvider的实例

我们还是通过一个实例来了解它吧。利用ContentProvider来对第三方的数据库进行操作。

  1. 首先我们建一个DBHelper的类继承SQLiteOpenHelper

    package com.example.database;
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    import android.database.sqlite.SQLiteDatabase.CursorFactory; public class DBHelper extends SQLiteOpenHelper{
    private static final int VERSION=1;
    /**
    * 在SQLiteOpenHelper的子类当中,必须有该构造函数
    * @param context 上下文对象
    * @param name 数据库名称
    * @param factory
    * @param version 当前数据库的版本,值必须是整数并且是递增的状态
    */
    public DBHelper(Context context,String name,CursorFactory factory,int version){
    super(context,name,factory,version);
    }
    public DBHelper(Context context, String name, int version){
    this(context,name,null,version);
    } public DBHelper(Context context, String name){
    this(context,name,VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
    // 数据库首次构造时,会调用该函数,可以在这里构造表、索引,等等
    System.out.println("create a database");
    //execSQL用于执行SQL语句
    db.execSQL("create table notebook(_id integer primary key autoincrement,title varchar(20),content text,time long)"); }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // 如果给定的当前数据库版本高于已有数据库版本,调用该函数
    System.out.println("upgrade a database");
    } }

    这一步没什么好解释的,不懂的可以看一看我写的上一篇关于数据库操作的博文。

  2. 接下来我们就要新建一个MyProvider的类继承ContentProvider
    package com.example.database;
    
    import android.content.ContentProvider;
    import android.content.ContentUris;
    import android.content.ContentValues;
    import android.content.UriMatcher;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.net.Uri; public class MyProvider extends ContentProvider { private DBHelper dh = null;// 数据库管理对象
    private SQLiteDatabase db;//获取其中的数据库
    //UriMatcher:Creates the root node of the URI tree.
    //按照官方解释,UriMatcher是一颗Uri的树,然后利用addURI()方法往里面添加枝干,通过match()函数来查找枝干。
    private static final UriMatcher MATCHER = new UriMatcher(
    UriMatcher.NO_MATCH);
    //设定匹配码
    private static final int NOTEBOOK = 1;
    private static final int NOTEBOOKS = 2;
    static {
    //添加枝干,并给它们加上唯一的匹配码,以方便查找
    //如果match()方法匹配content://com.example.database/notebook路径,返回匹配码为1
    MATCHER.addURI("com.example.database", "notebook", NOTEBOOKS);
    //如果match()方法匹配content://com.example.database/notebook/#路径,返回匹配码为2
    //其中#号为通配符。
    MATCHER.addURI("com.example.database", "notebook/#", NOTEBOOK);
    } @Override
    public boolean onCreate() {
    // 创建ContentProvider对象时会调用这个函数
    dh = new DBHelper(this.getContext(),"note.db");// 数据库操作对象
    db = dh.getReadableDatabase();
    return false;
    } /**
    * 查询,返回Cursor
    **/
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrder) {
    //通过match函数,获取匹配码
    switch (MATCHER.match(uri)) {
    case NOTEBOOKS:
    //返回数据库操作的结果
    return db.query("notebook", projection, selection, selectionArgs,
    null, null, sortOrder);
    case NOTEBOOK:
    //因为添加 了id,所以要把id加到where条件中
    long id = ContentUris.parseId(uri);
    String where = "_id=" + id;
    if (selection != null && !"".equals(selection)) {
    where = selection + " and " + where;
    }
    return db.query("notebook", projection, where, selectionArgs, null,
    null, sortOrder);
    default:
    throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
    }
    } //获取Uri的类型
    @Override
    public String getType(Uri uri) {
    // TODO Auto-generated method stub
    switch (MATCHER.match(uri)) {
    case NOTEBOOKS:
    return "com.example.Database.all/notebook"; case NOTEBOOK:
    return "com.example.Database.item/notebook"; default:
    throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
    }
    } //插入数据
    @Override
    public Uri insert(Uri uri, ContentValues values) {
    // TODO Auto-generated method stub
    switch (MATCHER.match(uri)) {
    case NOTEBOOKS:
    //调用数据库的插入功能
    // 特别说一下第二个参数是当title字段为空时,将自动插入一个NULL。
    long rowid = db.insert("notebook", "title", values);
    Uri insertUri = ContentUris.withAppendedId(uri, rowid);// 得到代表新增记录的Uri
    this.getContext().getContentResolver().notifyChange(uri, null);
    return insertUri; default:
    throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
    }
    } //删除数据
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
    // TODO Auto-generated method stub
    int count;
    switch (MATCHER.match(uri)) {
    case NOTEBOOKS:
    count = db.delete("notebook", selection, selectionArgs);
    return count; case NOTEBOOK:
    long id = ContentUris.parseId(uri);
    String where = "_id=" + id;
    if (selection != null && !"".equals(selection)) {
    where = selection + " and " + where;
    }
    count = db.delete("notebook", where, selectionArgs);
    return count; default:
    throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
    }
    } //更新数据
    @Override
    public int update(Uri uri, ContentValues values, String selection,
    String[] selectionArgs) {
    // TODO Auto-generated method stub
    int count = 0;
    switch (MATCHER.match(uri)) {
    case NOTEBOOKS:
    count = db.update("notebook", values, selection, selectionArgs);
    return count;
    case NOTEBOOK:
    long id = ContentUris.parseId(uri);
    String where = "_id=" + id;
    if (selection != null && !"".equals(selection)) {
    where = selection + " and " + where;
    }
    count = db.update("notebook", values, where, selectionArgs);
    return count;
    default:
    throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
    }
    } }

    因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris 。掌握它们的使用,会便于我们的开发工作。
    看上去这个类很像我上次写的DBManager类吧。其实这可以算是一个很简单的数据操作类,关键地方就在于它放在了ContentProvider这个“容器”上,让第三方应用也能访问到己方的数据。所以想要吃透这个组件,只要透彻理解什么是Uri,怎么操作Uri就八九不离十了。

  3. 最后,不要忘记在配置文件中为ContentProvider注册,因为这也是一个组件,所以无法避免了~
    <provider android:name=".MyProvider" android:authorities="com.example.database" />

    前面的是你的类名,后面则是关键地方,它是要写在Uri中的,所以不要弄错了。
    到此,一个可以供其他应用访问的工程就建好了,接下来我们来写个测试工程来检验效果吧。

三、调用ContentProvider

在使用其他应用为你提供的ContentProvider时,你必须要知道的有两点:(1)它的authorities值,在我这里的是“com.example.database”;(2)数据文件的结构,比如我这里要使用的是数据库中的booknote表,它里面有着(_id,title,content,time)这些字段。只有知道了这些你才能操作ContentProvider。

  1. 好的,我们先新建一个工程,设置一下布局文件,效果如下

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" > <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="90dp"
    android:orientation="horizontal" > <LinearLayout
    android:layout_width="120dp"
    android:layout_height="match_parent"
    android:orientation="vertical" > <TextView
    android:id="@+id/textView1"
    android:layout_width="match_parent"
    android:layout_height="45dp"
    android:text="Title:"
    android:textAlignment="center"
    android:textSize="25sp" /> <TextView
    android:id="@+id/textView2"
    android:layout_width="match_parent"
    android:layout_height="45dp"
    android:text="Content:"
    android:textAlignment="center"
    android:textSize="25sp" />
    </LinearLayout> <LinearLayout
    android:layout_width="200dp"
    android:layout_height="match_parent"
    android:orientation="vertical" > <EditText
    android:id="@+id/editText1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="25sp" > <requestFocus />
    </EditText> <EditText
    android:id="@+id/editText2"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="25sp" />
    </LinearLayout>
    </LinearLayout> <ListView
    android:id="@+id/listView1"
    android:layout_width="match_parent"
    android:layout_height="196dp" >
    </ListView> <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="90dp"
    android:layout_weight="0.20" > <Button
    android:id="@+id/button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="获取全部信息" /> <Button
    android:id="@+id/button2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="插入一条信息" /> <Button
    android:id="@+id/button3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="删除一条信息" /> <Button
    android:id="@+id/button4"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="更新一条信息" /> </LinearLayout> </LinearLayout>

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <TextView
    android:layout_width="80dip"
    android:layout_height="wrap_content"
    android:text="_id"
    android:id="@+id/id"
    />
    <TextView
    android:layout_width="80dip"
    android:layout_height="wrap_content"
    android:text="title"
    android:id="@+id/title"
    /> <TextView
    android:layout_width="100dip"
    android:layout_height="wrap_content"
    android:text="content"
    android:id="@+id/content"
    /> <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="time"
    android:id="@+id/time"
    /> </LinearLayout>

    item.xml

  2. 接下来在MainActivity添加代码
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.AdapterView;
    import android.widget.AdapterView.OnItemClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ListView;
    import android.widget.SimpleCursorAdapter;
    import android.widget.Toast;
    import android.app.Activity;
    import android.content.ContentResolver;
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle; public class MainActivity extends Activity implements OnClickListener { private ListView listView;
    private SimpleCursorAdapter adapter;
    private Button button_query, button_insert, button_delete, button_update;
    private EditText editText_title, editText_content;
    private int CurItem; @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); editText_title = (EditText) this.findViewById(R.id.editText1);
    editText_content = (EditText) this.findViewById(R.id.editText2); listView = (ListView) this.findViewById(R.id.listView1);
    listView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view,
    int position, long id) {
    ListView lView = (ListView) parent;
    Cursor data = (Cursor) lView.getItemAtPosition(position);
    int _id = data.getInt(data.getColumnIndex("_id"));
    Toast.makeText(MainActivity.this, _id + "", Toast.LENGTH_SHORT)
    .show();
    CurItem = _id;
    editText_title.setText(data.getString(data.getColumnIndex("title")));
    editText_content.setText(data.getString(data.getColumnIndex("content")));
    }
    });
    button_query = (Button) this.findViewById(R.id.button1);
    button_query.setOnClickListener(this);
    button_insert = (Button) this.findViewById(R.id.button2);
    button_insert.setOnClickListener(this);
    button_delete = (Button) this.findViewById(R.id.button3);
    button_delete.setOnClickListener(this);
    button_update = (Button) this.findViewById(R.id.button4);
    button_update.setOnClickListener(this); } @Override
    public void onClick(View v) {
    //ContentResolver它是ContentProvider提供的一个接口,它能够调用ContentProvider里面的所有方法。
    ContentResolver contentResolver;
    // TODO Auto-generated method stub
    switch (v.getId()) {
    case R.id.button1:
    contentResolver = getContentResolver();
    //Uri.parse()能将字符串转换成Uri格式。
    Uri selectUri = Uri.parse("content://com.example.database/notebook");
    Cursor cursor = contentResolver.query(selectUri, null, null, null,
    null);
    adapter = new SimpleCursorAdapter(this, R.layout.item, cursor,
    new String[] { "_id", "title", "content", "time" },
    new int[] { R.id.id, R.id.title, R.id.content, R.id.time },
    1);
    listView.setAdapter(adapter);
    break;
    case R.id.button2:
    contentResolver = getContentResolver();
    Uri insertUri = Uri
    .parse("content://com.example.database/notebook");
    ContentValues values = new ContentValues();
    values.put("title", editText_title.getText().toString());
    values.put("content", editText_content.getText().toString());
    values.put("time", System.currentTimeMillis());
    Uri uri = contentResolver.insert(insertUri, values);
    Toast.makeText(this, uri.toString() + "添加完成", Toast.LENGTH_SHORT)
    .show();
    break;
    case R.id.button3:
    contentResolver = getContentResolver();
    Uri deleteUri = Uri
    .parse("content://com.example.database/notebook/"+CurItem);
    int d = contentResolver.delete(deleteUri, null,null);
    Toast.makeText(this, CurItem+"删除完成", Toast.LENGTH_SHORT)
    .show();
    break;
    case R.id.button4:
    contentResolver = getContentResolver();
    Uri updateUri = Uri
    .parse("content://com.example.database/notebook/"+CurItem);
    ContentValues updatevalues = new ContentValues();
    updatevalues.put("title", editText_title.getText().toString());
    updatevalues.put("content", editText_content.getText().toString());
    updatevalues.put("time", System.currentTimeMillis());
    int u = contentResolver.update(updateUri, updatevalues,null,null);
    Toast.makeText(this, CurItem+"更新完成", Toast.LENGTH_SHORT)
    .show();
    break;
    }
    }
    }

    两个应用之间的流程图大概就是这样了(手挫,不要嫌弃~)

  3. 最后,将两个应用安装好,打开实践一下。那么我们看看运行结果吧

    正常运行。那么今天就到此结束,收工了~

四、结束语

理论上来说,数据源组件并没有所谓的生命周期,因为数据源组件的状态并不作为判定进程优先级的依据。所以系统回收进程资源时,并不会将数据源组件的销毁事件告诉开发者。但构造ContentProvider组件时还是会调用onCreate()函数。所以,不要在数据源组件中部署延迟写入等写优化策略,当被系统默默回收时,一些未持久化的数据会丢失。一旦数据源组件被构造出来,就会保持长期运行的状态至其所在的进程被系统回收。所以,也不要在数据源组件中缓存过多的数据,以免占用内存空间。

到此,Android的四大组件已经介绍完毕,他们各有各的的特色和用法,同时也是我们开发时候不可缺少的工具。希望通过这些介绍能让大家体会到Android设计的巧妙和特征,学会正确的使用Android应用的框架。那么接下来可能会带来的是Android的Intent机制,尽请期待~

参考文章:(1)ContentProvider和Uri详解  http://www.cnblogs.com/linjiqin/archive/2011/05/28/2061396.html

               (2) ContentProvider浅析   http://bbs.51cto.com/thread-1024108-1.html

               (3) 从头学Android之ContentProvider   http://blog.csdn.net/worker90/article/details/7016430

下载:demo下载

========================================

作者:cpacm

出处:http://www.cpacm.net/2015/03/22/Android开发日记(七)——Android四大组件之ContentProvider/)

【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider的更多相关文章

  1. 【Android开发日记】第一个任务Android Service!Service靴+重力感应器+弹出窗口+保持执行

    前言: 近期在写一个小程序,需求是手机摇一摇就弹窗出来.第一次使用了Service,学习了两天,实现了Service弹窗,开机启动,Service启动和销毁,Service保持一直执行. 满足了自己的 ...

  2. 【Android开发日记】之入门篇(十二)——Android组件间的数据传输

    组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制 ...

  3. 【Android开发日记】之入门篇(七)——Android数据存储(上)

    在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也 ...

  4. 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件

        好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...

  5. 【Android开发日记】之入门篇(十一)——Android的Intent机制

    继续我们的Android之路吧.今天我要介绍的是Android的Intent. 对于基于组件的应用开发而言,不仅需要构造和寻找符合需求的组件,更重要的是要将组件有机的连接起来,互联互通交换信息,才能够 ...

  6. 【Android开发日记】之入门篇(一)——开发环境的搭建

    写给自己的话:至此,大学的时光已经剩下一年的时光,下一年等毕业设计结束后就算是正式地踏入社会.自己学android也不过几个月的时间,为了更好管理文档,写点东西记录下自己曾经做过的点点滴滴是一个不错的 ...

  7. 【Android开发日记】之入门篇(八)——Android数据存储(下)

    废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基 ...

  8. 【Android开发日记】之入门篇(五)——Android四大组件之Service

    这几天忙着驾校考试,连电脑都碰不到了,今天总算告一段落了~~Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通.它跟Activity的 ...

  9. 【Android开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver

    广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应.如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户.又如网络状态改变时,电量变化时都 ...

随机推荐

  1. BZOJ2142 礼物 【扩展Lucas】

    题目 一年一度的圣诞节快要来到了.每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物.不同的人物在小E 心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多.小E从商店中购买了n件礼物, ...

  2. NetApp存储方案及巡检命令

    一.MCC概述 Clustered Metro Cluster(简称MCC)是Netapp Data Ontap提供的存储双活解决方案,当初的方案是把1个FAS/ V系列双控在数据中心之间拉远形成异地 ...

  3. mimikazhi Kerberos Modules

    Kerberos Modules 1.   .#####.   mimikatz 2.0 alpha (x64) release "Kiwi en C" (Oct  9201500 ...

  4. linux中man 2与man 3区别

    1.Standard commands (标准命令)2.System calls (系统调用)3.Library functions (库函数)4.Special devices (设备说明)5.Fi ...

  5. Android 65535 问题与 MultiDex分包

    Android Multidex 遇到的问题 http://blog.csdn.net/wangbaochu/article/details/51178881 Android 使用android-su ...

  6. 《剑指offer》— JavaScript(20)包含min函数的栈

    包含min函数的栈 题目描述 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数. 实现代码 var stack = []; function push(node) { stack. ...

  7. 使用EntitysCodeGenerate

    http://bbs.csdn.net/topics/360256700 public DataSet xxx(DateTime start, DateTime end, string type)   ...

  8. Java入门:绘制简单图形

    在上一节,我们学习了如何使用swing和awt工具创建一个空的窗口,本节学习如何绘制简单图形. 基本绘图介绍 Java中绘制基本图形,可以使用Java类库中的Graphics类,此类位于java.aw ...

  9. nginx启用stream日志配置文件

    主配置文件/etc/nginx/nginx.conf增加内容: stream { log_format proxy '$remote_addr [$time_local] ' '$protocol $ ...

  10. 使用Githubdesktop管理Eclipse项目

    使用Githubdesktop管理Eclipse项目 觉得有用的话,欢迎一起讨论相互学习~[Follow] 方案 使用Eclipse创建项目,使用githubdesktop进行管理 项目右键, Tea ...