Android 的数据持久化所保存的数据只能在当前应用程序中访问.
SharedPreferences 存储中提供了 MODE_WORLD_READABLE
和 MODE_WORLD_WRITEABLE
两种模式为什么在 4.2 版本以后被废弃了呢?
官方不推荐这种方式, 应该使用更加安全可靠的内容提供器技术.
账号密码等隐私数据不应提供给其他应用, 但是可以让其他程序进行二次开发的基础性数据还是可以提供的, 比如说电话簿, 短信, 媒体库等都提供了跨程序数据共享的功能.
内容提供器(Content Provider) 用于不同的应用程序之间实现数据共享的功能. 它允许一个程序访问另一个程序的数据, 同时还能保证安全性. 可以选择只对哪一部分数据进行共享, 从而保证隐私数据不会泄露.
如果想要访问内容提供器中共享的数据, 必须使用 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);
}
});
}
}