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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 一.菜单Menu 1.OptionsMenu 选项菜单 也叫系统菜单,右上角的三点 (1)高版本的菜单 ...
    chaohx阅读 930评论 0 7
  • 2017年5月17日 Kylin_Wu 标注(★☆)为考纲明确给出考点(必考) 常见手机系统(★☆) And...
    Azur_wxj阅读 1,767评论 0 10
  • Android_7_数据存数方式 使用SharedPreferences存储数据 获取SharedPreferen...
    icechao阅读 345评论 0 2
  • 抽象方法boolean test(T t); 该方法对传入的参数进行验证,满足条件返回true,否则返回false...
    ted005阅读 1,903评论 0 50
  • 《天龙八部》乔峰突然一声怒喝:“滚出来!”声震屋瓦,梁上灰尘簌簌而落。群雄均是耳中雷鸣,心跳加剧。乔峰一招打出,人...
    高大杰阅读 196评论 0 0