Android中的内容提供者
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
Android中的内容提供者的更多相关文章
- 在Android中把内容写到XML文件中
在Android中把内容写到XML文件中 saveXmlButton.setOnClickListener(new OnClickListener() { @Override public void ...
- Android学习---通过内容提供者(ContentProvider)操作另外一个应用私有数据库的内容
一.什么是ContentProvider? ContentProvider直译过来就是内容提供者,主要作用就是A应用提供接口给B应用调用数据,和之前介绍的sharedPreference和直接开放文件 ...
- Android开发13——内容提供者ContentProvider的基本使用
一.ContentProvider简介 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.ContentProvider为存储和获取数据提 ...
- android 53 ContentProvider内容提供者
ContentProvider内容提供者:像是一个中间件一样,一个媒介一样,可以以标准的增删改差操作对手机的文件.数据库进行增删改差.通过ContentProvider查找sd卡的音频文件,可以提供标 ...
- Android基础之内容提供者的实现
内容提供者可以实现应用间查询数据库的需求 一.在提供数据库访问的应用设置内容提供者 public class AccountProvider extends ContentProvider { sta ...
- Android中TextView内容过长加省略号
textview中有个内容过长加省略号的属性,即ellipsize,用法如下: 在xml中 Android:ellipsize = "end" 省略号在结尾 and ...
- android中实现内容搜索
在编写android搜索代码的时候,怎样去实现搜索功能,考虑中的有两种: 自己定义搜索方法: 1.自己定义搜索输入框,搜索图标,搜索button 2.自己定义语音输入方法 3.自己定义经常使用热词内容 ...
- android中TextView内容竖向显示
项目中遇到需要textview内容竖着排的需求,如图所示: 网上那些“教程”并不能达到需要的效果,发现有一个属性可以支持这种效果,android:ems=“*”,这是属性表示一行只显示*个字符. 具体 ...
- Android中的内容提供器
用途 不同于File, SharedPreferences和DataBase,Content Provider主要用于不同的应用程序间共享数据,允许一个程序安全的访问另一个程序中的数据. 用法 通过C ...
随机推荐
- 有关SQL模糊查询
执行 数据库查询时,有完整查询和模糊查询之分. 一般模糊语句如下: SELECT 字段 FROM 表 WHERE 某字段 Like 条件 其中关于条件,SQL提供了四种匹配模式: 1,%:表示任意0个 ...
- mysql+keepalived 双主热备高可用
理论介绍:我们通常说的双机热备是指两台机器都在运行,但并不是两台机器都同时在提供服务.当提供服务的一台出现故障的时候,另外一台会马上自动接管并且提供服务,而且切换的时间非常短.MySQL双主复制,即互 ...
- v9 频道页如果有下级栏目跳转到第一个栏目链接
{if $CATEGORYS[$catid]['child']==1} {php $firstarr = explode(',',$CATEGORYS[$catid]['arrchildid']);} ...
- mysql update 批量更新
UPDATE cntheater SET title = (SELECT title FROM cntheater_copy WHERE cntheater.id = cntheater_copy.i ...
- Android 4.0以后正确的获取外部sd卡存储目录
刚解决这个棘手的问题 找了很久,随笔记下. 网上搜索 android 获取外部sd卡存储目录 普遍都是: 1) Environment.getExternalStorageDirectory() 这个 ...
- PHP以星号隐藏用户名手机和邮箱
<?php class Hidesatr{ function hide_star_do($str) { //用户名.邮箱.手机账号中间字符串以*隐藏 if (strpos($str, '@')) ...
- java对mysql的增删改查
-----连接数据库 package connectdb;import java.sql.*;class Dbcon { // 此处连接数据库,独立开一个类,以后操作数据库的每次连接就不用写这么多 p ...
- 基于AFN封装的带缓存的网络请求
给大家分享一个基于AFN封装的网络请求 git: https://github.com/zhouxihi/NVNetworking #带缓存机制的网络请求 各类请求有分带缓存 , 不带缓存, 可自定义 ...
- JS综合练习
练习一.任意数求和(最多输入十位数),输入999终止 运行代码 <!DOCTYPE html><html> <head> <meta charset=&quo ...
- C#设置WebBrowser默认浏览器
由于VS的WebBrowser控件的默认浏览器是IE7,好多网页兼容性不是很好,所以要修改下默认浏览器. 设置前: 设置后: 在WebBrowser界面加载时执行以下方法,设置浏览器. /// ...