ContentProvider
ContentProvider 簡介
- 內容提供者是應用程序之間共享數據的接口
- 使用ContentProvider 共享數據的好處是統一了數據訪問方式
- 內容提供者中數據更改可被監聽
ContentProvider使用
- 定義類繼承ContentProvider,根據需要重寫內部方法(增刪改查)
- 在清單文件的節點下進行配置,標籤中需要指定name 和authorities 屬性
name 爲類名,包名從程序Package 開始,以“.”開始
authorities:是訪問Provider 時的路徑,要唯一
exported:true, 意思是是否對外發布,系統默認爲false,要設置爲true。
創建一個ContentProvider
示例 代碼
public class PersonContentProvider extends ContentProvider {
// 用於存放並匹配個Uri 標識信息,一般在靜態代碼塊中對其信息進行初始化操作
private static UriMatcher matcher;
// 聲明一個用於操作數據庫對象
private PersonOpenHelper openHelper;
// 主機名信息:對應清單文件的authorities 屬性
private static final String AUTHORITY = "com.itheima.person";
// 數據庫表名
private static final String TABLE_PERSON_NAME = "person";
// Uri 匹配成功的返回碼
private static final int PERSON_INSERT_CODE = 1000;
private static final int PERSON_DELETE_CODE = 10001;
private static final int PERSON_UPDATE_CODE = 10002;
private static final int PERSON_QUERYALL_CODE = 10003;
private static final int PERSON_QUERYONE_CODE = 10004;
// 靜態代碼塊,用於初始化UriMatcher
static {
// NO_MATCH:沒有Uri 匹配的時候返回的狀態碼(-1)
matcher = new UriMatcher(UriMatcher.NO_MATCH);
// 添加一個分機號:
// 對person
// 表進行添加操作,如果Uri=content://com.itheima.person/person/insert,則返回PERSON_INSERT_CODE
matcher.addURI(AUTHORITY, "person/insert", PERSON_INSERT_CODE);
// 對person 表進行刪除操作,如果Uri=content: // com.itheima.person/person/delete,
//則返回PERSON_DELETE_CODE
matcher.addURI(AUTHORITY, "person/delete", PERSON_DELETE_CODE);
// 對person 表進行修改操作,如果Uri=
content: // com.itheima.person/person/update,則返回PERSON_UPDATE_CODE
matcher.addURI(AUTHORITY, "person/update", PERSON_UPDATE_CODE);
// 對person
// 表進行查詢所有操作,如果Uri=content://com.itheima.person/person,則返回PERSON_QUERYALL_CODE
matcher.addURI(AUTHORITY, "person", PERSON_QUERYALL_CODE);
// 對person 表進行查詢單個操作,如果Uri=
content: // com.itheima.person/person/#,(#:代表數字)則返回PERSON_QUERYONE_CODE
matcher.addURI(AUTHORITY, "person/#", PERSON_QUERYONE_CODE);
}
@Override
public boolean onCreate() {
// 內容提供者中,獲取contenxt,是通過getContext,與測試類一樣,不能再成員變量,構造函數中調用,但是可以再onCreate
// 方法中獲取。
openHelper = new PersonOpenHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 用匹配器去匹配uri,如果匹配成功則返回匹配器中對應的狀態碼
int matchCode = matcher.match(uri);
SQLiteDatabase db = openHelper.getReadableDatabase();
switch (matchCode) {
case PERSON_QUERYALL_CODE:
return db.query(TABLE_PERSON_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
case PERSON_QUERYONE_CODE:
// 使用ContentUris 工具類解析出uri 中的id
long parseId = ContentUris.parseId(uri);
return db.query(TABLE_PERSON_NAME, projection, "id=?",
new String[] { parseId + "" }, null, null, sortOrder);
default:
throw new IllegalArgumentException("Uri 匹配失敗:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 新插入對象的id
long id = db.insert(TABLE_PERSON_NAME, null, values);
db.close();
// 使用ContentUris 工具類將id 追加到uri 中,返回給客戶
return ContentUris.withAppendedId(uri, id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 返回刪除的個數
int count = db.delete(TABLE_PERSON_NAME, selection, selectionArgs);
// 關閉數據庫
db.close();
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 返回更新的個數
int count = db.update(TABLE_PERSON_NAME, values, selection,
selectionArgs);
// 更新數據庫
db.close();
return count;
}
@Override
public String getType(Uri uri) {
return null;
}
}
訪問ContentProvider
- 外部程序只需知道內容提供者的Uri 路徑信息,通過內容解析器ContentResolver 即可調用內容提供者。
- 如果內容提供者所在應用程序,沒有啓動,其他應用程序訪問調用它時,就會啓動它。
啓動時,會創建這個內容提供者。
測試query(單個)方法
public void queryOne() {
ContentResolver resolver = getContext().getContentResolver();
Uri uri = Uri.parse("content://com.itheima.person/person/2");
Cursor cursor = resolver.query(uri, new String[] { "name", "age",
"phone", "address" }, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(0);
int age = cursor.getInt(1);
String phone = cursor.getString(2);
String address = cursor.getString(3);
System.out.println(name + "/" + age + "/" + phone + "/" + address);
}
cursor.close();
}
測試query(多個)方法
public void queryAll() {
ContentResolver resolver = getContext().getContentResolver();
Uri uri = Uri.parse("content://com.itheima.person/person");
Cursor cursor = resolver.query(uri, new String[] { "name", "age",
"phone", "address" }, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(0);
int age = cursor.getInt(1);
String phone = cursor.getString(2);
String address = cursor.getString(3);
System.out.println(name + "/" + age + "/" + phone + "/" + address);
}
cursor.close();
}
URI
schema | 主機名authority | path | ID |
---|---|---|---|
content:// | com.itheima.provider/ | person/ | 10 |
schema:用來說明一個ContentProvider 控制這些數據。”content://”
authority:它定義了是哪個ContentProvider 提供這些數據,provider>節點中的authorites 屬性
path:路徑,URI 下的某一個Item。
ID:通常定義Uri 時使用”#”號佔位符代替, 使用時替換成對應的數字
content://com.itheima.provider/person/#:#表示數據id(#代表任意數字)
content://com.itheima.provider/person/*:*來匹配任意文本。
示例 短信的備份與恢復
需要的權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
備份短信
public void backupSms(View view) {
// 如果當前正在備份,則返回。
if (backupFlag) {
Toast.makeText(this, "當前正在備份中。。。請稍後再操作", 0).show();
return;
}
// 開闢一個新的線程來處理業務,因爲短信備份是耗時操作,如果不放在子線程中操作可能會導致ANR 異常
new Thread(new Runnable() {
@Override
public void run() {
// 將標記位設置爲true
backupFlag = true;
// 子線程中創建一個Looper 對象
Looper.prepare();
// 獲取ContentResolver 對象
ContentResolver resolver = getContentResolver();
// 編輯短信訪問的uri
Uri uri = Uri.parse("content://sms");
// 執行查詢語句
Cursor cursor = resolver.query(uri, new String[] { "address",
"date", "body", "type" }, null, null, null);
List<Sms> list = new ArrayList<Sms>();
// 獲取短信的總共條數
int count = cursor.getCount();
// 設置進度條的最大值
pb.setMax(count);
int i = 0;
while (cursor.moveToNext()) {
Sms sms = new Sms();
String address = cursor.getString(0);
String date = cursor.getString(1);
String body = cursor.getString(2);
String type = cursor.getString(3);
sms.setAddress(address);
sms.setBody(body);
sms.setDate(date);
sms.setType(type);
list.add(sms);
// 更新進度條
pb.setProgress(++i);
// 這裏是爲了方便演示備份的效果加上線程等待
SystemClock.sleep(50);
}
try {
// 通過自定義的Sms2XmlUtil 工具類將短信保存到xml 中
Sms2XmlUtil.sms2Xml(list);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,
"備份短信失敗。" + e.getLocalizedMessage(), 1).show();
}
// 關閉遊標
cursor.close();
Toast.makeText(MainActivity.this, "短信備份成功!本次備份了" + i + "條短信。",
1).show();
// 循環消息
Looper.loop();
// 將標記設置爲flase
backupFlag = false;
}
}).start();
}
恢復短信
代碼缺陷:在插入數據庫之前先判斷該短信是否存在,如果存在則不插入。
public void reverseSms(View view) {
if (reverseFlag) {
Toast.makeText(this, "當前短信正在備份中,請稍後再操作。", 0).show();
return;
}
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
ContentResolver resolver = getContentResolver();
Uri url = Uri.parse("content://sms");
List<Sms> list = null;
try {
list = Sms2XmlUtil.xml2Sms();
pb.setMax(list.size());
pb.setProgress(0);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,
"短信恢復失敗!" + e.getLocalizedMessage(), 1).show();
return;
}
int i = 0;
for (Sms s : list) {
ContentValues values = new ContentValues();
values.put(Sms2XmlUtil.SMS_TAG_ADDRESS, s.getAddress());
values.put(Sms2XmlUtil.SMS_TAG_BODY, s.getBody());
values.put(Sms2XmlUtil.SMS_TAG_DATE, s.getDate());
values.put(Sms2XmlUtil.SMS_TAG_TYPE, s.getType());
Uri uri = resolver.insert(url, values);
System.out.println("插入的id:" + ContentUris.parseId(uri));
pb.setProgress(++i);
SystemClock.sleep(50);
}
Toast.makeText(MainActivity.this, "短信恢復成功!本次恢復了" + i + "條短信。",
1).show();
reverseFlag = false;
Looper.loop();
}
}).start();
}
示例 操作系統聯繫人
查看數據庫其中raw_contacts 表存放的是聯繫人條數信息,
data 表中存放的是raw_contacts 中的每一條id 對應的具體信息,
不同類型的信息由mimetype_id 來標識。
- raw_contacts 表:其中保存了聯繫人id 字段爲:_id
- data 表:和raw_contacts 是多對一的關係,保存了聯繫人的各項數據
- mimetypes 表:數據類型
查詢聯繫人信息思路:先查詢raw_contacts 得到每個聯繫人的id,在使用id 從data表中查詢對應數據,根據mimetype 分類數據
tips:
- 系統內部提供了根據電話號碼獲取data 表數據的功能, 路徑爲:data/phones/filter/*
- 用電話號碼替換“*”部分就可以查到所需數據,獲取“display_name”可以獲取到
聯繫人顯示名
需要的權限
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
獲取聯繫人代碼
public class MainActivity extends Activity {
private ListView lv;
private List<Contact> list;
private MyAdapter adapter;
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
Toast.makeText(MainActivity.this, "數據獲取成功。", 1).show();
// 更新數據
adapter.notifyDataSetChanged();
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.lv);
list = new ArrayList<Contact>();
adapter = new MyAdapter();
lv.setAdapter(adapter);
}
/**
* 查詢所有的系統聯繫人,這裏的業務放在子線程中操作
*/
public void queryContacts(View view) {
new Thread(new Runnable() {
@Override
public void run() {
ContentResolver contentResolver = getContentResolver();
Uri uri = Uri
.parse("content://com.android.contacts/raw_contacts");
Cursor cursor = contentResolver.query(uri,
new String[] { "contact_id" }, null, null, null);
list.clear();
int i = 0;
while (cursor.moveToNext()) {
i++;
// 先從raw_contacts 表中獲取contact_id
String contact_id = cursor.getString(0);
if (TextUtils.isEmpty(contact_id)) {
continue;
}
// 根據contact_id 從data 表中查詢具體的數據
Cursor cursor2 = contentResolver.query(
Uri.parse("content://com.android.contacts/data"),
new String[] { "data1", "mimetype" },
"contact_id=?", new String[] { contact_id }, null);
Contact contact = new Contact();
while (cursor2.moveToNext()) {
String data = cursor2.getString(0);
String mimetype = cursor2.getString(1);
if ("vnd.android.cursor.item/name".equals(mimetype)) {
contact.setName(data);
} else if ("vnd.android.cursor.item/phone_v2"
.equals(mimetype)) {
contact.setPhone(data);
} else if ("vnd.android.cursor.item/email_v2"
.equals(mimetype)) {
contact.setEmail(data);
} else {
contact.setOther(data);
}
}
list.add(contact);
cursor2.close();
}
cursor.close();
// 發送一個空消息,更新ListView
handler.sendEmptyMessage(RESULT_OK);
}
}).start();
}
class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 這裏面通過ViewHolder 類將其他子屬性值綁定在View 上面,這裏對 ListView 作了進一步的優化處理
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
if (convertView != null) {
view = convertView;
holder = (ViewHolder) view.getTag();
} else {
view = View
.inflate(MainActivity.this, R.layout.list_item, null);
holder = new ViewHolder();
holder.tv_name = (TextView) view.findViewById(R.id.tv_name);
holder.tv_phone = (TextView) view.findViewById(R.id.tv_phone);
holder.tv_email = (TextView) view.findViewById(R.id.tv_email);
view.setTag(holder);
}
Contact contact = list.get(position);
holder.tv_name.setText(contact.getName());
holder.tv_email.setText(contact.getEmail());
holder.tv_phone.setText(contact.getPhone());
return view;
}
}
class ViewHolder {
TextView tv_name;
TextView tv_email;
TextView tv_phone;
}
}
插入聯繫人
/**
* 往系統聯繫人表中插入一條數據,這裏爲了方便演示,我們直接插入一 條固定的數據
*/
public void insertContacts(View view) {
// 創建一個自定義的Contact 類,將要網系統聯繫人表中插入的字段封裝起來
Contact contact = new Contact();
contact.setEmail("[email protected]");
contact.setName("王二麻子" + new Random().nextInt(1000));
contact.setPhone("9999999" + new Random().nextInt(100));
contact.setOther("北京市中關村軟件園");
// 獲取ContentResolver 對象
ContentResolver resolver = getContentResolver();
// 操作raw_contacts 表的uri
Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts");
// 操作data 表的uri
Uri data_uri = Uri.parse("content://com.android.contacts/data");
// 在插入數據之前先查詢出當前最大的id
Cursor cursor = resolver.query(raw_uri, new String[] { "contact_id" },
null, null, "contact_id desc limit 1");
int id = 1;
if (cursor != null) {
boolean moveToFirst = cursor.moveToFirst();
if (moveToFirst) {
id = cursor.getInt(0);
}
}
cursor.close();
// 要插入數據的contact_id 值
int newId = id + 1;
// 給raw_contact 表中添加一天記錄
ContentValues values = new ContentValues();
values.put("contact_id", newId);
resolver.insert(raw_uri, values);
// 在data 表中添加數據
// 添加name
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/name");
values.put("data1", contact.getName());
resolver.insert(data_uri, values);
// 添加phone
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/phone_v2");
values.put("data1", contact.getPhone());
resolver.insert(data_uri, values);
// 添加地址
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/postal-address_v2");
values.put("data1", contact.getOther());
resolver.insert(data_uri, values);
// 添加email
values = new ContentValues();
values.put("raw_contact_id", newId);
values.put("mimetype", "vnd.android.cursor.item/email_v2");
values.put("data1", contact.getEmail());
resolver.insert(data_uri, values);
Toast.makeText(this, "成功插入聯繫人" + contact, 0).show();
}
ContentObserver 內容觀察者
- 類似於數據庫技術中的觸發器(Trigger)
- 當ContentObserver所觀察的Uri 發生變化時,便會觸發它
- ContentObserver 分爲:“表“ContentObserver、“行”ContentObserver,與的Uri MIME Type相關
- Uri Type可以分爲:返回多條數據的Uri、返回單條數據的Uri。
示例 使用ContentObserver短信監聽
發出的短信有這樣幾個過程:草稿箱->發件箱->已發送。所以只需查詢發件箱中的信息即可,處於正在發送狀態的短信放在發送箱中。
權限
<uses-permission android:name="android.permission.READ_SMS"/>
創建一個內容觀察者
public class SmsContentObserver extends ContentObserver {
// 聲明一個Content 對象,在構造函數中對其進行實例化
private Context context;
private Handler handler;
// 聲明構造函數
public SmsContentObserver(Context context, Handler handler) {
super(handler);
this.context = context;
this.handler = handler;
}
// 覆寫父類onChange 方法
@Override
public void onChange(boolean selfChange) {
// 通過查看發件箱中的短信即可觀察到新短信的發送
Uri uri = Uri.parse("content://sms/outbox");
// 獲取Context 對象的ContentResolver 對象
ContentResolver resolver = context.getContentResolver();
String[] projection = { "address", "body", "date" };
// 獲取發件箱中的短信
Cursor cursor = resolver.query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToNext()) {
String address = cursor.getString(0);
String body = cursor.getString(1);
Long date = cursor.getLong(2);
// 調用DateFormat 的方法將long 類型的時間轉化爲系統相關時間
String dateStr = DateFormat.getTimeFormat(context).format(date);
// 打印觀察到正在發送的短信
System.out.println("address=" + address + " body=" + body + "date="
+ dateStr);
}
cursor.close();
}
}
在MainActivity 類中註冊SmsContentObserver
public class MainActivity extends Activity {
public class MainActivity extends Activity {
private SmsContentObserver observer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 實例化一個自定義的SMSContentObserver 對象
observer = new SmsContentObserver(this, null);
// 操作系統短信的uri
Uri uri = Uri.parse("content://sms");
/**
* 獲取ContentResolver,然後註冊ContentObserver
*/
getContentResolver().registerContentObserver(uri, true, observer);
}
}
}
自定義 ContentObserver
通過ContentObserver 實現了對系統發送短信的監聽
這是因爲系統短信數據數據庫中插入新的短信的時候通過ContentResolver 調用了notifyChange方法,
正是該方法對外發出了消息,這樣我們的ContentObserver 才監聽到了短信的發送
示例 發送消息
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = openHelper.getWritableDatabase();
// 新插入對象的id
long id = db.insert(TABLE_PERSON_NAME, null, values);
db.close();
// 聲明一個uri,通過這個uri ContentObserver 可以進行監聽
Uri notifyUri = Uri.parse("com.itheima.person");
/*
* 方法中第一個參數指定一個被監聽的uri 第二個參數是指定ContentObserver,如果不爲null 則代表只有指定
* 的ContentObserver 可以接收到消息 爲null 則所有的ContentObserver 都有機會接收到該消息
*/
getContext().getContentResolver().notifyChange(notifyUri, null);
// 使用ContentUris 工具類將id 追加到uri 中,返回給客戶
return ContentUris.withAppendedId(uri, id);
}
監聽
contentResolver.registerContentObserver(uri,notifyForDescendents,contentObserver);
參數:
uri、監聽哪個uri 的操作
notifyForDescendents 是否觀察uri 的子集,如果是false,不觀察他的子孫,就只能完全匹配
contentObserver,內容觀察者