美文网首页android开发专题Android技术知识Android开发经验谈
【Android开发基础系列】数据持久化专题

【Android开发基础系列】数据持久化专题

作者: Kevin_Junbaozi | 来源:发表于2018-04-16 00:04 被阅读115次

    1 Android四种数据持久化方式

            Android有四种数据持久化方式:

        SharePreference

            轻量级键-值方式存储,以XML文件方式保存。

        文件

            采用java.io.*库所提供有I/O接口,读写文件。

        SQLit数据

            SQLite是轻量级嵌入式内置数据库。

        ContentProvider

            ContentProvider可为数据封装,为多个应用共享

    2 SharePreference

    Android--sharepreference总结

    http://blog.csdn.net/wulianghuan/article/details/8501063

    2.1 简介

            SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data//shared_prefs目录下:

            一个简单的存储代码如下:

    SharedPreferences sharedPreferences = getSharedPreferences("wujay", Context.MODE_PRIVATE); //私有数据

    Editor editor = sharedPreferences.edit();     //获取编辑器

    editor.putString("name", "wujaycode");

    editor.putInt("age", 4);

    editor.commit();    //提交修改

    生成的wujay.xml文件内容如下:

    2.2 使用方法

            分析以下几个方法:

    2.2.1 一、getSharedPreferences(name, mode)

            方法的第一个参数用于指定该文件的名称,名称不用带后缀,后缀会由Android自动加上;方法的第二个参数指定文件的操作模式,共有四种操作模式。

            四种操作模式分别为:

        1. MODE_APPEND: 追加方式存储

        2. MODE_PRIVATE: 私有方式存储,其他应用无法访问

        3. MODE_WORLD_READABLE: 表示当前文件可以被其他应用读取

        4. MODE_WORLD_WRITEABLE: 表示当前文件可以被其他应用写入

    2.2.2 二、edit()方法获取editor对象

    Editor editor = sharedPreferences.edit();

            editor存储对象采用key-value键值对进行存放,

    editor.putString("name", "wujaycode");

            通过commit()方法提交数据,与之对应的获取数据的方法:

    SharedPreferences share = getSharedPreferences("Acitivity", Activity.MODE_WORLD_READABLE);

    int i = share.getInt("i", 0);

    String str = share.getString("str", "");

    boolean flag = share.getBoolean("flag", false);

            getString()第二个参数为缺省值,如果preference中不存在该key,将返回缺省值。如果你想要删除通过SharedPreferences产生的文件,可以通过以下方法:

    File file = newFile("/data/data/"+getPackageName().toString()+"/shared_prefs", "Activity.xml");

    if(file.exists()){

        file.delete();

        Toast.makeText(TestActivity.this, "删除成功", Toast.LENGTH_LONG).show();

    }

    2.2.3 三、访问其他应用中的Preference

            如果要访问其他应用中的Preference,必须满足的条件是,要访问的应用的Preference创建时指定了Context.MODE_WORLD_READABLE或者Context.MODE_WORLD_WRITEABLE权限。

            举例,假如有个name>为com.wujay.action下面的应用使用了下面语句创建了Preference,getSharedPreferences("wujay", Context.MODE_WORLD_READABLE),

            现在要访问该Preferences:

            首先,需要创建上面的Context,然后通过Context访问Preferences,访问preference时会在应用所在包下的shared_prefs目录找到preference:

    Context otherAppsContext = createPackageContext("com.wujay.action", Context.CONTEXT_IGNORE_SECURITY);

    SharedPreferences sharedPreferences = otherAppsContext.getSharedPreferences("wujay", Context.MODE_WORLD_READABLE);

    String name = sharedPreferences.getString("name", "");

    int age = sharedPreferences.getInt("age", 0);

            如果不通过创建Context访问其他应用的preference,可以以读取xml文件方式直接访问其他应用preference对应的xml文件,如:

    File xmlFile = new File(“/data/data/name>/shared_prefs/itcast.xml”); //应替换成应用的包名。

            创建SharePreference

    SharedPreferences settings = this.getSharedPreferences("TestXML", 0);

    SharedPreferences.Editor localEditor = settings.edit();

    //A_MAP03类必需要继承了Activity的子类 才会有getSharedPreferences方法.

            以键值<String Key, String Value> 方式加入数据:

    localEditor.putBoolean("ShowNote", false);

    IocalEditor.commit();

            以 String Key 为索引来取出数据:

    String str =settings.getString("ShowNote", "");

            清除数据

    localEditor.clear().commit();

    SharedPreferences数据保存在:

            存入XML后的内容目录:/data/data/<包>/shared_prefs/***.xml

    3 SQLite

    Android中SQLite应用详解

    http://blog.csdn.net/liuhe688/article/details/6715983/

    最受欢迎的5个Android ORM框架

    http://www.codeceo.com/article/5-android-orm-framework.html

    3.1 简介

            现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧。对于Android平台来说,系统内置了丰富的API来供开发人员操作SQLite,我们可以轻松的完成对数据的存取。

            下面就向大家介绍一下SQLite常用的操作方法,为了方便,我将代码写在了Activity的onCreate中:

    3.2 SQLite常用操作方法

    3.2.1 Db创建

        @Override

        protected void onCreate(Bundle savedInstanceState) {

            super.onCreate(savedInstanceState);

            //打开或创建test.db数据库

            SQLiteDatabase db = openOrCreateDatabase("test.db", Context.MODE_PRIVATE, null);

            db.execSQL("DROP TABLE IF EXISTS person");

            //创建person表

            db.execSQL("CREATE TABLE person (_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR, age SMALLINT)");

            Person person = new Person();

            person.name = "john";

            person.age = 30;

            //插入数据

            db.execSQL("INSERT INTO person VALUES (NULL, ?, ?)", new Object[]{person.name, person.age});

            person.name = "david";

            person.age = 33;

            //ContentValues以键值对的形式存放数据

            ContentValues cv = new ContentValues();

            cv.put("name", person.name);

            cv.put("age", person.age);

            //插入ContentValues中的数据

            db.insert("person", null, cv);

            cv = new ContentValues();

            cv.put("age", 35);

            //更新数据

            db.update("person", cv, "name = ?", new String[]{"john"});

            Cursor c = db.rawQuery("SELECT * FROM person WHERE age >= ?", new String[]{"33"});

            while(c.moveToNext()) {

                int _id = c.getInt(c.getColumnIndex("_id"));

                String name = c.getString(c.getColumnIndex("name"));

                int age = c.getInt(c.getColumnIndex("age"));

                Log.i("db", "_id=>" + _id + ", name=>" + name + ", age=>"+ age);

        }

        c.close();

        //删除数据

        db.delete("person", "age < ?", new String[]{"35"});

        //关闭当前数据库

        db.close();

        //删除test.db数据库

        //      deleteDatabase("test.db");

            在执行完上面的代码后,系统就会在/data/data/[PACKAGE_NAME]/databases目录下生成一个“test.db”的数据库文件,如图:

            上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用。

    3.2.2 executeSQL

    db.executeSQL(String sql);

    db.executeSQL(String sql, Object[] bindArgs);//sql语句中使用占位符,然后第二个参数是实际的参数集

            除了统一的形式之外,他们还有各自的操作方法:

    3.2.3 增删改

    db.insert(String table, String nullColumnHack, ContentValues values);

    db.update(String table, Contentvalues values, String whereClause, String whereArgs);

    db.delete(String table, String whereClause, String whereArgs);

            以上三个方法的第一个参数都是表示要操作的表名;insert中的第二个参数表示如果插入的数据每一列都为空的话,需要指定此行中某一列的名称,系统将此列设置为NULL,不至于出现错误;insert中的第三个参数是ContentValues类型的变量,是键值对组成的Map,key代表列名,value代表该列要插入的值;update的第二个参数也很类似,只不过它是更新该字段key为最新的value值,第三个参数whereClause表示WHERE表达式,比如“age> ? and age < ?”等,最后的whereArgs参数是占位符的实际参数值;delete方法的参数也是一样。

    3.2.4 查询

            下面来说说查询操作。查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式:

    db.rawQuery(String sql, String[] selectionArgs);

    db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);

    db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);

    db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);

            上面几种都是常用的查询方法,第一种最为简单,将所有的SQL语句都组织到一个字符串中,使用占位符代替实际参数,selectionArgs就是占位符实际参数集;下面的几种参数都很类似,columns表示要查询的列所有名称集,selection表示WHERE之后的条件语句,可以使用占位符,groupBy指定分组的列名,having指定分组条件,配合groupBy使用,orderBy指定排序的列名,limit指定分页参数,distinct可以指定“true”或“false”表示要不要过滤重复值。需要注意的是,selection、groupBy、having、orderBy、limit这几个参数中不包括“WHERE”、“GROUPBY”、“HAVING”、“ORDER BY”、“LIMIT”等SQL关键字。

            最后,他们同时返回一个Cursor对象,代表数据集的游标,有点类似于JavaSE中的ResultSet。

            下面是Cursor对象的常用方法:

    c.move(int offset); //以当前位置为参考,移动到指定行

    c.moveToFirst();    //移动到第一行

    c.moveToLast();     //移动到最后一行

    c.moveToPosition(int position); //移动到指定行

    c.moveToPrevious(); //移动到前一行

    c.moveToNext();     //移动到下一行

    c.isFirst();        //是否指向第一条

    c.isLast();     //是否指向最后一条

    c.isBeforeFirst();  //是否指向第一条之前

    c.isAfterLast();    //是否指向最后一条之后

    c.isNull(int columnIndex);  //指定列是否为空(列基数为0)

    c.isClosed();       //游标是否已关闭

    c.getCount();       //总数据项数

    c.getPosition();    //返回当前游标所指向的行数

    c.getColumnIndex(String columnName);//返回某列名对应的列索引值

    c.getString(int columnIndex);   //返回当前行指定列的值

            在上面的代码示例中,已经用到了这几个常用方法中的一些,关于更多的信息,大家可以参考官方文档中的说明。

            最后当我们完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。

            上面就是SQLite的基本应用,但在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。

    3.2.5 封装操作类DBHelper

          下面,我们就以一个实例来讲解具体的用法,我们新建一个名为db的项目,结构如下:

           其中DBHelper继承了SQLiteOpenHelper,作为维护和管理数据库的基类,DBManager是建立在DBHelper之上,封装了常用的业务方法,Person是我们的person表对应的JavaBean,MainActivity就是我们显示的界面。

            下面我们先来看一下DBHelper:

    package com.scott.db;

    import android.content.Context;

    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) {

            //CursorFactory设置为null,使用默认值

            super(context, DATABASE_NAME, null, DATABASE_VERSION);

    }

        //数据库第一次被创建时onCreate会被调用

        @Override

        public void onCreate(SQLiteDatabase db) {

            db.execSQL("CREATE TABLE IF NOT EXISTS person" + "(_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR, age INTEGER, info TEXT)");

    }

        //如果DATABASE_VERSION值被改为2,系统发现现有数据库版本不同,即会调用onUpgrade

        @Override

        public void onUpgrade(SQLiteDatabase db, int oldVersion, intnewVersion) {

            db.execSQL("ALTER TABLE person ADD COLUMN other STRING");

        }

    }

            正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade方法,我们可以执行修改表结构等语句。

            为了方便我们面向对象的使用数据,我们建一个Person类,对应person表中的字段,如下:

    package com.scott.db;

    public class Person {

        public int_id;

        public String name;

        public int age;

        public String info;

        public Person() {

        }

        public Person(String name, int age, String info) {

            this.name = name;

            this.age = age;

            this.info = info;

        }

    }

            然后,我们需要一个DBManager,来封装我们所有的业务方法,代码如下:

    package com.scott.db;

    import java.util.ArrayList;

    import java.util.List;

    import android.content.ContentValues;

    import android.content.Context;

    import android.database.Cursor;

    import android.database.sqlite.SQLiteDatabase;

    public class DBManager {

        private DBHelper helper;

        private SQLiteDatabase db;

        public DBManager(Context context) {

            helper = new DBHelper(context);

            //因为getWritableDatabase内部调用了mContext.openOrCreateDatabase(mName, 0, mFactory);

            //所以要确保context已初始化,我们可以把实例化DBManager的步骤放在Activity的onCreate里

            db = helper.getWritableDatabase();

        }

        /**

         * add persons

         * @param persons

         */

        public void add(List persons) {

            db.beginTransaction();  //开始事务

            try{

                for(Person person : persons) {

                    db.execSQL("INSERT INTO person VALUES(null, ?, ?, ?)", new Object[]{person.name, person.age, person.info});

                }

                db.setTransactionSuccessful();  //设置事务成功完成

            } finally{

                db.endTransaction();    //结束事务

            }

        }

        /**

         * update person's age

         * @param person

         */

        public void updateAge(Person person) {

            ContentValues cv = new ContentValues();

            cv.put("age", person.age);

            db.update("person", cv, "name = ?", new String[]{person.name});

        }

        /**

         * delete old person

         * @param person

         */

        public void deleteOldPerson(Person person) {

            db.delete("person", "age >= ?", new String[]{String.valueOf(person.age)});

        }

        /**

         * query all persons, return list

         * @return List

         */

        public List query() {

            ArrayList persons = new ArrayList();

            Cursor c = queryTheCursor();

            while(c.moveToNext()) {

                Person person = new Person();

                person._id = c.getInt(c.getColumnIndex("_id"));

                person.name = c.getString(c.getColumnIndex("name"));

                person.age = c.getInt(c.getColumnIndex("age"));

                person.info = c.getString(c.getColumnIndex("info"));

                persons.add(person);

            }

            c.close();

            return persons;

        }

        /**

         * query all persons, return cursor

         * @return  Cursor

         */

        public Cursor queryTheCursor() {

            Cursor c = db.rawQuery("SELECT * FROM person", null);

            return c;

        }

        /**

         * close database

         */

        public void closeDB() {

            db.close();

        }

    }

           我们在DBManager构造方法中实例化DBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;在添加多个Person信息时,我们采用了事务处理,确保数据完整性;最后我们提供了一个closeDB方法,释放数据库资源,这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。

            我们获取数据库实例时使用了getWritableDatabase()方法,也许朋友们会有疑问,在getWritableDatabase()和getReadableDatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。

            我们来看一下SQLiteOpenHelper中的getReadableDatabase()方法:

    public synchronized SQLiteDatabase getReadableDatabase() {

        if (mDatabase != null && mDatabase.isOpen()) {

            // 如果发现mDatabase不为空并且已经打开则直接返回

            return mDatabase;

        }

        if(mIsInitializing) {

            // 如果正在初始化则抛出异常

            throw new IllegalStateException("getReadableDatabase called recursively");

        }

        // 开始实例化数据库mDatabase

        try{

            // 注意这里是调用了getWritableDatabase()方法

            return getWritableDatabase();

        } catch(SQLiteException e) {

            if (mName == null)

                throw e; // Can't open a temp database read-only!

            Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);

        }

        // 如果无法以可读写模式打开数据库 则以只读方式打开

        SQLiteDatabase db = null;

        try{

            mIsInitializing = true;

            String path = mContext.getDatabasePath(mName).getPath();// 获取数据库路径

            // 以只读方式打开数据库

            db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);

            if(db.getVersion() != mNewVersion) {

                throw new SQLiteException("Can't upgrade read -only database from version " + db.getVersion() + " to " + mNewVersion + ": "+ path);

            }

            onOpen(db);

            Log.w(TAG, "Opened " + mName + " in read-only mode");

            mDatabase = db;// 为mDatabase指定新打开的数据库

            return mDatabase;// 返回打开的数据库

        } finally{

            mIsInitializing = false;

            if (db != null&& db != mDatabase)

                db.close();

        }

    }

            在getReadableDatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mDatabase赋值为最新打开的数据库实例。既然有可能调用到getWritableDatabase()方法,我们就要看一下了:

    public synchronized SQLiteDatabase getWritableDatabase() {

        if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {

            // 如果mDatabase不为空已打开并且不是只读模式 则返回该实例

            return mDatabase;

        }

        if(mIsInitializing) {

            throw new IllegalStateException("getWritableDatabase called recursively");

        }

        // If we have a read-only database open, someone could be using it

        // (though they shouldn't), which would cause a lock to be held on

        // the file, and our attempts to open the database read-write would

        // fail waiting for the file lock. To prevent that, we acquire the

        // lock on the read-only database, which shuts out other users.

        boolean success = false;

        SQLiteDatabase db = null;

        // 如果mDatabase不为空则加锁 阻止其他的操作

        if (mDatabase != null)

            mDatabase.lock();

        try{

            mIsInitializing = true;

            if (mName == null) {

                db = SQLiteDatabase.create(null);

            } else{

                // 打开或创建数据库

                db = mContext.openOrCreateDatabase(mName, 0, mFactory);

            }

            // 获取数据库版本(如果刚创建的数据库,版本为0)

            int version = db.getVersion();

            // 比较版本(我们代码中的版本mNewVersion为1)

            if(version != mNewVersion) {

                db.beginTransaction();    // 开始事务

                try{

                    if (version == 0) {

                        // 执行我们的onCreate方法

                        onCreate(db);

                    } else{

                        // 如果我们应用升级了mNewVersion为2,而原版本为1则执行onUpgrade方法

                        onUpgrade(db, version, mNewVersion);

                    }

                    db.setVersion(mNewVersion);// 设置最新版本

                    db.setTransactionSuccessful();// 设置事务成功

                } finally{

                    db.endTransaction();// 结束事务

                }

            }

            onOpen(db);

            success = true;

            return db;// 返回可读写模式的数据库实例

        } finally{

            mIsInitializing = false;

            if(success) {

                // 打开成功

                if (mDatabase != null) {

                    // 如果mDatabase有值则先关闭

                    try{

                        mDatabase.close();

                    } catch(Exception e) {

                    }

                    mDatabase.unlock();// 解锁

                }

                mDatabase = db;// 赋值给mDatabase

            } else{

                // 打开失败的情况:解锁、关闭

                if (mDatabase != null)

                    mDatabase.unlock();

                if (db != null)

                    db.close();

            }

        }

    }

            大家可以看到,几个关键步骤是,首先判断mDatabase如果不为空已打开并不是只读模式则直接返回,否则如果mDatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mDatabase并解锁,把新打开的数据库实例赋予mDatabase,并返回最新实例。

    3.3 Realm

    4 ContentProvider

    Android ContentProvider的介绍(很详细)

    http://xiechengfa.iteye.com/blog/1415829

    android四大组件--ContentProvider详解

    http://www.2cto.com/kf/201404/296974.html

    ContentProvider总结(Android)

    http://blog.csdn.net/chuyuqing/article/details/39995607

    4.1 ContentProvider简介

            ContentProvider:为存储和获取数据提供统一的接口。可以在不同的应用程序之间共享数据。Android已经为常见的一些数据提供了默认的ContentProvider。

        1、ContentProvider使用表的形式来组织数据

                无论数据的来源是什么,ContentProvider都会认为是一种表,然后把数据组织成表格。

        2、ContentProvider提供的方法

            query:查询

            insert:插入

            update:更新

            delete:删除

            getType:得到数据类型

            onCreate:创建数据时调用的回调函数

       3、每个ContentProvider都有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。Android所提供的ContentProvider都存放在android.provider包当中。

    4.1.1 使用ContentProvider共享数据

        1)ContentProvider类主要方法的作用:

    public boolean onCreate():

            该方法在ContentProvider创建后就会被调用,Android开机后,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中获取数据。

    public String getType(Uri uri):

            该方法用于返回当前Url所代表数据的MIME类型。

    2)如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,

            例如:要得到所有person记录的Uri为content://com.bing.provider.personprovider/person,那么返回的MIME类型字符串应该为:"vnd.android.cursor.dir/person"。

    3)如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,

            例如:得到id为10的person记录,Uri为content://com.bing.provider.personprovider/person/10,那么返回的MIME类型字符串为:"vnd.android.cursor.item/person"。

    4.1.2 ContentResolver操作ContentProvider中的数据

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

            2)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和对其中的什么数据进行操作,其实和contentprovider里面的方法是一样的。他们所对应的数据,最终是会被传到我们在之前程序里面定义的那个contentprovider类的方法,假设给定的是:Uri.parse("content://com.bing.providers.personprovider/person/10"),那么将会对主机名为com.bing.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。

            使用ContentResolver对ContentProvider中的数据进行添加、删除、修改和查询操作:

    ContentResolver resolver =  getContentResolver();

    Uri uri = Uri.parse("content://com.bing.provider.personprovider/person");

    //添加一条记录

    ContentValues values = newContentValues();

    values.put("name", "bingxin");

    values.put("age", 25);

    resolver.insert(uri, values); 

    //获取person表中所有记录

    Cursor cursor = resolver.query(uri, null, null, null, "personid desc");

    while(cursor.moveToNext()){

          Log.i("ContentTest", "personid=" + cursor.getInt(0)+", name=" + cursor.getString(1));

    }

    //把id为1的记录的name字段值更改新为zhangsan

    ContentValues updateValues = newContentValues();

    updateValues.put("name", "zhangsan");

    Uri updateIdUri = ContentUris.withAppendedId(uri, 2);

    resolver.update(updateIdUri, updateValues, null, null);

    //删除id为2的记录

    Uri deleteIdUri =ContentUris.withAppendedId(uri, 2);

    resolver.delete(deleteIdUri, null, null);

    4.1.3 监听ContentProvider中数据的变化

            如果ContentProvider的访问者需要知道ContentProvider中的数据发生变化,可以在ContentProvider发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下:

    public class PersonContentProviderextends ContentProvider {

        public Uri insert(Uri uri, ContentValues values) {

            db.insert("person", "personid", values);

            getContext().getContentResolver().notifyChange(uri, null);

        }

    }

            如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听,当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:

    getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"), true, new PersonObserver(new Handler()));

    public class PersonObserver extends ContentObserver{

        public PersonObserver(Handler handler) {

            super(handler);

        }

        public void onChange(boolean selfChange) {

             //此处可以进行相应的业务处理

         }

    }

    4.2 Uri介绍

    4.2.1 Uri简介   

            Uri为系统的每一个资源给其一个名字,比方说通话记录。

        1)每一个ContentProvider都拥有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据。

        2)Android所提供的ContentProvider都存放在android.provider包中。 将其分为A,B,C,D 4个部分:

            A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的"content://";

            B:URI 的标识,用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的类名。这个标识在 元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称;

            C:路径(path),通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;"content://com.bing.provider.myprovider/tablename";

            D:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部;"content://com.bing.provider.myprovider/tablename/#" #表示数据id。

        PS

           路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:

        1、要操作person表中id为10的记录,可以构建这样的路径:/person/10

        2、要操作person表中id为10的记录的name字段,person/10/name

        3、要操作person表中的所有记录,可以构建这样的路径:/person

        4、要操作xxx表中的记录,可以构建这样的路径:/xxx

        5、当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下: 要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name

        6、如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")

    4.2.2 UriMatcher类使用介绍

            因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris 。掌握它们的使用,会便于我们的开发工作。

            UriMatcher类用于匹配Uri,它的用法如下:

            首先第一步把你需要匹配Uri路径全部给注册上,如下:

    //常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码

    UriMatcher  sMatcher = newUriMatcher(UriMatcher.NO_MATCH);

    //如果match()方法匹配content://com.bing.procvide.personprovider/person路径,返回匹配码为1

    sMatcher.addURI("com.bing.procvide.personprovider", "person", 1);//添加需要匹配uri,如果匹配就会返回匹配码

    //如果match()方法匹配content://com.bing.provider.personprovider/person/230路径,返回匹配码为2

    sMatcher.addURI("com.bing.provider.personprovider", "person/#", 2);//#号为通配符

    switch(sMatcher.match(Uri.parse("content://com.ljq.provider.personprovider/person/10"))){

          case 1

            break;

          case 2

            break;

          default://不匹配

            break;

    }

            注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://com.ljq.provider.personprovider/person路径,返回的匹配码为1

    4.2.3 ContentUris类使用介绍

            ContentUris类用于操作Uri路径后面的ID部分,它有两个比较实用的方法:

        withAppendedId(uri, id)用于为路径加上ID部分:

    Uri uri = Uri.parse("content://com.bing.provider.personprovider/person");

    Uri resultUri = ContentUris.withAppendedId(uri, 10);

    //生成后的Uri为:content://com.bing.provider.personprovider/person/10

    parseId(uri)方法用于从路径中获取ID部分:

    Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person/10");

    long personid = ContentUris.parseId(uri);    //获取的结果为:10

    4.3 ContentProvider示例代码

        自定义一个ContentProvider,来实现内部原理

       步骤:

     1、定义一个CONTENT_URI常量(里面的字符串必须是唯一)

       Public static final Uri CONTENT_URI =Uri.parse("content://com.WangWeiDa.MyContentprovider");

       如果有子表,URI为:

       Publicstatic final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentProvider/users");

     2、定义一个类,继承ContentProvider

       Publicclass MyContentProvider extends ContentProvider

     3、实现ContentProvider的所有方法(query、insert、update、delete、getType、onCreate)

       package com.WangWeiDa.cp;

       import java.util.HashMap;

       import com.WangWeiDa.cp.MyContentProviderMetaData.UserTableMetaData;

       import com.WangWeiDa.data.DatabaseHelp;

       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.database.sqlite.SQLiteQueryBuilder;

       import android.net.Uri;

       import android.text.TextUtils;

       public class MyContentProvider extends ContentProvider {

           //访问表的所有列

            public static final intINCOMING_USER_COLLECTION = 1;

            //访问单独的列

            public static final int INCOMING_USER_SINGLE =2;

            //操作URI的类

            public static final UriMatcher uriMatcher;

            //为UriMatcher添加自定义的URI

            static{

                uriMatcher = newUriMatcher(UriMatcher.NO_MATCH);

                uriMatcher.addURI(MyContentProviderMetaData.AUTHORITIES,"/user",

        INCOMING_USER_COLLECTION);

                uriMatcher.addURI(MyContentProviderMetaData.AUTHORITIES,"/user/#",

        INCOMING_USER_SINGLE);

        }

        private DatabaseHelp dh;

        //为数据库表字段起别名

        public static HashMap userProjectionMap;

        static

        {

            userProjectionMap = new HashMap();

            userProjectionMap.put(UserTableMetaData._ID, UserTableMetaData._ID);

            userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME);

        }

        /**

        *删除表数据

        */

        @Override

        public int delete(Uri uri, String selection, String[] selectionArgs) {

            System.out.println("delete");

            //得到一个可写的数据库

            SQLiteDatabase db = dh.getWritableDatabase();

            //执行删除,得到删除的行数

            int count = db.delete(UserTableMetaData.TABLE_NAME, selection, selectionArgs);

            return count;

        }

        /**

        *数据库访问类型

        */

        @Override

        public String getType(Uri uri) {

            System.out.println("getType");

            //根据用户请求,得到数据类型

            switch (uriMatcher.match(uri)) {

                case INCOMING_USER_COLLECTION:

                    return MyContentProviderMetaData.UserTableMetaData.CONTENT_TYPE;

                case INCOMING_USER_SINGLE:

                    return MyContentProviderMetaData.UserTableMetaData.CONTENT_TYPE_ITEM;

                default:

                    throw newIllegalArgumentException("UnKnown URI" + uri);

            }

        }

        /**

        *插入数据

        */

        @Override

        public Uri insert(Uri uri, ContentValuesvalues) {

            //得到一个可写的数据库

            SQLiteDatabase db = dh.getWritableDatabase();

            //向指定的表插入数据,得到返回的Id

            long rowId = db.insert(UserTableMetaData.TABLE_NAME, null, values);

            if(rowId > 0){    //判断插入是否执行成功

                //如果添加成功,利用新添加的Id和

                Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);

                //通知监听器,数据已经改变

                getContext().getContentResolver().notifyChange(insertedUserUri, null);

                return insertedUserUri;

            }

            return uri;

        }

        /**

        *创建ContentProvider时调用的回调函数

        */

        @Override

        public boolean onCreate() {

            System.out.println("onCreate");

            //得到数据库帮助类

            dh = newDatabaseHelp(getContext(), MyContentProviderMetaData.DATABASE_NAME);

            return false;

        }

        /**

        *查询数据库

        */

        @Override

        public Cursor query(Uri uri, String[]projection, String selection, String[] selectionArgs, String sortOrder) {

            //创建一个执行查询的Sqlite

            SQLiteQueryBuilder qb = newSQLiteQueryBuilder();

            //判断用户请求,查询所有还是单个

            switch(uriMatcher.match(uri)){

                case INCOMING_USER_COLLECTION:

                    //设置要查询的表名

                    qb.setTables(UserTableMetaData.TABLE_NAME);

                    //设置表字段的别名

                    qb.setProjectionMap(userProjectionMap);

                    break;

                case INCOMING_USER_SINGLE:

                    qb.setTables(UserTableMetaData.TABLE_NAME);

                    qb.setProjectionMap(userProjectionMap);

                    //追加条件,getPathSegments()得到用户请求的Uri地址截取的数组,get(1)得到去掉地址中/以后的第二个元素

                    qb.appendWhere(UserTableMetaData._ID + "=" + uri.getPathSegments().get(1));

                    break;

                }

                //设置排序

                String orderBy;

                if(TextUtils.isEmpty(sortOrder)){

                    orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;

                }

                else{

                    orderBy = sortOrder;

                }

                //得到一个可读的数据库

                SQLiteDatabase db = dh.getReadableDatabase();

                //执行查询,把输入传入

                Cursor c = qb.query(db, projection, selection,selectionArgs, null, null, orderBy);

                //设置监听

                c.setNotificationUri(getContext().getContentResolver(), uri);

                return c;

            }

            /**

            *更新数据库

            */

            @Override

            public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

                System.out.println("update");

                //得到一个可写的数据库

                SQLiteDatabase db = dh.getWritableDatabase();

                //执行更新语句,得到更新的条数

                int count =db.update(UserTableMetaData.TABLE_NAME, values, selection, selectionArgs);

                return count;

            }

       }

       4、在AndroidMinifest.xml中进行声明

    <android:name = ".cp.MyContentProvider"

    android:authorities = "com.WangWeiDa.cp.MyContentProvider" />

       //为ContentProvider提供一个常量类MyContentProviderMetaData.java

       package com.WangWeiDa.cp;

       import android.net.Uri;

       import android.provider.BaseColumns;

       public class MyContentProviderMetaData {

           //URI的指定,此处的字符串必须和声明的authorities一致

            public static final String AUTHORITIES = "com.wangweida.cp.MyContentProvider";

            //数据库名称

            public static final String DATABASE_NAME = "myContentProvider.db";

            //数据库的版本

            public static final int DATABASE_VERSION = 1;

            //表名

            public static final String USERS_TABLE_NAME = "user";

            public static final class UserTableMetaData implements BaseColumns{

                //表名

                public static final String TABLE_NAME ="user";

                //访问该ContentProvider的URI

                public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");

                //该ContentProvider所返回的数据类型的定义

                public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.myprovider.user";

                public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.myprovider.user";

                //列名

                public static final String USER_NAME = "name";

                //默认的排序方法

                public static final String DEFAULT_SORT_ORDER = "_id desc";

            }

       }

    5 参考链接

    Android--sharepreference总结

    http://blog.csdn.net/wulianghuan/article/details/8501063

    Android中SharePreference的使用

    http://www.cnblogs.com/wanqieddy/archive/2012/04/07/2436299.html

    android--存储之SharePreference

    http://blog.csdn.net/jie1991liu/article/details/8665479

    Android中SQLite应用详解

    http://blog.csdn.net/liuhe688/article/details/6715983/

    最受欢迎的5个Android ORM框架

    http://www.codeceo.com/article/5-android-orm-framework.html

    Android ContentProvider的介绍(很详细)

    http://xiechengfa.iteye.com/blog/1415829

    android四大组件--ContentProvider详解

    http://www.2cto.com/kf/201404/296974.html

    ContentProvider总结(Android)

    http://blog.csdn.net/chuyuqing/article/details/39995607

    相关文章

      网友评论

      本文标题:【Android开发基础系列】数据持久化专题

      本文链接:https://www.haomeiwen.com/subject/tkojkftx.html