《第一行代码》读书笔记(十一)----内容提供器

Android 的数据持久化所保存的数据只能在当前应用程序中访问.
SharedPreferences 存储中提供了 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 两种模式为什么在 4.2 版本以后被废弃了呢?
官方不推荐这种方式, 应该使用更加安全可靠的内容提供器技术.

账号密码等隐私数据不应提供给其他应用, 但是可以让其他程序进行二次开发的基础性数据还是可以提供的, 比如说电话簿, 短信, 媒体库等都提供了跨程序数据共享的功能.

简介

内容提供器(Content Provider) 用于不同的应用程序之间实现数据共享的功能. 它允许一个程序访问另一个程序的数据, 同时还能保证安全性. 可以选择只对哪一部分数据进行共享, 从而保证隐私数据不会泄露.

访问其他程序中的数据

ContentResolver 的基本用法

如果想要访问内容提供器中共享的数据, 必须使用 ContentResolve 类, 可以通过 Context 的 getContentResolver() 方法获取到该类的实例. 该类提供了一系列方法诸如 insert(), update(), delete(), query() 来对数据进行 CRUD 操作.
这些方法的参数不接收表名, 而是使用 Uri 参数, 称为 内容URI. 它为数据建立了唯一标识符, 由 权限(authority) 和路径(path) 两部分组成. 一般使用程序包名的方式命名权限, 路径添加在权限的后面. 还需要在字符串的头部加上协议声明. 标准格式:

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

有了 内容URI字符串, 需要将它解析成Uri对象才可以作为参数传入:

Uri uri = Uri.parse("content://com.example.app.provider/table1");

使用这个 Uri 对象来查询 table1 表中的数据:

Cursor cursor = getContentResolver().query(
    uri,
    projection,
    selection,
    selectionArgs,
    sortOrder);

这些参数的意义:
- uri: 指定查询某个应用程序下的某一张表
- projection: 指定查询的列名
- selection: 指定 where 的约束条件
- selectionArgs: 为 where 中的占位符提供具体的值
- orderBy: 指定查询结果的排序方式

查询返回的是一个 Cursor 对象, 读取:

if (cursor != null) {
    while (cursor.moveToNext()) {
        String column1 = cursor.getString(cursor.getColumnIndex("column1"));
        int culumn2 = cursor.getInt(cursor.getColumnIndex("column2"));
    }
}

添加数据:

ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);

更新数据:

ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text", "1"});

删除数据:

getContentResolver.delete(uri, "column2 = ?", new String[] {"1"});

读取系统联系人

新建一个项目, 修改 activity_main.xml, 添加一个 ListView, 用来显示系统的联系人.
修改 MainActivity :

public class MainActivity extends Activity {

    ListView contactsView;
    ArrayAdapter adapter;
    List contactsList = new ArrayList();

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

        contactsView = (ListView) findViewById(R.id.contacts_view);
        adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList);
        contactsView.setAdapter(adapter);
        readContacts();
    }

    private void readContacts() {
        Cursor cursor = null;
        try {
            //查询联系人数据
            cursor = getContentResolver().query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null, null, null, null);

            while (cursor.moveToNext()) {
                //获取联系人姓名
                String displayName = cursor.getString(cursor.getColumnIndex(
                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                //获取联系人手机号
                String number = cursor.getString(cursor.getColumnIndex(
                        ContactsContract.CommonDataKinds.Phone.NUMBER));

                contactsList.add(displayName + "\n" + number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }
}

最后添加权限:

<uses-permission android:name="android.permission.READ_CONTACTS"/>

创建自己的内容提供器

创建内容提供器的步骤

新建一个类继承 ContentProvider 类, 重写它的全部六个抽象方法.
* onCreate(): 初始化时调用. 在这里完成对数据库的创建和升级等操作.返回的是布尔值. 只有当存在 ContentResolver 尝试访问程序中数据时, 内容提供器才会被初始化.
* query(): 从内容提供器中查询数据.
* insert(): 向内容提供器添加数据.
* update(): 更新内容提供器中的已有数据.
* delete(): 从内容提供器中删除数据.
* getType(): 根据传入的内容URI来返回相应的 MIME 类型. 一个内容URI所对应的 MIME 字符串主要由三部分组成:
* 必须以 vnd 开头.
* 如果内容URI以路径结尾, 则后接 android.cursor.dir/, 如果内容URI以id 结尾, 则后接android.cursor.item/
* 最后接上 vnd..

内容URI的格式还有另外一种, 就是以id结尾, 比如:

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

表示期望访问该表中拥有相应 id 的数据.
如果以路径结尾, 表示期望访问该表中所有的数据.
可以使用通配符的方式来分别匹配这两种格式的内容URI, 规则:
1, *: 表示匹配任何长度的任意字符
2, #: 表示匹配任意长度的数字
所以, 能够匹配任意表的内容URI可以写成:

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

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

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

可以借助 UriMatcher 类来实现匹配内容URI功能. 该类提供一个 addURI() 方法, 三个参数可以分别把权限, 路径 和 一个自定义代码传进去. 当调用 UriMatcher 的 match() 方法时, 就可以将一个 Uri 对象传入, 返回值是某个能够匹配这个 Uri 对象所对应的自定义代码, 利用这个代码, 判断出掉用方期望访问的是哪张表中的数据.

新建 MyProvider 继承自 ContentProvider:

public class MyProvider 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_DIR);
        uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM);
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @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;
        }
        return null;
    }

    @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;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @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;
    }
}

所有的 CRUD 操作都一定要匹配到相应的内容URI格式才能进行, 我们不向 UriMatcher 中添加隐私数据的URI, 这样就保证了安全性.

实现跨程序数据共享

在之前写过的 Database 项目里添加一个类 DatabaseProvider :

public class DatabaseProvider 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 = "me.zipstream.database.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 String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.me.zipstream.database.provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.me.zipstream.database.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.me.zipstream.database.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.me.zipstream.database.provider.category";
        }

        return null;
    }

    @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 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 int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updateRows = 0;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                updateRows = db.update("Book", values, selection, selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updateRows = db.update("Book", values, "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                updateRows = db.update("category", values, selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updateRows = db.update("Category", values, "id = ?", new String[]{categoryId});
                break;
            default:
                break;
        }

        return updateRows;
    }
}

在 AndroidManifest.xml 文件中注册这个内容提供者:

<provider
    android:authorities="me.zipstream.database.provider"
    android:name="me.zipstream.database.DatabaseProvider"
    android:exported="true">
provider>

其中:
android:authorities 指定该内容提供器的权限
android:name 指定该类的全名
android:exported 指定这个内容提供器可以被其他应用程序访问

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

再新建一个项目来访问数据, 布局文件添加了四个按钮, 分别用来增删改查数据.
修改 MainActivity :

public class MainActivity extends Activity {

    private String newId;

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

        Button addData = (Button) findViewById(R.id.add);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://me.zipstream.database.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", 22.85);
                Uri newUri = getContentResolver().insert(uri, values);
                newId = newUri.getPathSegments().get(1);
            }
        });

        Button queryData = (Button) findViewById(R.id.query);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://me.zipstream.database.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("TAG", "book name is " + name);
                        Log.d("TAG", "book author is " + author);
                        Log.d("TAG", "book pages is " + pages);
                        Log.d("TAG", "book price is " + price);
                    }

                    cursor.close();
                }
            }
        });

        Button updateData = (Button) findViewById(R.id.update);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://me.zipstream.database.provider/book" + newId);
                ContentValues values = new ContentValues();
                values.put("name", "A Storm of Swords");
                values.put("pages", 1216);
                values.put("price", 24.99);
                getContentResolver().update(uri, values, null, null);
            }
        });

        Button deleteData = (Button) findViewById(R.id.delete);
        deleteData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://me.zipstream.database.provider/book" + newId);
                getContentResolver().delete(uri, null, null);
            }
        });
    }
}

你可能感兴趣的:(Android)