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

来源:互联网 发布:杜兰特07年首秀数据 编辑:程序博客网 时间:2024/05/14 04:22

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/table1content://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<String> adapter;    List<String> contactsList = new ArrayList<String>();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        contactsView = (ListView) findViewById(R.id.contacts_view);        adapter = new ArrayAdapter<String>(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.<authority>.<path>

内容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);            }        });    }}
0 0
原创粉丝点击