【Android的从零单排开发日记】——Android四大组件之ContentProvider

数据源组件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. 好的,我们先新建一个工程,设置一下布局文件,效果如下

     activity_main.xml

     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()函数。所以,不要在数据源组件中部署延迟写入等写优化策略,当被系统默默回收时,一些未持久化的数据会丢失。一旦数据源组件被构造出来,就会保持长期运行的状态至其所在的进程被系统回收。所以,也不要在数据源组件中缓存过多的数据,以免占用内存空间。

时间: 2024-11-16 04:26:45

【Android的从零单排开发日记】——Android四大组件之ContentProvider的相关文章

【Android的从零单排开发日记】之入门篇(四)——Android四大组件之Activity

在Android中,无论是开发者还是用户,接触最多的就算是Activity.它是Android中最复杂.最核心的组件.Activity组件是负责与用户进行交互的组件,它的设计理念在很多方面都和Web页面类似.当然,这种相似性主要体现在设计思想上.在具体实现方面,Android的Activity组件有自己的设计规范,同时,它能够更简便地使用线程.文件数据等本地资源. 一.Activity 的生命周期 Activity 的生命周期是被以下的函数控制的. 1 public class Activity

【Android的从零单排开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver

广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应.如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户.又如网络状态改变时,电量变化时都可以通过广播来通知用户.要做比喻的话,广播就像是我们的感官,能够有效且快速的从外界获取信息来反馈给自身. 一.广播的功能和特征 广播的生命周期很短,经过 调用对象—实现onReceive—结束 整个过程就结束了.从实现的复杂度和代码量来看,广播无疑是最迷你的Android 组件,实现往往只需几行代码

【Android的从零单排开发日记】之入门篇(三)——Android目录结构

本来的话,这一章想要介绍的是Android的系统架构,毕竟有了这些知识的储备,再去看实际的项目时才会更清楚地理解为什么要这样设计,同时在开发中遇到难题,也可以凭借着对Android的了解,尽快找出哪些模块和设计能够帮助解决该问题.但想了一下,这毕竟是入门篇,若没有实际项目开发经验的人看了之后肯定是一头雾水,所以就决定将其搁浅到大家熟悉Android之后再为大家介绍. 那么今天的主题是Android的目录结构,将系统架构比作人的骨骼架构的话,目录结构就像是人的各个器官,彼此功能各不相同,却能有序地

【Android的从零单排开发日记】之入门篇(五)——Android四大组件之Service

这几天忙着驾校考试,连电脑都碰不到了,今天总算告一段落了~~Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通.它跟Activity的级别差不多,但不能自己运行只能后台运行.service可以在很多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等, 总之服务总是藏在后台的. ps:Service运行在主线程中的,所

【Android的从零单排开发日记】之入门篇(八)——Android数据存储(下)

废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基于数据库的应用.Android的数据库应用,依托于当下最流行的开源嵌入式数据库SQLite.在Android中,应用的数据库文件是该应用私有的,存储在应用数据目录下的databases子目录内.从代码结构来看,Android的数据库实现可以分成两个层次,在底层通过C++调用SQLite的接口来执行S

【Android的从零单排开发日记】之入门篇(七)——Android数据存储(上)

在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也是开发平台必须提供的基础功能.不光是在Android平台上,在其他的平台上,数据的存储永远是不可缺少的一块.Android的数据存储是构建在Linux的文件系统上,它充分利用Linux的账号系统来限定应用对数据的访问,部署了一套安全和灵活并重的数据存储解决方案.Android的文件框架,以及各种数据

【Android的从零单排开发日记】之入门篇(一)——开发环境的搭建

写给自己的话:至此,大学的时光已经剩下一年的时光,下一年等毕业设计结束后就算是正式地踏入社会.自己学android也不过几个月的时间,为了更好管理文档,写点东西记录下自己曾经做过的点点滴滴是一个不错的选择,接下来都会将我自己所学所感一一记录下来,算是给后来的我一份复习的资料和还算不错的回忆. 开始正题吧,android环境的安装,网上很多教程,本来不想写这章的,后来帮同学配置时想想有个总结也是不错的,所以就写了这篇满是链接的文章. 一.需要准备的文件 JDK 官方网站 http://www.or

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

组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制数据. 一.Intent数据传递 那么首先是简单的跳转.我们可以借助bundle这个容器来存放我们想要传递的数据. Intent intent = new Intent(); intent.setClass(activity1.this, activity2.class); //描述起点和目标 Bu

【Android的从零单排开发日记】——Android数据存储(上)

在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也是开发平台必须提供的基础功能.不光是在Android平台上,在其他的平台上,数据的存储永远是不可缺少的一块.Android的数据存储是构建在Linux的文件系统上,它充分利用Linux的账号系统来限定应用对数据的访问,部署了一套安全和灵活并重的数据存储解决方案.Android的文件框架,以及各种数据