Android之内容提供器Content Provider详解(二)

上一篇 Android之内容提供器Content Provider详解(一)讲解了Content Provider之访问其他程序中的数据,本篇继续讲解创如何建自己的内容提供器

本博文是《第一行代码 Android》的读书笔记/摘录。

三、创建自己的内容提供器

在上一篇中,我们学习了如何在自己的程序中访问其他应用程序的数据。总体来说思路还是非常简单的,只需要获取到该应用程序的内容URI,然后借助ContentResolver 进行CRUD 操作就可以了。可是,那些提供外部访问接口的应用程序都是如何实现这种功能的呢?它们又是怎样保证数据的安全性,使得隐私数据不会泄漏出去?

(一)创建内容提供器的步骤

前面已经提到过,如果想要实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以通过新建一个类去继承ContentProvider 的方式来创建一个自己的内容提供器。新建MyContentProvider继承自ContentProvider,代码如下所示:

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;

public class MyContentProvider extends ContentProvider {
    public MyContentProvider() {
    }

    @Override
    public boolean onCreate() {

        return false;
    }

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

        return null;
    }

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

        return 0;
    }

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

        return 0;
    }

    @Override
    public String getType(Uri uri) {

        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {

        return null;
    }
}

ContentProvider 类中有六个抽象方法,我们在使用子类继承它的时候,需要将这六个方法全部重写。
(1) onCreate()
初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回true 表示内容提供器初始化成功,返回false 则表示失败。注意,只有当存在ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。

(2)query()
从内容提供器中查询数据。使用uri 参数来确定查询哪张表,projection 参数用于确定查询哪些列,selection 和selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在Cursor 对象中返回。

(3) insert()
向内容提供器中添加一条数据。使用uri 参数来确定要添加到的表,待添加的数据保存在values 参数中。添加完成后,返回一个用于表示这条新记录的URI。

(4)update()
更新内容提供器中已有的数据。使用uri 参数来确定更新哪一张表中的数据,新数据保存在values 参数中,selection 和selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。

(5)delete()
从内容提供器中删除数据。使用uri 参数来确定删除哪一张表中的数据,selection和selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。

(6)getType()
根据传入的内容URI 来返回相应的MIME 类型。

可以看到,几乎每一个方法都会带有Uri 这个参数,这个参数也正是调用ContentResolver的增删改查方法时传递过来的。而现在,我们需要对传入的Uri 参数进行解析,从中分析出调用方期望访问的表和数据。
一个标准的内容URI 写法是这样的:

content://com.example.app.provider/table1

这就表示调用方期望访问的是com.example.app 这个应用的table1 表中的数据。除此之外,我们还可以在这个内容URI 的后面加上一个id,如下所示:

content://com.example.app.provider/table1/1

这就表示调用方期望访问的是com.example.app 这个应用的table1 表中id 为1 的数据。

内容URI 的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以id 结尾就表示期望访问该表中拥有相应id 的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下:
(1) *:表示匹配任意长度的任意字符;
(2) #:表示匹配任意长度的数字。

所以,一个能够匹配任意表的内容URI 格式就可以写成:

content://com.example.app.provider/*

而一个能够匹配table1 表中任意一行数据的内容URI 格式就可以写成:

content://com.example.app.provider/table1/#

接着,我们再借助UriMatcher 这个类就可以轻松地实现匹配内容URI 的功能。UriMatcher中提供了一个addURI()方法:

public void addURI(String authority, String path, int code);

这个方法接收三个参数,可以分别把权限、路径和一个自定义代码传进去。这样,当调用UriMatcher 的match()方法时,就可以将一个Uri 对象传入,返回值是某个能够匹配这个Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。修改MyContentProvider中的代码,如下所示:

public class MyContentProvider extends ContentProvider {
    public static final int TABLE1_DIR = 0;
    public static final int TABLE1_ITEM = 1;
    public static final int TABLE2_DIR = 2;
    public static final int TABLE2_ITEM = 3;
    private static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider ", "table1/#", TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider ", "table2", TABLE2_ITEM);
        uriMatcher.addURI("com.example.app.provider ", "table2/#", TABLE2_ITEM);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                // 查询table1表中的所有数据
                break;
            case TABLE1_ITEM:
                // 查询table1表中的单条数据
                break;
            case TABLE2_DIR:
                // 查询table2表中的所有数据
                break;
            case TABLE2_ITEM:
                // 查询table2表中的单条数据
                break;
            default:
                break;
        }
        ......
    }
    ......
}

可以看到,MyProvider 中新增了四个整型常量:
(1)TABLE1_DIR 表示访问table1 表中的所有数据;
(2)TABLE1_ITEM 表示访问table1 表中的单条数据;
(3)TABLE2_DIR 表示访问table2 表中的所有数据;
(4)TABLE2_ITEM 表示访问table2 表中的单条数据。

接着在静态代码块里我们创建了UriMatcher 的实例,并调用addURI()方法,将期望匹配的内容URI 格式传递进去,注意这里传入的路径参数是可以使用通配符的。

然后当query()方法被调用的时候,就会通过UriMatcher 的match()方法对传入的Uri 对象进行匹配,如果发现UriMatcher 中某个内容URI 格式成功匹配了该Uri 对象,则会返回相应的自定义代码,然后我们就可以判断出调用方期望访问的到底是什么数据了。

上述代码只是以query()方法为例做了个示范,其实insert()、update()、delete()这几个方法的实现也是差不多的,它们都会携带Uri 这个参数,然后同样利用UriMatcher 的match()方法判断出调用方期望访问的是哪张表,再对该表中的数据进行相应的操作就可以了。

除此之外,还有一个方法你会比较陌生,即getType()方法。它是所有的内容提供器都必须提供的一个方法,用于获取Uri 对象所对应的MIME 类型。一个内容URI 所对应的MIME字符串主要由三部分组分,Android 对这三个部分做了如下格式规定。
(1) 必须以vnd 开头。
(2)如果内容URI 以路径结尾,则后接android.cursor.dir/,如果内容URI 以id 结尾,则后接android.cursor.item/
(3)最后接上vnd.<authority>.<path>

所以,对于content://com.example.app.provider/table1 这个内容URI,它所对应的MIME类型就可以写成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

对于content://com.example.app.provider/table1/1 这个内容URI,它所对应的MIME 类型就可以写成:

vnd.android.cursor.item/vnd. com.example.app.provider.table1

现在我们可以继续完善MyContentProvider中的内容了,这次来实现getType()方法中的逻辑,代码如下所示:

    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider. table1";
            case TABLE1_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider. table1";
            case TABLE2_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider. table2";
            case TABLE2_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider. table2";
            default:
                break;
        }
        return null;
    }

到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用ContentResolver 来访问我们程序中的数据。那么前面所提到的,如何才能保证隐私数据不会泄漏出去呢?
其实多亏了内容提供器的良好机制,这个问题在不知不觉中已经被解决了。因为所有的CRUD 操作都一定要匹配到相应的内容URI 格式才能进行的,而我们当然不可能向UriMatcher 中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问到,安全问题也就不存在了。

(二)实现跨程序数据共享

本案例两个项目的源码下载地址如下:
http://download.csdn.net/detail/wei_zhi/9667962

我们创建一个简单的MyBook项目,有插入数据和查询的功能,然后通过内容提供器来给它加入外部访问接口。

首先来看布局文件activity_main.xml,就简单的两个按钮:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wz.mybook.MainActivity">

    <Button
        android:text="插入数据"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="74dp"
        android:id="@+id/insert_button" />

    <Button
        android:text="查询数据"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="31dp"
        android:id="@+id/query_button"
        android:layout_below="@+id/insert_button"
        android:layout_alignStart="@+id/insert_button" />

</RelativeLayout>

然后是MainActivity:

package com.wz.mybook;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private MyDatabaseHelper databaseHelper;

    private Button insertBtn,queryBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        insertBtn = (Button) findViewById(R.id.insert_button);
        insertBtn.setOnClickListener(this);
        queryBtn = (Button) findViewById(R.id.query_button);
        queryBtn.setOnClickListener(this);

        databaseHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.insert_button:
                insert();
                break;
            case R.id.query_button:
                query();
                break;
            default:
                break;
        }
    }

    /**
     * 插入数据
     */
    private void insert() {
        SQLiteDatabase db = databaseHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        //插入第一条数据
        values.put("name", "Java开发");
        values.put("author", "小明");
        values.put("pages", 454);
        values.put("price", 49.9);
        db.insert("Book", null, values);

        values.clear();
        //插入第二条数据
        values.put("name", "Android开发");
        values.put("author", "小李");
        values.put("pages", 696);
        values.put("price", 69.9);
        db.insert("Book", null, values);
    }

    /**
     * 查询数据
     */
    private void query() {
        SQLiteDatabase database = databaseHelper.getWritableDatabase();
        Cursor cursor = null;
        try {
            cursor = database.query("Book", null, null, null, null, null, null);
            while (cursor != null && cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String author = cursor.getString(cursor.getColumnIndex("author"));
                int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                double price = cursor.getDouble(cursor.getColumnIndex("price"));
                Log.d("MainActivity", "book name is " + name);
                Log.d("MainActivity", "book author is " + author);
                Log.d("MainActivity", "book pages is " + pages);
                Log.d("MainActivity", "book price is " + price);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(cursor != null){
                cursor.close();
            }
        }
    }
}

其中数据库操作类MyDatabaseHelper让其继承SQLiteOpenHelper:

package com.wz.mybook;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table Book ("
            + "id integer primary key autoincrement, "
            + "author text, "
            + "price real, "
            + "pages integer, "
            + "name text)";

    public static final String CREATE_CATEGORY = "create table Category ("
            + "id integer primary key autoincrement, "
            + "category_name text, "
            + "category_code integer)";

    public MyDatabaseHelper(Context context, String name,
                            CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Category");
        onCreate(db);
    }

}

最主要的是通过内容提供器来给它加入外部访问接口:

package com.wz.mybook;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class MyBookProvider extends ContentProvider {
    public static final int BOOK_DIR = 0;

    public static final int BOOK_ITEM = 1;

    public static final int CATEGORY_DIR = 2;

    public static final int CATEGORY_ITEM = 3;

    public static final String AUTHORITY = "com.wz.mybook.provider";

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }


    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        return true;

    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("Book", projection, "id = ?", new String[]{bookId}, null, null,
                        sortOrder);
                break;
            case CATEGORY_DIR:
                cursor = db.query("Category", projection, selection, selectionArgs, null, null,
                        sortOrder);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("Category", projection, "id = ?", new String[]{categoryId}, null,
                        null, sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                updatedRows = db.update("Book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book", values, "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                updatedRows = db.update("Category", values, selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("Category", values, "id = ?", new String[]{categoryId});
                break;
            default:
                break;
        }
        return updatedRows;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deletedRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                deletedRows = db.delete("Book", selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deletedRows = db.delete("Book", "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                deletedRows = db.delete("Category", selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deletedRows = db.delete("Category", "id = ?", new String[]{categoryId});
                break;
            default:
                break;
        }
        return deletedRows;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("Book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
                break;
            default:
                break;
        }
        return uriReturn;
    }


    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.wz.mybook.provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.wz.mybook.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.wz.mybook.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.wz.mybook.provider.category";
        }
        return null;
    }

}

我们来分析这个类:
首先在类的一开始,同样是定义了四个常量,分别用于表示访问Book 表中的所有数据、访问Book 表中的单条数据、访问Category 表中的所有数据和访问Category 表中的单条数据。然后在静态代码块里对UriMatcher 进行了初始化操作,将期望匹配的几种URI 格式添加了进去。

接下来就是每个抽象方法的具体实现了,先来看下onCreate()方法,这个方法的代码很短,就是创建了一个MyDatabaseHelper 的实例,然后返回true 表示内容提供器初始化成功,这时数据库就已经完成了创建或升级操作。

接着看一下query()方法,在这个方法中先获取到了SQLiteDatabase 的实例,然后根据传入的Uri 参数判断出用户想要访问哪张表,再调用SQLiteDatabase 的query()进行查询,并将Cursor 对象返回就好了。注意当访问单条数据的时候有一个细节,这里调用了Uri 对象的getPathSegments()方法,它会将内容URI 权限之后的部分以“/”符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第0 个位置存放的就是路径,第1 个位置存放的就是id 了。得到了id 之后,再通过selection 和selectionArgs 参数进行约束,就实现了查询单条数据的功能。

再往后就是insert()方法,同样它也是先获取到了SQLiteDatabase 的实例,然后根据传入的Uri 参数判断出用户想要往哪张表里添加数据,再调用SQLiteDatabase 的insert()方法进行添加就可以了。注意insert()方法要求返回一个能够表示这条新增数据的URI,所以我们还需要调用Uri.parse()方法来将一个内容URI 解析成Uri 对象,当然这个内容URI 是以新增数据的id 结尾的。

接下来就是update()方法了,也是先获取SQLiteDatabase 的实例,然后根据传入的Uri 参数判断出用户想要更新哪张表里的数据,再调用SQLiteDatabase 的update()方法进行更新就好了,受影响的行数将作为返回值返回。

下面是delete()方法,这里仍然是先获取到SQLiteDatabase 的实例,然后根据传入的Uri 参数判断出用户想要删除哪张表里的数据,再调用SQLiteDatabase 的delete()方法进行删除就好了,被删除的行数将作为返回值返回。

最后是getType()方法。

最后的最后,还需要将内容提供器在AndroidManifest.xml 文件中注册:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wz.mybook">

    <permission android:name="com.wz.permission.READ_CONTENTPROVIDER"/>
    <permission android:name="com.wz.permission.WRITE_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=".MyBookProvider"
            android:authorities="com.wz.mybook.provider"
            android:enabled="true"
            android:exported="true"></provider>
    </application>

</manifest>

可以看到,这里我们使用了<provider>标签来对MyBookProvider这个内容提供器进行注册,在android:name 属性中指定了该类的全名,又在android:authorities 属性中指定了该内容提供器的权限。

注意:
(1)android:exported属性非常重要。

android:exported="true"

这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互,否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。如果content provider允许其他应用调用,即允许其他进程调用,需要将该属性设置为true。

(2)在Android 6.0上需要自定义读写权限,将provider暴露出去:

    <permission android:name="com.wz.permission.READ_CONTENTPROVIDER"/>
    <permission android:name="com.wz.permission.WRITE_CONTENTPROVIDER"/>

现在MyBook这个项目就已经拥有了跨程序共享数据的功能了。

看一下效果:
1

点击【插入数据】按钮,此时,应该已经插入了两条数据,点击【查询数据】,可以看到:

10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book name is Java开发
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book author is 小明
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book pages is 454
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book price is 49.9
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book name is Android开发
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book author is 小李
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book pages is 696
10-30 09:18:32.665 23675-23675/com.wz.mybook D/MainActivity: book price is 69.9

说明OK了。

下面,我们将创建一个新项目MyProviderTest,通过这个程序去访问MyBook中的数据。

首先,编写布局文件activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="wz.com.myprovidertest.MainActivity">

    <Button
        android:text="向MyBook添加数据"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:layout_marginStart="134dp"
        android:layout_marginTop="56dp"
        android:id="@+id/add_btn"
        android:elevation="0dp" />

    <Button
        android:text="从MyBook中查询数据"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/add_btn"
        android:layout_alignStart="@+id/add_btn"
        android:layout_marginTop="31dp"
        android:id="@+id/query_btn" />

    <Button
        android:text="更新MyBook中的数据"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/update_btn"
        android:layout_below="@+id/query_btn"
        android:layout_alignStart="@+id/query_btn"
        android:layout_marginTop="36dp" />

    <Button
        android:text="删除MyBook中的数据"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="39dp"
        android:id="@+id/delete_btn"
        android:layout_below="@+id/update_btn"
        android:layout_alignStart="@+id/update_btn" />
</RelativeLayout>

布局文件很简单,里面放置了四个按钮,分别用于添加、查询、修改和删除数据的。然后修改MainActivity 中的代码,如下所示:

package wz.com.myprovidertest;

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button addBtn, queryBtn, updateBtn, deleteBtn;

    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        addBtn = (Button) findViewById(R.id.add_btn);
        queryBtn = (Button) findViewById(R.id.query_btn);
        updateBtn = (Button) findViewById(R.id.update_btn);
        deleteBtn = (Button) findViewById(R.id.delete_btn);

        addBtn.setOnClickListener(this);
        queryBtn.setOnClickListener(this);
        updateBtn.setOnClickListener(this);
        deleteBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.add_btn:
                add();
                break;
            case R.id.query_btn:
                query();
                break;
            case R.id.update_btn:
                update();
                break;
            case R.id.delete_btn:
                delete();
                break;
            default:
                break;
        }
    }

    private void add() {
        Uri uri = Uri.parse("content://com.wz.mybook.provider/book");
        ContentValues values = new ContentValues();
        values.put("name", "Python开发");
        values.put("author", "小王");
        values.put("pages", 798);
        values.put("price", 89.9);
        Uri newUri = getContentResolver().insert(uri, values);
        newId = newUri.getPathSegments().get(1);
    }

    private void query() {
        Uri uri = Uri
                .parse("content://com.wz.mybook.provider/book");
        Cursor cursor = getContentResolver().query(uri, null, null,
                null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String name = cursor.getString(cursor
                        .getColumnIndex("name"));
                String author = cursor.getString(cursor
                        .getColumnIndex("author"));
                int pages = cursor.getInt(cursor
                        .getColumnIndex("pages"));
                double price = cursor.getDouble(cursor
                        .getColumnIndex("price"));
                Log.d("MainActivity", "book name is " + name);
                Log.d("MainActivity", "book author is " + author);
                Log.d("MainActivity", "book pages is " + pages);
                Log.d("MainActivity", "book price is " + price);
            }
            cursor.close();
        }
    }

    private void update() {
        Uri uri = Uri
                .parse("content://com.wz.mybook.provider/book/" + newId);
        ContentValues values = new ContentValues();
        values.put("name", "JavaScript开发");
        values.put("pages", 1198);
        values.put("price", 129.9);
        getContentResolver().update(uri, values, null, null);
    }

    private void delete() {
        Uri uri = Uri
                .parse("content://com.wz.mybook.provider/book/" + newId);
        getContentResolver().delete(uri, null, null);
    }
}

可以看到,我们分别在这四个按钮的点击事件里面处理了增删改查的逻辑。
(1)添加数据的时候,首先调用了Uri.parse()方法将一个内容URI 解析成Uri 对象,然后把要添加的数据都存放到ContentValues 对象中,接着调用ContentResolver 的insert()方法执行添加操作就可以了。注意insert()方法会返回一个Uri 对象,这个对象中包含了新增数据的id,我们通过getPathSegments()方法将这个id 取出,稍后会用到它。

(2)查询数据的时候,同样是调用了Uri.parse()方法将一个内容URI 解析成Uri 对象,然后调用ContentResolver 的query()方法去查询数据,查询的结果当然还是存放在Cursor 对象中的。之后对Cursor 进行遍历,从中取出查询结果,并一一打印出来。

(3)更新数据的时候,也是先将内容URI 解析成Uri 对象,然后把想要更新的数据存放到ContentValues 对象中,再调用ContentResolver 的update()方法执行更新操作就可以了。注意这里我们为了不想让Book 表中其他的行受到影响,在调用Uri.parse()方法时,给内容URI的尾部增加了一个id,而这个id 正是添加数据时所返回的。这就表示我们只希望更新刚刚添加的那条数据,Book 表中的其他行都不会受影响。

(4)删除数据的时候,也是使用同样的方法解析了一个以id 结尾的内容URI,然后调用ContentResolver 的delete()方法执行删除操作就可以了。由于我们在内容URI 里指定了一个id,因此只会删掉拥有相应id 的那行数据,Book 表中的其他数据都不会受影响。

最后是AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="wz.com.myprovidertest">

    <uses-permission android:name="com.wz.permission.READ_CONTENTPROVIDER"/>
    <uses-permission android:name="com.wz.permission.WRITE_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>
    </application>

</manifest>

特别注意,Android 6.0中,之前在MyBook中,我们自定义了两个权限,所以我们需要在这里申请该权限:

    <uses-permission android:name="com.wz.permission.READ_CONTENTPROVIDER"/>
    <uses-permission android:name="com.wz.permission.WRITE_CONTENTPROVIDER"/>

好,两个项目都写完了,我们来看效果:

2

(1)点击【向MyBook添加数据】,此时数据已经本添加到MyBook中的数据库了,点击【从MyBook中查询数据】:

10-30 09:26:38.710 23948-23948/wz.com.myprovidertest D/MainActivity: book name is Python开发
10-30 09:26:38.710 23948-23948/wz.com.myprovidertest D/MainActivity: book author is 小王
10-30 09:26:38.710 23948-23948/wz.com.myprovidertest D/MainActivity: book pages is 798
10-30 09:26:38.710 23948-23948/wz.com.myprovidertest D/MainActivity: book price is 89.9

(2)点击【更新MyBook中的数据】,然后再点击【从MyBook中查询数据】:

10-30 09:27:49.304 23948-23948/wz.com.myprovidertest D/MainActivity: book name is JavaScript开发
10-30 09:27:49.304 23948-23948/wz.com.myprovidertest D/MainActivity: book author is 小王
10-30 09:27:49.304 23948-23948/wz.com.myprovidertest D/MainActivity: book pages is 1198
10-30 09:27:49.304 23948-23948/wz.com.myprovidertest D/MainActivity: book price is 129.9

(3)点击【删除MyBook中的数据】,然后再点击【从MyBook中查询数据】,发现查询不到数据。

由此可以看出,我们的跨程序共享数据功能已经成功实现了!现在不仅是MyProviderTest程序,任何一个程序只要有权限,都可以轻松访问MyBook中的数据,而且我们还丝毫不用担心隐私数据泄漏的问题。

本案例两个项目的源码下载地址如下:
http://download.csdn.net/detail/wei_zhi/9667962

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值