Android中的內(nèi)容提供者
為什么需要內(nèi)容提供者
為了跨程序訪問數(shù)據(jù)训枢。試想如果在App-1中創(chuàng)建了一個(gè)私有數(shù)據(jù)庫,App-2是不能直接訪問的迹缀。因?yàn)闄?quán)限不夠瓦糕,雖然可以使用chmod 777
來修改權(quán)限胁艰,然后使用SQLiteDatabase.openDatabase
的靜態(tài)方法款筑,填上具體的路徑和模式來訪問。但這并不推薦腾么,有沒有更好的辦法醋虏?官方推薦使用ContentProvider--內(nèi)容提供者。
創(chuàng)建內(nèi)容提供者
簡(jiǎn)單起見哮翘,使用以前的數(shù)據(jù)庫的項(xiàng)目DatabaseTest,同時(shí)建立兩個(gè)表book和category毛秘, onUpgrade
方法實(shí)現(xiàn)了數(shù)據(jù)庫的升級(jí)功能饭寺。onUpgrade
里面強(qiáng)制onCreate
,注意必須先刪除原來的表叫挟,否則我們創(chuàng)建時(shí)候發(fā)現(xiàn)原來的表還存在就會(huì)報(bào)錯(cuò)艰匙。
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里面就顯示下界面,省略了抹恳。
如果想讓這個(gè)數(shù)據(jù)庫共享员凝,其他應(yīng)用也能訪問?只需新增一個(gè)內(nèi)容提供者即可奋献。
New -> Other -> ContentProvider健霹,AS會(huì)幫我們?cè)贏ndroidManifest.xml里注冊(cè)好。有一個(gè)屬性authorities
比較重要瓶蚂,一般命名方式是<包名>.provider
糖埋,比如com.example.cptest.provider
。
可以看到窃这,內(nèi)容提供者的方法和操作數(shù)據(jù)庫差不多瞳别。最大的不同是操作數(shù)據(jù)庫需要填上表名,而內(nèi)容提供者中的方法需要填上Uri杭攻。為什么呢祟敛?因?yàn)槭强绯绦蛟L問數(shù)據(jù),多個(gè)應(yīng)用的表名可能一樣兆解,這樣就不知道到底訪問哪個(gè)應(yīng)用的數(shù)據(jù)了馆铁。Uri的格式一般如下
content://<package_name>.provider/<path>/<id>
,舉個(gè)例子content://com.example.databasetest.provider/book/2
表示訪問book表的id為2的那行數(shù)據(jù)锅睛。
甚至可以使用通配符
-
*
表示匹配任意長(zhǎng)度的任意字符 -
#
表示匹配任意長(zhǎng)度的數(shù)字
于是可以匹配任意表的URI可以寫成content://com.example.databasetest.provider/*
可以匹配一個(gè)表中任意一行的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是自定義代碼叼架,用于清楚表達(dá)我們想要訪問訪問數(shù)據(jù)庫的哪個(gè)表或者哪一行數(shù)據(jù)
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);
// 第三個(gè)參數(shù)填上自定義的代碼畔裕,對(duì)應(yīng)于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;
}
// 一旦使用到內(nèi)容提供者就調(diào)用此方法,并得到數(shù)據(jù)庫連接的實(shí)例乖订,返回true表示內(nèi)容提供者初始化成功
@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;
}
}
流程是這樣的扮饶,一旦需要內(nèi)容提供者時(shí)就會(huì)調(diào)用其onCreate方法并且實(shí)例化了數(shù)據(jù)庫連接幫助類。提供了UriMatcher乍构,在靜態(tài)代碼塊里初始化甜无,添加上我們期望匹配的URI
static {
// NO_MATCH就是-1
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 第三個(gè)參數(shù)填上自定義的代碼霸株,對(duì)應(yīng)于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);
}
接收三個(gè)參數(shù)碗旅,分別是authority、path和自定義唯一碼销部。
增刪改查的方法就不說了眠饮,注意兩點(diǎn)奥帘。
- 有個(gè)新方法
uri.getPathSegments().get(1);
這是什么意思呢?簡(jiǎn)單來說比如一個(gè)URI是這樣的content://com.example.databasetest.provider/book/2
仪召,那么以provider/
處分割寨蹋,后面的部分是<path>.<id>
,那么get(0)
就獲取到了路徑扔茅,get(1)
就獲取到了id已旧。 -
insert
方法返回的是一個(gè)新的URI,比如新增的一行db.insert返回一個(gè)新的id為3召娜,那么內(nèi)容提供者的insert方法返回的新URI為content://com.example.databasetest.provider/book/3
最后介紹getType()
這個(gè)方法 -- 根據(jù)傳入的內(nèi)同URI來返回相應(yīng)的MIME類型运褪。
- 必須以vnd開頭
- 如果URI以path結(jié)尾,則后接
android.cursor.dir/
;如果URI以id結(jié)尾玖瘸,則后接android.cursor.item/
- 最后接上
vnd.<authority>.<path>
對(duì)于content://com.example.databasetest.provider/book
這個(gè)URI秸讹,對(duì)應(yīng)的MIME是vnd.android.cursor.dir/vnd.com.example.databasetest.book
;
對(duì)于content://com.example.databasetest.provider/book/2
這個(gè)URI,對(duì)應(yīng)的MIME是vnd.android.cursor.item/vnd.com.example.databasetest.book
雅倒。
好了內(nèi)容提供者寫好了嗦枢,趕緊在另外一個(gè)應(yīng)用里嘗試一下!
通過內(nèi)容提供者訪問數(shù)據(jù)
假設(shè)此應(yīng)用時(shí)App-B屯断,上面的應(yīng)用是App-A文虏。
布局實(shí)現(xiàn)對(duì)上述應(yīng)用數(shù)據(jù)庫中的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()
殖演。得到內(nèi)容提供者后氧秘,嘗試訪問App-A的數(shù)據(jù)。此時(shí)App-A里內(nèi)容提供者的onCreate
方法得到執(zhí)行趴久,由此創(chuàng)建了數(shù)據(jù)庫丸相。
我們只需正確匹配Uri就能訪問到App-A中的數(shù)據(jù)庫了,代碼很簡(jiǎn)單彼棍,不需要講解了灭忠。
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是每插入一條數(shù)據(jù)就會(huì)被賦值的膳算,所以進(jìn)行更新和刪除操作時(shí)只能操作最后插入的數(shù)據(jù),其他數(shù)據(jù)不會(huì)受到影響
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中的內(nèi)容涕蜂。還有一些常見的例子,比如訪問聯(lián)系人和短信數(shù)據(jù)等映琳。
短信的備份
由于短信的數(shù)據(jù)庫已經(jīng)通過內(nèi)容提供者暴露出來 所以我們直接通過內(nèi)容的解析者去查詢數(shù)據(jù)庫机隙,查看源碼其authority是sms;查看添加的URI萨西,其中有一條null有鹿,表示讀取全部短信。
備份好的xml大概長(zhǎng)這樣
<Smss>
<Sms>
<address>123456</number>
<date>"2017/4/10"</date>
<body>"請(qǐng)你吃飯谎脯,快出來葱跋!給你5秒 5"</body>
</Sms>
<Sms>
<address>123456</number>
<date>"2017/2/10"</date>
<body>"請(qǐng)你吃飯,快出來源梭!給你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="恢復(fù)短信" />
</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:
// 點(diǎn)擊按鈕查詢短信內(nèi)容 然后把短信內(nèi)容進(jìn)行備份
try {
//[1]獲取XmlSerializer的實(shí)例
XmlSerializer serializer = Xml.newSerializer();
//[2]設(shè)置序列化器參數(shù)
File file = new File(Environment.getExternalStorageDirectory().getPath(), "smsbackup.xml");
FileOutputStream fos = new FileOutputStream(file);
serializer.setOutput(fos, "utf-8");
//[3]寫xml文檔開頭, 第二個(gè)參數(shù)娱俺,是否獨(dú)立,xml默認(rèn)獨(dú)立咸产,填寫true
serializer.startDocument("utf-8", true);
//[4]寫xml的根節(jié)點(diǎn)
serializer.startTag(null, "smss");
//[5]構(gòu)造uri,這個(gè)authority為sms從源碼可以看到仲闽,同時(shí)path為null表示查詢所有的短信脑溢,所以URI就是下面的樣子了
Uri uri = Uri.parse("content://sms/"); // content://sms 也可以
//[6]由于短信的數(shù)據(jù)庫已經(jīng)通過內(nèi)容提供者暴露出來 所以我們直接通過內(nèi)容解析者查詢
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節(jié)點(diǎn)
serializer.startTag(null, "sms");
//[8]寫address節(jié)點(diǎn)
serializer.startTag(null, "address");
serializer.text(address);
serializer.endTag(null, "address");
//[9]寫date節(jié)點(diǎn)
serializer.startTag(null, "date");
serializer.text(date);
serializer.endTag(null, "date");
//[10]寫body節(jié)點(diǎn)
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卡根目錄下,所以需要申請(qǐng)權(quán)限赖欣。而且讀取短信也需要權(quán)限屑彻。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SMS" />
之后再申請(qǐng)運(yùn)行時(shí)權(quán)限。
沒有實(shí)現(xiàn)短信恢復(fù)的功能顶吮,這個(gè)功能現(xiàn)在實(shí)現(xiàn)起來麻煩了社牲。
不能直接申請(qǐng)WRITE_SMSA
權(quán)限了!Android 4.4 (KitKat) 開始悴了,更新了 SMS 的部分API搏恤。只有default SMS app才能對(duì)短信數(shù)據(jù)庫有寫權(quán)限,但是用戶可以把第三方應(yīng)用設(shè)置為default SMS app湃交。詳情看這里
其實(shí)想想也正常熟空,如果任何應(yīng)用都能寫入短信數(shù)據(jù)庫,將是一大安全隱患搞莺。
讀取手機(jī)聯(lián)系人
手機(jī)聯(lián)系人信息也通過provider對(duì)外暴露息罗。任何應(yīng)用都可以輕松訪問到。ContactsContract才沧。CommonDataKinds.Phone
這個(gè)類已經(jīng)幫我們封裝好了迈喉,使得用戶不用操心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();
}
}
}
記得添加權(quán)限<uses-permission android:name="android.permission.READ_CONTACTS"/>
插入聯(lián)系人
插入聯(lián)系人也很方便挨摸,使用方式和上面大同小異孩革。
首先是布局,輸入姓名和手機(jī)號(hào)碼油坝,點(diǎn)擊按鈕就可插入到聯(lián)系人嫉戚。
<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="請(qǐng)輸入姓名" />
<EditText
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請(qǐng)輸入電話號(hào)碼" />
<Button
android:id="@+id/bt_add_contact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="插入到聯(lián)系人" />
</LinearLayout>
然后關(guān)鍵是addContact
方法了,注意先插入空值以獲得一個(gè)新的id澈圈,之后使用這個(gè)ID添加聯(lián)系人姓名和手機(jī)好彬檀,下面的代碼可以說是一個(gè)模板。
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) {
// 創(chuàng)建一個(gè)空的ContentValues
ContentValues values = new ContentValues();
// 向RawContacts.CONTENT_URI空值插入瞬女,用于獲取Android系統(tǒng)返回的rawContactId窍帝。后面要基于此id插入值
Uri rawContactUri = getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rawContactUri);
// 添加下一條之前先清空
values.clear();
// 1. 添加聯(lián)系人姓名
// id
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
// 內(nèi)容類型添加了才會(huì)顯示出來,否則顯示無姓名
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
// 聯(lián)系人名字
values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, name);
// 向聯(lián)系人URI添加聯(lián)系人名字
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
values.clear();
// 2. 添加手機(jī)號(hào)碼诽偷,這個(gè)id要保證和上面的一致
values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
values.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
// 聯(lián)系人的電話號(hào)碼
values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
// 電話類型
values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
// 向聯(lián)系人電話號(hào)碼URI添加電話號(hào)碼
getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values);
Toast.makeText(this, "聯(lián)系人數(shù)據(jù)添加成功", Toast.LENGTH_SHORT).show();
}
}
記得添加權(quán)限<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
內(nèi)容觀察者簡(jiǎn)介
當(dāng)某一個(gè)應(yīng)用中內(nèi)容提供者中共享的數(shù)據(jù)發(fā)生改變時(shí)候坤学,就會(huì)收到一個(gè)通知。具體來說报慕,當(dāng)App-A訪問或者修改了內(nèi)容提供者的數(shù)據(jù)時(shí)深浮,同時(shí)發(fā)送一個(gè)通知。(調(diào)用了getContentResolver().notifyChange()
)然后App-B中會(huì)響應(yīng)onChange()
方法眠冈。起到一個(gè)監(jiān)視的作用飞苇。
一個(gè)簡(jiǎn)單的例子,監(jiān)聽短信數(shù)據(jù)庫的變化蜗顽。系統(tǒng)源碼中已經(jīng)發(fā)送了通知布卡,我們只需接受即可。
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]注冊(cè)一個(gè)內(nèi)容觀察者
Uri uri = Uri.parse("content://sms/");
// false表示指定的這個(gè)URI和其父路徑 true還能表示其子路徑
getContentResolver().registerContentObserver(uri, true, new MyContentObserver(new Handler()));
}
private class MyContentObserver extends ContentObserver{
public MyContentObserver(Handler handler) {
super(handler);
}
//當(dāng)觀察的內(nèi)容發(fā)生改變的時(shí)候調(diào)用
@Override
public void onChange(boolean selfChange) {
Toast.makeText(MainActivity.this, "短信數(shù)據(jù)庫變化", Toast.LENGTH_SHORT).show();
Log.d("Sms", "onChange: ");
super.onChange(selfChange);
}
}
}
記得添加權(quán)限<uses-permission android:name="android.permission.READ_SMS" /
by @sunhaiyu
2017.6.5