Android中的内容提供者

中的内容提供者

为什么需要内容提供者

为了跨程序访问数据。试想如果在App-1中创建了一个私有数据库,App-2是不能直接访问的。因为权限不够,虽然可以使用chmod 777来修改权限,然后使用SQLiteDatabase.openDatabase的静态方法,填上具体的路径和模式来访问。但这并不推荐,有没有更好的办法?官方推荐使用ContentProvider--内容提供者。

创建内容提供者

简单起见,使用以前的数据库的项目DatabaseTest,同时建立两个表book和category, onUpgrade方法实现了数据库的升级功能。onUpgrade里面强制onCreate,注意必须先删除原来的表,否则我们创建时候发现原来的表还存在就会报错。

package com.example.administrator.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
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)";

    private Context mContext;

    publicMyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        this.mContext = context;
    }

    @Override
    publicvoidonCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }

    @Override
    publicvoidonUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists book");
        db.execSQL("drop table if exists category");
        onCreate(db);
    }
}

MainActivity里面就显示下界面,省略了。

如果想让这个数据库共享,其他应用也能访问?只需新增一个内容提供者即可。

New -> Other -> ContentProvider,AS会帮我们在AndroidManifest.xml里注册好。有一个属性authorities比较重要,一般命名方式是<包名>.provider,比如com.example.cptest.provider

可以看到,内容提供者的方法和操作数据库差不多。最大的不同是操作数据库需要填上表名,而内容提供者中的方法需要填上Uri。为什么呢?因为是跨程序访问数据,多个应用的表名可能一样,这样就不知道到底访问哪个应用的数据了。Uri的格式一般如下

content://<package_name>.provider/<path>/<id>,举个例子content://com.example.databasetest.provider/book/2表示访问book表的id为2的那行数据。

甚至可以使用通配符

  • *表示匹配任意长度的任意字符
  • #表示匹配任意长度的数字

于是可以匹配任意表的URI可以写成content://com.example.databasetest.provider/*

可以匹配一个表中任意一行的URI可以写成content://com.example.databasetest.provider/book/#

package com.example.administrator.databasetest;

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;
import android.support.annotation.NonNull;

public class DatabaseProvider extends ContentProvider {
    // 0123是自定义代码,用于清楚表达我们想要访问访问数据库的哪个表或者哪一行数据
    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;
    // 和清单文件里provider的authority属性一致
    public static final String AUTHORITY = "com.example.databasetest.provider";

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;

    static {
      // NO_MATCH就是-1
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
      // 第三个参数填上自定义的代码,对应于uriMatcher.match(uri)
        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
    publicintdelete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        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:
        }
        return deletedRows;
    }

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

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        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:
        }
        return uriReturn;
    }
    // 一旦使用到内容提供者就调用此方法,并得到数据库连接的实例,返回true表示内容提供者初始化成功
    @Override
    publicbooleanonCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 19);
        return true;
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        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); // 这里path是/book/bookId,get(1)就是bookId
                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:
        }
        return cursor;
    }

    @Override
    publicintupdate(@NonNull Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        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:
        }
        return updatedRows;
    }
}

流程是这样的,一旦需要内容提供者时就会调用其onCreate方法并且实例化了数据库连接帮助类。提供了UriMatcher,在静态代码块里初始化,添加上我们期望匹配的URI

 static {
      // NO_MATCH就是-1
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
      // 第三个参数填上自定义的代码,对应于uriMatcher.match(uri)
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }

接收三个参数,分别是authority、path和自定义唯一码。

增删改查的方法就不说了,注意两点。

  1. 有个新方法uri.getPathSegments().get(1);这是什么意思呢?简单来说比如一个URI是这样的content://com.example.databasetest.provider/book/2,那么以provider/处分割,后面的部分是<path>.<id>,那么get(0)就获取到了路径,get(1)就获取到了id。
  2. insert方法返回的是一个新的URI,比如新增的一行db.insert返回一个新的id为3,那么内容提供者的insert方法返回的新URI为content://com.example.databasetest.provider/book/3

最后介绍getType()这个方法 -- 根据传入的内同URI来返回相应的MIME类型。

  • 必须以vnd开头
  • 如果URI以path结尾,则后接android.cursor.dir/;如果URI以id结尾,则后接android.cursor.item/
  • 最后接上vnd.<authority>.<path>

对于content://com.example.databasetest.provider/book这个URI,对应的MIME是vnd.android.cursor.dir/vnd.com.example.databasetest.book;

对于content://com.example.databasetest.provider/book/2这个URI,对应的MIME是vnd.android.cursor.item/vnd.com.example.databasetest.book

好了内容提供者写好了,赶紧在另外一个应用里尝试一下!

通过内容提供者访问数据

假设此应用时App-B,上面的应用是App-A。

布局实现对上述应用数据库中的book表的CURD

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">


    <Button        android:id="@+id/add_data"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Add to Book"/>

    <Button        android:id="@+id/query_data"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Query From Book"/>

    <Button    android:id="@+id/update_data"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:text="Update Data"/>

    <Button        android:id="@+id/del_data"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="Delete Data"/>

</LinearLayout>

MainActivity,所有的方法都是基于getContentResolver()。得到内容提供者后,尝试访问App-A的数据。此时App-A里内容提供者的onCreate方法得到执行,由此创建了数据库。

我们只需正确匹配Uri就能访问到App-A中的数据库了,代码很简单,不需要讲解了。

package com.sunhaiyu.contentprovidertest;

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 {
    // newId是每插入一条数据就会被赋值的,所以进行更新和删除操作时只能操作最后插入的数据,其他数据不会受到影响
    private String newId;

    @Override
    protectedvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            publicvoidonClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name", "A Clash of Kings");
                values.put("author", "George Martin");
                values.put("pages", 1040);
                values.put("price", 19.99);
                Uri newUri = getContentResolver().insert(uri, values);
                if (newUri != null) {
                    newId = newUri.getPathSegments().get(1);
                }
            }
        });

        Button queryData = (Button) findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            publicvoidonClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.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();
                }
            }
        });

        Button  updataData = (Button) findViewById(R.id.update_data);
        updataData.setOnClickListener(new View.OnClickListener() {
            @Override
            publicvoidonClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name", "A Storm of Swords");
                values.put("pages", 1216);
                values.put("price", 24.05);
                getContentResolver().update(uri, values, null, null);
            }
        });

        Button delData = (Button) findViewById(R.id.del_data);
        delData.setOnClickListener(new View.OnClickListener() {
            @Override
            publicvoidonClick(View v) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                getContentResolver().delete(uri, null, null);
            }
        });
    }
}

是不是很方便?使用ContentProvider就能式样App-A轻松访问到App-B中的内容。还有一些常见的例子,比如访问联系人和短信数据等。

猜你喜欢

转载自www.linuxidc.com/Linux/2017-06/145212.htm