基于SQLiteDatabase使用ContentProvider共享数据

来源:互联网 发布:淘宝电脑直通车怎么找 编辑:程序博客网 时间:2024/06/06 02:53

当我们的的应用数据需要给外界访问的时候,就需要跨进程通信。在Android中,如果进行数据共享,ContentProvider是最好的选择,它的实现很简单,并且底层封装好了Binder对象用于处理远程连接。在另一个应用中,如果想要共享当前应用的数据,只需要获取一个ContentResolver对象,然后通过Uri匹配就可以与当前应用的ContentProvider建立连接,之后对ContentResolver进行增删改查操作其实就是对远程数据进行增删改查操作。接下来将介绍SQLiteOpenHelper,SQLiteDatabase,ContentProvider,ContentResolver,UriMather类,来了解整个共享数据的过程。


(1)SQLiteOpenHelper



SQLiteOpenHelper是一个用于数据库创建和版本控制(即当前数据库版本的改变会导致系统对数据库的重新安装)的类。通常作为一个SQLiteOpenHelper的子类,只需要实现onCreate和onUpgrade方法就可以了。前者会在我们第一次调用数据库的时候判断当前数据库是否存在,如果存在就打开它,如果不存在就创建它。后者可以通过判断当前的数据库的版本号来确定是否需要更新当前的数据库。同时,这个类可以让ContentProvide延迟打开或者更新数据库,从而避免在应用程序启动时因为数据库更新或者启动占用过多的时间导致阻塞。


SQLiteOpenHelper的构造方法:
   public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {        this(context, name, factory, version, null);    }
这个方法用来创建一个用于打开,创建,管理数据库的SQLiteOpenHelper对象。需要注意的是,这个方法会快速的执行但是并不会立即创建数据库,只有在开发者第一次调用的getWriteableDatabase或者getReadbleDatabase方法时才会打开或者创建数据库。参数解析如下:

①:name:用于保存数据库的文件名称,可以传值null,此时会在内存中临时创建一个数据库。
②:factory:CourseFactory(一个用于在查询数据的时候返回Cursor的接口)是用于创建Cursor对象,通常情况下传值null就可以,此时系统会用默认的CourseFactory给我们创建Cursor对象。
③:version:表示当前数据库的版本号,如果当前数据库的的版本号太旧了,会调用onUpgrade方法进行数据库更新。

SQLiteOpenHelper用于打开数据库的方法:

①:getWriteableDatabase:用于获取一个可进行读写操作的数据库操作对象,在第一次调用这个方法打开数据库的时候,onCreate,onUpgrade会被调用。一旦数据库操作对象成功获取,数据库就会在内存中有缓存,所以后续可以多次调用此方法进行读写操作,但要注意在不需要使用的时候必须调用close方法将其关闭。同时,因为此方法会回调onCreate、onUpdrade方法,所以不要在主线程中调用这个方法来获取数据库操作对象,因为数据库的获取和更新会耗费很多时间。

②:getReadableDatabase:用于获取一个只能进行读取数据的数据库操作对象,如果在打开了只能读取数据的数据库操作对象后,调用了getWriteableDatabase对象,那么原来的只读取对象会被关闭,而新的可读写数据库的操作对象将被返回。同样的,此方法和上面的方法一样,不应该在主线程调用。

SQLiteOpenHelper的子类必须实现的方法:

①:onCreate:这是一个必须实现的抽象方法,在第一次创建数据库的时候会被调用,一般用于创建表并初始化一些表的数据。

②:onUpdrade:这是一个必须实现的抽象方法,用于在需要更新数据库的时候调用,在这里应该执行一些如添加表删除表的操作。


(2)SQLiteDatabase



SQLiteDatabase封装了很多用于管理SQLite database的方法,比如增删改查,执行Sql命令等操作。其中最重要的是事务管理和执行增删改查的操作。

1、事务管理方面有三个重要的方法,事务一旦开始执行,直到执行完毕,只有两种结果,一是成功执行并提交,而是执行出错进行回滚到原来的状态:

①:beginTransaction:开始执行一个事务。

②:setTransactionSuccessful:标志当前的事务执行成功。

③:endTransaction:结束一个事务。

使用上述方法是有要点的,典型的使用方法如下:

db.beginTransaction();     *   try {     *     ...     *     db.setTransactionSuccessful();     *   } finally {     *     db.endTransaction();     *   }

其中要注意的是,如果没有调用setTransactionSuccessful方法的话,当前的事务操作将会回滚。在调用了setTransectionSuccessful方法之后,就不应该再做任何有关于数据库的操作,在setTransactionSuccessful方法和endTransation方法之间应该只执行一些简单的无关数据库的操作,同时在此期间发生的错误不会导致事务的回滚。

2、增删该查操作及各参数解析:

①:查询:

public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy)

参数解析:
table:当前查询的表名称。
columns:需要返回列数据,当传值为null时,表示需要返回所有的列数据。为了减少不必要的耗时查询,强烈建议只获取需要用到的数据。
selection:查询条件。比如:where id=?。
selectionArgs:这个数组会与selection查询条件里面的?对应起来,来替代?的真实值。
groupBy:声明根据哪些条件进行分组,传值null将不对结果进行分组。
having:声明包含在分组列表里面的数据的条件。传值null将对所有的结果进行分组。
orderBy:对结果的排序条件,desc表示降序,asc表示升序。

例子:db.query("user",null,"where name =?",{"hy"},null,null,null);

如果对数据库操作命令比较熟悉,可以用下面的方法进行查询数据:

public Cursor rawQuery(String sql,String[] selectionArgs)

其中sql表示slq语句的查询命令,但是其中的查询条件的值用?代替,在语句结尾不可以用;结束。每个?对应的值在selectionArgs参数里面。

②:插入:

public long insert(String table,String nullColumnHack,ContentValues values)

参数解析:
nullColumnHack:正常情况下传null值就好。这里的主要作用是,在values是null的时候,会将nullColumnHack指定的值设为null,,这样就可以在数据库里添加一条空数据了。
values:是一个键值对形式的值,里面的实现使用了HashMap。用于保存需要修改的数据。比如需要修改name为hy,就调用values.put("name","hy"),将需要修改的数据保存起来。


③:删除:

public int delete(String table,String whereClause,String[] whereArgs)
参数解析:
whereClause:希望删除的数据满足的删除条件。
whereArgs:删除条件重whereClause中?对应的值。

④:更新:

public int update(String table,ContentValues values,String whereClause,String[] whereArgs)

各参数和上面讲解的一样,不再解析。

⑤:不需要返回结果的查询(适合熟悉sql语句的开发者使用):

public void execSQL(String sql)

sql为sql语句,因为此方法不会返回任何结果,所以不适用查询或者任何需要返回结果的事务操作。

⑥:删除表:

public static boolean deleteDatabase(File file)

删除指定文件的表数据,包括任何该表的日志文件和辅助文件。


(3)ContentProvider

ContentProvider是android四大组件之一,封装了很多数据和方法并通过ContentResolver来给别的应用提供支持。只有当需要在多个应用共享数据时才需要使用ContentProvider。如果只是在应用内使用数据的话,只用SQLiteDatabase方法就可以了。系统是根据Uri来区分当前的请求需要启动哪个ContentProvider来处理当前的请求,在ContentProvider中,最重要的六个方法是:
①:onCreate:初始化ContentProvider。
②:query:查询当前ContentProvider的数据。
③:insert:给当前的ContentProvider添加数据。
④:delete:在当前的ContentProvider删除数据。
⑤:update:修改当前的ContentProvider的数据。
⑥:getType:获取当前COntentProvider提供的数据的媒体类型(MIME Type)。
以上六个方法中,onCreate是运行在运行在主线程中的,所以不要进行耗时操作,增删改查是可并发运行的,其实底层是Binder实现的跨进程通信,所以这些方法都是运行在BInder线程池里边。在调用这些方法的时候我们要保证操作是线程安全的。因为ContentProvider底层封装好了跨进程通信,所以远程客户端通过ContentResolver请求数据时,作为开发者不需要关心跨进程通信的细节,系统会将ContentResolver和对应的ContentProvider关联起来。ContentProvider必须在AndroidManifest.xml文件中声明,因为在使用过程中,使系统自动将ContentResolver和ContentProvider关联起来的,所以我们不需要去实例化ContentProvider。任何我们需要初始化的数据都应该放在onCreate方法里面。在ContentProvider中有一个Transport类,一个Binder的子类,是专门用于处理远程通信的。因此我们不需要关心跨进程通信的细节方面了。
ContentProvider中的增删改查方法:
public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values); public abstract int delete(@NonNull Uri uri, @Nullable String selection,            @Nullable String[] selectionArgs); public abstract int update(@NonNull Uri uri, @Nullable ContentValues values,            @Nullable String selection, @Nullable String[] selectionArgs);public abstract @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,            @Nullable String selection, @Nullable String[] selectionArgs,            @Nullable String sortOrder);

上述方法中,除了Uri之外,各参数的意思很签名讲述的一样,Uri是远程应用请求的Uri,里面包含了权限信息和路径信息,具体的后面解析。在ContentProvider中,通常是使用UriMather方法来匹配一个Uri所对应的请求。下面将讲解UriMather。

(4)UriMather



UriMather是ContentProvider中专门用于匹配uri的类,通过它可以解析出ContentResolver所请求的方法。为了使用它,首先需要在UriMather对象里面建立Uri基本的匹配信息。比如:


 private static final int PEOPLE = 1;    private static final int PEOPLE_ID = 2;    private static final int PEOPLE_PHONES = 3;    private static final int PEOPLE_PHONES_ID = 4;    private static final int PEOPLE_CONTACTMETHODS = 7;    private static final int PEOPLE_CONTACTMETHODS_ID = 8;    private static final int DELETED_PEOPLE = 20;    private static final int PHONES = 9;    private static final int PHONES_ID = 10;    private static final int PHONES_FILTER = 14;    private static final int CONTACTMETHODS = 18;    private static final int CONTACTMETHODS_ID = 19;    private static final int CALLS = 11;    private static final int CALLS_ID = 12;    private static final int CALLS_FILTER = 15;    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);    static    {        sURIMatcher.addURI("contacts", "people", PEOPLE);        sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);        sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);        sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);        sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS);        sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);        sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE);        sURIMatcher.addURI("contacts", "phones", PHONES);        sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER);        sURIMatcher.addURI("contacts", "phones/#", PHONES_ID);        sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS);        sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID);        sURIMatcher.addURI("call_log", "calls", CALLS);        sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER);        sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);    }

之后调用UriMather.match方法就可以获得对应的code(addURI的第三个参数)静态常量。比如:

 public String getType(Uri url)    {        int match = sURIMatcher.match(url);        switch (match)        {            case PEOPLE:                return "vnd.android.cursor.dir/person";            case PEOPLE_ID:                return "vnd.android.cursor.item/person";... ...                return "vnd.android.cursor.dir/snail-mail";            case PEOPLE_ADDRESS_ID:                return "vnd.android.cursor.item/snail-mail";            default:                return null;        }    }


(5)ContentResolver



这是一个抽象类,作为开发者不需要去实现它,因为系统已经给我们实现好了,我们只要使用getContentResolver方法就可以获得一个ContentResolver对象,之后就可以对他进行增删该查操作。由于这些方法和ContentProvider几乎一样,所以此处不再解析。

下面通过一个例子来讲述更加细节的东西。例子讲述了从另一个进程查询当前进程的user和book信息。

首先是建立数据库版本管理的类SqliteManager,代码如下:

package com.cw.contentprovider;import android.annotation.TargetApi;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;import android.os.Build;import android.util.Log;import java.io.File;/** * Created by Myy on 2016/7/19. */public class SqliteManager extends SQLiteOpenHelper {    private final static String DATABASE_NAME = "test.db";    private final static String USER_NAME = "user", BOOK_NAME = "book";    private final static String CREATE_TABLE_USER = "create table if not exists " + USER_NAME + "(_id integer primary key," + "name text);";    private final static String CREATE_TABLE_BOOK = "create table if not exists " + BOOK_NAME + "(_id integer primary key," + "name text);";    public SqliteManager(Context context) {        super(context.getApplicationContext(), DATABASE_NAME, null, 1);    }    @Override    public void onCreate(SQLiteDatabase db) {        try {            db.execSQL(CREATE_TABLE_BOOK);            db.execSQL(CREATE_TABLE_USER);        } catch (Exception err) {            Log.i("exception", err.getMessage());        }    }    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)    @Override    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        if (newVersion > oldVersion) {            db.execSQL("delete from user");            db.execSQL("delete from book");            Log.i("onUpgrade","更新");        }    }}

然后建立执行增删该查的数据库管理者Databasemanager:

package com.cw.contentprovider;import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;/** * Created by Myy on 2016/7/20. */public class DatabaseManager {    private static SqliteManager manager;    private DatabaseManager() {    }    private static class DatabaseManagerHolder {        private static DatabaseManager instance = new DatabaseManager();    }    public static DatabaseManager getInstance(Context context) {        if (manager == null) {            manager = new SqliteManager(context.getApplicationContext());        }        return DatabaseManagerHolder.instance;    }    public Cursor query(String table, String[] columns, String selection, String[] selectionArgs            , String groupBy, String having, String orderBy) {        SQLiteDatabase sb = getDatabase();        Cursor cursor = sb.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);        return cursor;    }    public int insert(String table, String nullColumnHack, ContentValues values) {        SQLiteDatabase sb = getDatabase();        long result = sb.insert(table, nullColumnHack, values);        return (int) result;    }    private SQLiteDatabase getDatabase() {        return manager.getWritableDatabase();    }}

然后建立一个ContentProvider的子类,用于跨进程共享数据:

package com.cw.contentprovider;import android.content.ContentProvider;import android.content.ContentValues;import android.content.UriMatcher;import android.database.Cursor;import android.net.Uri;import android.support.annotation.Nullable;import android.util.Log;/** * Created by Myy on 2016/7/20. */public class TestContentProvider extends ContentProvider {    private final static int USER = 0, BOOK = 1;    private final static String authority = "com.hy.blog.contentProvider";    private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);    static {        matcher.addURI(authority, "user", USER);        matcher.addURI(authority, "book", BOOK);    }    private DatabaseManager manager;    @Override    public boolean onCreate() {        manager = DatabaseManager.getInstance(getContext());        Log.i("onCreate", Thread.currentThread().getName());        return false;    }    @Nullable    @Override    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {        Log.i("query", Thread.currentThread().getName());        String table = getTableName(uri);        if (table != null)            return manager.query(table, projection, selection, selectionArgs, null, null, sortOrder);        return null;    }    private String getTableName(Uri uri) {        int code = matcher.match(uri);        switch (code) {            case USER:                return "user";            case BOOK:                return "book";            default:                return null;        }    }    @Nullable    @Override    public String getType(Uri uri) {        return null;    }    @Nullable    @Override    public Uri insert(Uri uri, ContentValues values) {        Log.i("insert", Thread.currentThread().getName());        String table = getTableName(uri);        manager.insert(table, null, values);        return uri;    }    @Override    public int delete(Uri uri, String selection, String[] selectionArgs) {        return 0;    }    @Override    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {        return 0;    }}

provider必须注册。如下:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.cw.contentprovider">    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:supportsRtl="true"        android:theme="@style/AppTheme">        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <provider            android:name=".TestContentProvider"            android:authorities="com.hy.blog.contentProvider"            android:permission="com.hy.BLOG"            android:process=":remote" />    </application></manifest>

重点关注provider节点,其中authorities是表示当前provider能够处理的权限,当一个Uri只有含有这部分信息时,才会被当前provider进行解析。permission(provider有读权限和写权限,这里不再阐述)表示访问权限,当使用另一个应用访问provider的时候,在另一个应用必须声明此权限。process用于指定proivder的运行进程,在这里目的是模拟跨进程共享信息。

最后进行数据的访问:

package com.cw.contentprovider;import android.content.ContentResolver;import android.content.ContentValues;import android.database.Cursor;import android.net.Uri;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;public class MainActivity extends AppCompatActivity {    private Uri bookUri, userUri;    private ContentResolver resolver;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        bookUri = Uri.parse("content://com.hy.blog.contentProvider/book");        userUri = Uri.parse("content://com.hy.blog.contentProvider/user");        resolver = getContentResolver();        new Thread(new Runnable() {            @Override            public void run() {                Log.i("mainActivity", Thread.currentThread().getName());                insert();                query();            }        }).start();    }    private void query() {        Cursor userCursor = resolver.query(userUri, null, null, null, null);        while (userCursor.moveToNext()) {            int id = userCursor.getInt(userCursor.getColumnIndex("_id"));            String name = userCursor.getString(userCursor.getColumnIndex("name"));            Log.i("user", "id:" + id + ",name:" + name);        }        userCursor.close();        Cursor bookCursor = resolver.query(bookUri, null, null, null, null);        while (bookCursor.moveToNext()) {            int id = bookCursor.getInt(bookCursor.getColumnIndex("_id"));            String name = bookCursor.getString(bookCursor.getColumnIndex("name"));            Log.i("user", "id:" + id + ",name:" + name);        }        bookCursor.close();    }    private void insert() {        ContentValues userValues1 = new ContentValues();        userValues1.put("_id", 1);        userValues1.put("name", "hy");        ContentValues userValues2 = new ContentValues();        userValues2.put("_id", 2);        userValues2.put("name", "hd");        resolver.insert(userUri, userValues1);        resolver.insert(userUri, userValues2);        Log.i("user", "insert success");        ContentValues bookValues1 = new ContentValues();        bookValues1.put("_id", 222);        bookValues1.put("name", "hlm");        ContentValues bookValues2 = new ContentValues();        bookValues2.put("_id", 333);        bookValues2.put("name", "shz");        resolver.insert(bookUri, bookValues1);        resolver.insert(bookUri, bookValues2);        Log.i("book", "insert success");    }}

再看看打印日志:

07-20 12:59:39.375 5115-5425/com.cw.contentprovider I/mainActivity: Thread-13439
07-20 12:59:39.609 5427-5427/com.cw.contentprovider:remote I/onCreate: main
07-20 12:59:39.613 5427-5448/com.cw.contentprovider:remote I/insert: Binder_1
07-20 12:59:39.645 5427-5449/com.cw.contentprovider:remote I/insert: Binder_2
07-20 12:59:39.650 5115-5425/com.cw.contentprovider I/user: insert success
07-20 12:59:39.657 5427-5448/com.cw.contentprovider:remote I/insert: Binder_1
07-20 12:59:39.663 5427-5449/com.cw.contentprovider:remote I/insert: Binder_2
07-20 12:59:39.668 5115-5425/com.cw.contentprovider I/book: insert success
07-20 12:59:39.674 5427-5448/com.cw.contentprovider:remote I/query: Binder_1
07-20 12:59:39.676 5115-5425/com.cw.contentprovider I/user: id:1,name:hy
07-20 12:59:39.676 5115-5425/com.cw.contentprovider I/user: id:2,name:hd
07-20 12:59:39.678 5427-5448/com.cw.contentprovider:remote I/query: Binder_1
07-20 12:59:39.680 5115-5425/com.cw.contentprovider I/book: id:222,name:hlm
07-20 12:59:39.680 5115-5425/com.cw.contentprovider I/book: id:333,name:shz


我们可以看到,跨进程调用contentProvider的时候,执行增删改查的任务的线程都是从Binder线程池里面取出来的。另外,onCreate是TestContentProvider的onCreate的日志,可以得出此方法运行在主线程。

我们尝试着把SqliteManager的构造方法改成下面那样:

public SqliteManager(Context context) {        super(context.getApplicationContext(), DATABASE_NAME, null, 2);//把1改成2    }

我们运行程序查看log日志:

07-20 13:05:10.683 9180-9180/com.cw.contentprovider:remote I/onCreate: main
07-20 13:05:10.686 9180-9203/com.cw.contentprovider:remote I/insert: Binder_2
07-20 13:05:10.693 9180-9203/com.cw.contentprovider:remote I/onUpgrade: 更新
07-20 13:05:10.710 9180-9202/com.cw.contentprovider:remote I/insert: Binder_1
07-20 13:05:10.716 8979-9177/com.cw.contentprovider I/user: insert success
07-20 13:05:10.717 9180-9203/com.cw.contentprovider:remote I/insert: Binder_2
07-20 13:05:10.721 9180-9202/com.cw.contentprovider:remote I/insert: Binder_1
07-20 13:05:10.727 8979-9177/com.cw.contentprovider I/book: insert success
07-20 13:05:10.727 9180-9203/com.cw.contentprovider:remote I/query: Binder_2
07-20 13:05:10.730 8979-9177/com.cw.contentprovider I/user: id:1,name:hy
07-20 13:05:10.730 8979-9177/com.cw.contentprovider I/user: id:2,name:hd
07-20 13:05:10.731 9180-9203/com.cw.contentprovider:remote I/query: Binder_2
07-20 13:05:10.733 8979-9177/com.cw.contentprovider I/book: id:222,name:hlm
07-20 13:05:10.733 8979-9177/com.cw.contentprovider I/book: id:333,name:shz

可以发现onUpgrade被回调了,原因是我们将数据库的版本升级了。

---------文章写自:HyHarden---------

--------博客地址:http://blog.csdn.net/qq_25722767/article/details/51895992-----------




1 0
原创粉丝点击