zoukankan      html  css  js  c++  java
  • Android开发历程_17(ContentProvider的使用)

      

       前言        

      Content Provider为存储数据和获取数据提供了统一的接口,它可以完成在不同应用程序下的数据共享,而在上一篇文章中Android开发历程_16(SQLite的使用) 讲到的SQLite只能在同一个程序中共享数据。另外android为一些常见的数据,比如说音频,视频,图片,通讯录等提供了Content Provider,这样我们就可以很方便的对这些类型的数据操作了。使用ContentProvider的好处是开发人员不需要考虑数据内部是怎么存储的,比如说如果我们想利用ContenProvider来存数据,只需告诉insert函数该ContentProvider的uri和想存入的数据(包括列名和数值),查询时也是一样,只需输入Uri和查询的表,列名和查询条件,至于ContentProvider里面是怎么进行这些操作的我们不需要知道。

      实验基础

      在了解本实验的内容,需要用到下面这几个跟ContentProvider有关的类。

      UriMatcher:

      要了解UriMatcher,首先需要了解android中的Uri表示方法,众所周知,Uri为通用资源标识符,它代表的是要操作的数据,Android中的每一种资源(比如文本,图像,视频等)都可以用Uri来表示。Android中的Uri由以下三部分组成:”content://”(即authory),数据的路径,资源标识ID(可选),其中如果存在ID,则表示某一个具体的资源,如果不存在ID,则表示路径下的整体。因此addUri()函数的3个参数也是对应上面的那3个。

      网上有一篇文章将UriMatcher比较详细,可以参考:http://blog.csdn.net/feng88724/article/details/6331396  

      UriMatcher的匹配过程分为3步,初始化UriMatcher;注册需要用的Uri;与已经注册的Uri进行匹配。

      ContentResolver :

      当使用ContentProvider在不同的应用程序中共享数据时,其数据的暴露方式是采取类似数据库中表的方法。而ContentResolver 是恰好是采用类似数据库的方法来从ContentProvider中存取数据的,它是通过Uri来查询ContentProvider中提供的数据,查询时,还需知道目的数据库的名称,数据段的数据类型,或者说资源的ID。

      SQLiteQueryBuilder:

      SQLiteQueryBuilder是一个用来生产SQL查询语句的辅助类,可以方便的去访问SQLiteDatabase. 在构造SQL查询语句时,它同样也需要指定表名,指定列名,指定where条件等。

      实验过程

      本文通过一个实例来实现一个ContentProvider,其实一般情况下我们是不需要自己来实现的,而是使用andorid内置的ContentProvider,但是自己实现一个以后,对它的原理能更深刻的认识,以后使用内置的就得心应手了。这是mars老师的话,本人火候不够,暂时还没深刻的体会。Mars老师将实现ContentProvider的步骤总结为如下:

       

      程序中需要4个java文件,下面就分别来介绍实现这些类需注意的事项:

      FirstProviderMetaData类:

      因为在继承类FirstContentProvider得到的子类中要用到很多常量,所以这里我们新建了一个类专门用来存储这些常量的,该类这里取名为FirstProviderMetaData,里面存着authorty名,数据库名,数据库版本号,表名,字表名,子表Uri,子表ContentProvider数据类型等等。其中字表是继承BaseColumns类的,而BaseColumns类中已经有_ID和_COUNT这2列了。

      DatabaseHelper类:

      与android中使用SQLite类似,这里同样需要一个继承SQLiteOpenHelper的子类,子类名为DatabaseHelper,我们在子类的回调函数onCreate()中建立了一个表,表名和表中的列名都是引用FirstProviderMetaData类中定义好了的常量的。

      FirstContentProvider类:

      新建一个类,名为FirstContentProvider,继承类ContentProvider这个类,且必须重写父类的下面5个方法,否则会报错。这5个方法分别为onCreate(), getType(), insert(), update(), delete().

      onCreate()为回调函数,是指当ContentProvider创建的时候调用,本程序在该函数中使用DatabaseHelper新建了一个SQLite数据库。

      在getType()方法完成的功能是根据传入的Uri,返回该Uri所表示的数据类型。函数内部是使用的UriMatcher来匹配该函数所传进来的参数,来得到其数据类型。

      insert()函数给指定的数据库表中插入一个指定的值,插入完成后必然会生成一个新的记录,然后利用该记录和表的Uri重新生成一个新的Uri,这个Uri就代表了插入成功的那条记录的Uri,该函数返回的也是这个Uri。

      MainActivity类:

      在MainActivity中,主要是有2个按钮,分为为它们绑定了监听器,来完成插入和查询操作。

      当单击插入按钮时,在监听器函数中会首先得到一个ContentResolver,然后当在执行ContentResolver的insert方法时会自动调用ContentProvider的insert方法,因为ContentResolver的insert方法中的第一个参数就为某个ContentProvider的Uri。

      AndroidManifest.xml:

      ContentProvider的使用需要在AndroidManifest.xml中进行注册,在activity标签外加入如下声明即可:

    <provider
        android:name="com.example.cptest.FirstContentProvider"
    
        android:authorities="com.example.cptest.FirstContentProvider"
    
        />

      实验主要部分代码及注释:

    MainActivity.java:

    package com.example.cptest;
    
    //import com.example.cptest.FirstProviderMetaData;
    import com.example.cptest.FirstProviderMetaData.UserTableMetaData;
    
    import android.app.Activity;
    import android.content.ContentValues;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    
    public class MainActivity extends Activity {
    
        private Button insert = null;
        private Button query = null;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            insert = (Button)findViewById(R.id.insert);
            insert.setOnClickListener(new InsertOnClickListener());
            query = (Button)findViewById(R.id.query);
            query.setOnClickListener(new QueryOnClickListener());
            System.out.println(getContentResolver().getType(FirstProviderMetaData.UserTableMetaData.CONTENT_URI));
        }
    
        //往子表中插入一条记录
        public class InsertOnClickListener implements OnClickListener{
    
            public void onClick(View arg0) {
                // TODO Auto-generated method stub    
                ContentValues values = new ContentValues();
                values.put(FirstProviderMetaData.UserTableMetaData.USER_NAME, "tornadomeet");
                //实际上使用的是ContentResolver的insert方法
                //该insert中有2个参数,第一个为代表了ContentProvider的Uri,第二个参数为要插入的值。此处的insert函数
                //一执行,则自动调用ContentProvider的insert方法。
                Uri uri = getContentResolver().insert(FirstProviderMetaData.UserTableMetaData.CONTENT_URI, 
                        values);
                System.out.println("uri--->" +uri.toString());
            }        
        }
        
        
        //查询也是采用的ContentResolver中的query方法。
        public class QueryOnClickListener implements OnClickListener{
            public void onClick(View v) {
                // TODO Auto-generated method stub        
                Cursor c = getContentResolver().query(FirstProviderMetaData.UserTableMetaData.CONTENT_URI, 
                        null, null, null, null);
                while(c.moveToNext())
                    System.out.println(c.getString(c.getColumnIndex(UserTableMetaData.USER_NAME)));
            }     
        }
        
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.activity_main, menu);
            return true;
        }
    }

    DatabaseHelper:

    package com.example.cptest;
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    import android.database.sqlite.SQLiteDatabase.CursorFactory;
    
    public class DatabaseHelper extends SQLiteOpenHelper {
    
        private static final int  VERSON = 1;//默认的数据库版本
        
        //继承SQLiteOpenHelper类的类必须有自己的构造函数
        //该构造函数4个参数,直接调用父类的构造函数。其中第一个参数为该类本身;第二个参数为数据库的名字;
        //第3个参数是用来设置游标对象的,这里一般设置为null;参数四是数据库的版本号。
        public DatabaseHelper(Context context, String name, CursorFactory factory, int verson){
            super(context, name, factory, verson);
        }
        
        //该构造函数有3个参数,因为它把上面函数的第3个参数固定为null了
        public DatabaseHelper(Context context, String name, int verson){
            this(context, name, null, verson);
        }
        
        //该构造函数只有2个参数,在上面函数 的基础山将版本号固定了
        public DatabaseHelper(Context context, String name){
            this(context, name, VERSON);
        }
        
        //该函数在数据库第一次被建立时调用
        @Override
        public void onCreate(SQLiteDatabase arg0) {
            // TODO Auto-generated method stub
            System.out.println("create a database");
            //execSQL()为执行参数里面的SQL语句,因此参数中的语句需要符合SQL语法,这里是创建一个表
            //arg0.execSQL("create table user1(id int, name varchar(20))");下面的语句格式是与该句类似
    //        arg0.execSQL("create table" + FirstProviderMetaData.USERS_TABLE_NAME
    //                + "(" + FirstProviderMetaData.UserTableMetaData._ID 
    //                + " INTEGER PRIMARY KEY AUTOINCREMENT," +   //ID类型为自增长的整型
    //                FirstProviderMetaData.UserTableMetaData.USER_NAME + " varchar(20));"
    //                );
        //    arg0.execSQL("create table user1(id int, name varchar(20))");
            arg0.execSQL("create table" + FirstProviderMetaData.USERS_TABLE_NAME + "("
                    + FirstProviderMetaData.UserTableMetaData._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                    +FirstProviderMetaData.UserTableMetaData.USER_NAME + " varchar(20))");
            System.out.println("create a database ok");
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
            // TODO Auto-generated method stub
            System.out.println("update a database");
        }
    
    }

    FirstProviderMetaData.java:

    package com.example.cptest;
    
    import android.net.Uri;
    import android.provider.BaseColumns;
    
    public class FirstProviderMetaData {
    
        //这里的AUTHORTY为包的全名+ContentProvider子类的全名
        public static final String AUTHORTY = "com.example.cptest.FirstContentProvider";
        //数据库的名称
        public static final String DATABASE_NAME = "FisrtProvider.db";
        //数据库的版本号
        public static final int DATABASE_VERSION = 1;
        //数据库中的表名
        public static final String USERS_TABLE_NAME = "users";
        //表中的字表
        public static final class UserTableMetaData implements BaseColumns{
            //子表名
            public static final String TABLE_NAME = "users";
            //CONTENT_URI为常量Uri; parse是将文本转换成Uri
            public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORTY + "/users");
            //返回ContentProvider中表的数据类型
            public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.firstprovider.user";
            //返回ContentProvider表中item的数据类型
            public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.firstprovider.user";
            //子表列名
            public static final String USER_NAME = "name";
            //表中记录的默认排序算法,这里是降序排列
            public static final String DEFAULT_SORT_ORDER = "_id desc";
            
            
        }
        
    }

    FirstContentProvider.java:

    package com.example.cptest;
    
    import java.util.HashMap;
    
    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.database.sqlite.SQLiteQueryBuilder;
    import android.net.Uri;
    import android.text.TextUtils;
    
    import com.example.cptest.FirstProviderMetaData.UserTableMetaData;
    
    public class FirstContentProvider extends ContentProvider {
    
        //定义一个UriMatcher类对象,用来匹配Uri的。
        public static final UriMatcher uriMatcher;
        //组时的ID
        public static final int INCOMING_USER_COLLECTION = 1;
        //单个时的ID
        public static final int INCOMING_USER_SIGNAL = 2;
        private DatabaseHelper dh;//定义一个DatabaseHelper对象
        static{
            uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);//UriMatcher.NO_MATCH表示不匹配任何路径的返回码
            uriMatcher.addURI(FirstProviderMetaData.AUTHORTY, "users", INCOMING_USER_COLLECTION);
            uriMatcher.addURI(FirstProviderMetaData.AUTHORTY, "users/#", INCOMING_USER_SIGNAL);//后面加了#表示为单个
        }
        
        public static HashMap<String, String> userProjectionMap;//新建一个HashMap,后面执行插入操作时有用
        static
        {
            userProjectionMap = new HashMap<String, String>();
            //这里可以直接调用另外一个类的public变量,这里put里面的2个参数一样,
            //是因为这里是给数据库表中的列取别名,因此取的是一样的名字
            userProjectionMap.put(UserTableMetaData._ID, UserTableMetaData._ID);
            userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME);
        }
        
        //得到ContentProvider的数据类型,返回的参数Uri所代表的数据类型
        @Override
        public String getType(Uri arg0) {
            // TODO Auto-generated method stub
            System.out.println("getType");
            switch(uriMatcher.match(arg0)){
            //matcher满足Uri的前2项(即协议+路径)为第1种情况时,switch语句的值为Uri的第3项,此处为INCOMING_USER_COLLECTION
            case INCOMING_USER_COLLECTION:
                return UserTableMetaData.CONTENT_TYPE;
            case INCOMING_USER_SIGNAL://同上
                return UserTableMetaData.CONTENT_TYPE_ITEM;
            default:
                throw new IllegalArgumentException("Unknown URI" + arg0);//throw是处理异常的,java中的语法
            }
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            // TODO Auto-generated method stub
            System.out.println("delete");
            return 0;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection,
                String[] selectionArgs) {
            // TODO Auto-generated method stub
            System.out.println("update");
            return 0;
        }
    
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            // TODO Auto-generated method stub
            System.out.println("insert");
    //        dh = new DatabaseHelper(getContext(), FirstProviderMetaData.DATABASE_NAME);
            SQLiteDatabase db = dh.getWritableDatabase();
            long rowId = db.insert(UserTableMetaData.TABLE_NAME, null, values);
    //        System.out.println("insert  OK");
    //        System.out.println("" + rowId);
            if(rowId > 0){            
                //发出通知给监听器,说明数据已经改变
                //ContentUris为工具类
                Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);
                getContext().getContentResolver().notifyChange(insertedUserUri, null);
                
                return insertedUserUri;
            }
            throw new SQLException("Failed to insert row into" + uri);
        }
    
        //回调函数,在ContentProvider创建的时候调用
        @Override
        public boolean onCreate() {
            // TODO Auto-generated method stub
            System.out.println("onCreate");
            dh = new DatabaseHelper(getContext(), FirstProviderMetaData.DATABASE_NAME);//创建1个DatabaseHelper对象
            return true;
        }
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                String[] selectionArgs, String sortOrder) {
            // TODO Auto-generated method stub
            System.out.println("query");
            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
            switch(uriMatcher.match(uri)){
            case INCOMING_USER_COLLECTION:
                qb.setTables(UserTableMetaData.TABLE_NAME);//设置表的名称
                qb.setProjectionMap(userProjectionMap);//其中userProjectionMap为上面建立好了的hashmap
                break;
            case INCOMING_USER_SIGNAL:
                qb.setTables(UserTableMetaData.TABLE_NAME);//设置表的名称
                qb.setProjectionMap(userProjectionMap);//其中userProjectionMap为上面建立好了的hashmap
                //uri.getPathSegments()得到Path部分,即把uri的协议+authory部分去掉,把剩下的部分分段获取,这里取第
                //一部分
                qb.appendWhere(UserTableMetaData._ID + "=" +uri.getPathSegments().get(1));//设置where条件
                break;
            }
            //排序
            String orderBy;
            if(TextUtils.isEmpty(sortOrder)){
                orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;//传入的排序参数为空的时候采用默认的排序
            }
            else{
                orderBy = sortOrder;//不为空时用指定的排序方法进行排序
            }
            SQLiteDatabase db = dh.getWritableDatabase();
            //采用传入的参数进行查询
            Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
            //发出通知
            c.setNotificationUri(getContext().getContentResolver(), uri);
            return c;
        }
    
    }

      总结:

         本次实验是mars老师自己实现的一个ContentProvider类,这有利于对它建立的流程有个整体的了解,不过经过本次实验,还是对代码中很多地方不是特别理解。

      参考资料:

         http://blog.csdn.net/feng88724/article/details/6331396

         http://www.mars-droid.com/bbs/forum.php

  • 相关阅读:
    rest framework 认证 权限 频率
    rest framework 视图,路由
    rest framework 序列化
    10.3 Vue 路由系统
    10.4 Vue 父子传值
    10.2 Vue 环境安装
    10.1 ES6 的新增特性以及简单语法
    Django 跨域请求处理
    20190827 On Java8 第十四章 流式编程
    20190825 On Java8 第十三章 函数式编程
  • 原文地址:https://www.cnblogs.com/tornadomeet/p/2671085.html
Copyright © 2011-2022 走看看