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;
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
this.mContext = context;
}
@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);
}
}
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
public int delete(@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
public boolean onCreate() {
// 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
public int update(@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和自定义唯一码。
增删改查的方法就不说了,注意两点。
- 有个新方法
uri.getPathSegments().get(1);
这是什么意思呢?简单来说比如一个URI是这样的content://com.example.databasetest.provider/book/2
,那么以provider/
处分割,后面的部分是<path>.<id>
,那么get(0)
就获取到了路径,get(1)
就获取到了id。 -
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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(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
public void onClick(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
public void onClick(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
public void onClick(View v) {
Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}
是不是很方便?使用ContentProvider就能式样App-A轻松访问到App-B中的内容。还有一些常见的例子,比如访问联系人和短信数据等。
短信的备份
由于短信的数据库已经通过内容提供者暴露出来 所以我们直接通过内容的解析者去查询数据库,查看源码其authority是sms;查看添加的URI,其中有一条null,表示读取全部短信。
备份好的xml大概长这样
<Smss>
<Sms>
<address>123456</number>
<date>"2017/4/10"</date>
<body>"请你吃饭,快出来!给你5秒 5"</body>
</Sms>
<Sms>
<address>123456</number>
<date>"2017/2/10"</date>
<body>"请你吃饭,快出来!给你5秒 4"</body>
</Sms>
</Smss>
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.sunhaiyu.smscontentprovider.MainActivity">
<Button
android:id="@+id/bt_backup_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="备份短信" />
<Button
android:id="@+id/bt_restore_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="恢复短信" />
</LinearLayout>
MainActivity
package com.sunhaiyu.smscontentprovider;
import android.Manifest;
import android.support.v7.app.AppCompatActivity;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Xml;
import android.view.View;
import android.widget.Button;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private List<String> permissons = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissons.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
permissons.add(Manifest.permission.READ_SMS);
}
if (!permissons.isEmpty()) {
ActivityCompat.requestPermissions(MainActivity.this, permissons.toArray(new String[permissons.size()]), 1);
}
Button btBackup = (Button) findViewById(R.id.bt_backup_sms);
Button btRestore = (Button) findViewById(R.id.bt_restore_sms);
btBackup.setOnClickListener(this);
btRestore.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_backup_sms:
// 点击按钮查询短信内容 然后把短信内容进行备份
try {
//[1]获取XmlSerializer的实例
XmlSerializer serializer = Xml.newSerializer();
//[2]设置序列化器参数
File file = new File(Environment.getExternalStorageDirectory().getPath(), "smsbackup.xml");
FileOutputStream fos = new FileOutputStream(file);
serializer.setOutput(fos, "utf-8");
//[3]写xml文档开头, 第二个参数,是否独立,xml默认独立,填写true
serializer.startDocument("utf-8", true);
//[4]写xml的根节点
serializer.startTag(null, "smss");
//[5]构造uri,这个authority为sms从源码可以看到,同时path为null表示查询所有的短信,所以URI就是下面的样子了
Uri uri = Uri.parse("content://sms/"); // content://sms 也可以
//[6]由于短信的数据库已经通过内容提供者暴露出来 所以我们直接通过内容解析者查询
Cursor cursor = getContentResolver().query(uri, new String[]{"address", "date", "body"}, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String address = cursor.getString(cursor.getColumnIndex("address"));
String date = cursor.getString(cursor.getColumnIndex("date"));
String body = cursor.getString(cursor.getColumnIndex("body"));
//[7]写sms节点
serializer.startTag(null, "sms");
//[8]写address节点
serializer.startTag(null, "address");
serializer.text(address);
serializer.endTag(null, "address");
//[9]写date节点
serializer.startTag(null, "date");
serializer.text(date);
serializer.endTag(null, "date");
//[10]写body节点
serializer.startTag(null, "body");
serializer.text(body);
serializer.endTag(null, "body");
serializer.endTag(null, "sms");
}
cursor.close();
}
serializer.endTag(null, "smss");
serializer.endDocument();
} catch (Exception e) {
e.printStackTrace();
}
break;
case R.id.bt_restore_sms:
break;
default:
break;
}
}
}
由于我直接八备份文件放在了sd卡根目录下,所以需要申请权限。而且读取短信也需要权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SMS" />
之后再申请运行时权限。
没有实现短信恢复的功能,这个功能现在实现起来麻烦了。
不能直接申请WRITE_SMSA
权限了!Android 4.4 (KitKat) 开始,更新了 SMS 的部分API。只有default SMS app才能对短信数据库有写权限,但是用户可以把第三方应用设置为default SMS app。详情看这里
其实想想也正常,如果任何应用都能写入短信数据库,将是一大安全隐患。
读取手机联系人
手机联系人信息也通过provider对外暴露。任何应用都可以轻松访问到。ContactsContract。CommonDataKinds.Phone
这个类已经帮我们封装好了,使得用户不用操心URI的匹配问题,十分方便。
package com.sunhaiyu.contacttest;
import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
}
Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d("Contact", displayName + " : " + phoneNumber);
}
cursor.close();
}
}
}
记得添加权限<uses-permission android:name="android.permission.READ_CONTACTS"/>
插入联系人
插入联系人也很方便,使用方式和上面大同小异。
首先是布局,输入姓名和手机号码,点击按钮就可插入到联系人。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入姓名" />
<EditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入电话号码" />
<Button
android:id="@+id/bt_add_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="插入到联系人" />
</LinearLayout>
然后关键是addContact
方法了,注意先插入空值以获得一个新的id,之后使用这个ID添加联系人姓名和手机好,下面的代码可以说是一个模板。
package com.sunhaiyu.addcontacttest;
import android.Manifest;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText etName;
private EditText etPhone;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_CONTACTS}, 1);
}
etName = (EditText) findViewById(R.id.et_name);
etPhone = (EditText) findViewById(R.id.et_phone);
Button btAdd = (Button) findViewById(R.id.bt_add_contact);
btAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = etName.getText().toString().trim();
String phone = etPhone.getText().toString().trim();
addContact(name, phone);
}
});
}
public void addContact(String name, String phoneNumber) {
// 创建一个空的ContentValues
ContentValues values = new ContentValues();
// 向RawContacts.CONTENT_URI空值插入,用于获取Android系统返回的rawContactId。后面要基于此id插入值
Uri rawContactUri = getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rawContactUri);
// 添加下一条之前先清空
values.clear();
// 1. 添加联系人姓名
// id
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
// 内容类型添加了才会显示出来,否则显示无姓名
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
// 联系人名字
values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name);
// 向联系人URI添加联系人名字
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
values.clear();
// 2. 添加手机号码,这个id要保证和上面的一致
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
// 联系人的电话号码
values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
// 电话类型
values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
// 向联系人电话号码URI添加电话号码
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
Toast.makeText(this, "联系人数据添加成功", Toast.LENGTH_SHORT).show();
}
}
记得添加权限<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
内容观察者简介
当某一个应用中内容提供者中共享的数据发生改变时候,就会收到一个通知。具体来说,当App-A访问或者修改了内容提供者的数据时,同时发送一个通知。(调用了getContentResolver().notifyChange()
)然后App-B中会响应onChange()
方法。起到一个监视的作用。
一个简单的例子,监听短信数据库的变化。系统源码中已经发送了通知,我们只需接受即可。
package com.sunhaiyu.contentbservertest;
import android.Manifest;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_SMS)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_SMS}, 1);
}
//[1]注册一个内容观察者
Uri uri = Uri.parse("content://sms/");
// false表示指定的这个URI和其父路径 true还能表示其子路径
getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler()));
}
private class MyContentObserver extends ContentObserver{
public MyContentObserver(Handler handler) {
super(handler);
}
//当观察的内容发生改变的时候调用
@Override
public void onChange(boolean selfChange) {
Toast.makeText(MainActivity.this, "短信数据库变化", Toast.LENGTH_SHORT).show();
Log.d("Sms", "onChange: ");
super.onChange(selfChange);
}
}
}
记得添加权限<uses-permission android:name="android.permission.READ_SMS" /
by @sunhaiyu
2017.6.5