Android 调用系统的ContentProvider
1.調用系統的ContentProvider
Android系統提供了一些定義好的ContentProvider,可以根據這些ContentProvider的Uri得到相應的數據。比方說想獲得手機短信的信息,或者想獲得圖庫、聯系人信息…必定有相應的Uri與之對應。知道對應的Uri后,就可以通過ContentResolver對象,根據Uri進行數據訪問。
2.舉例—手機短信數據的讀寫
根據Uri獲取手機短信的信息,并將其備份和恢復:
首先在DDMS中向模擬器中發幾條短信,然后運行程序,點擊備份,提示備份成功后,將所有的短信刪除,然后點擊恢復,打開短信界面發現剛才刪除的短信已經恢復,這就是我們要實現的功能。
首先分析一下怎么實現上述效果,如果想備份短信,首先要做的就是獲取短信的列表,這一步比較簡單,因為谷歌已經封裝好,我們要做的就是用Uri去查詢短信庫,然后拿到數據后需要將數據以XML的形式保存到SD卡里面,當然用其它的方式也可以,只要能將其恢復就行。最后恢復的時候將指定路徑的XML文件解析,根據Uri將解析的短信數據插入到系統的短信列表中。
了解了大概思路后,另一個重要的任務就是看看短信的表結構,在模擬器中它的路徑是data->data->com.android.providers.telephony->databases下,如下圖:
把它導出來,然后用Sqlite數據打開可以看到數據的結構,這里只關心threads表和sms表就夠了,threads代表所有會話信息,每個會話代表和一個聯系人之間短信的群組;sms代表短信具體信息。在sms表中的thread_id指向了threads表中的_id,指定每條短信的會話id,以便對短信進行分組。
threads表的結構如下:
_id:會話id,用于區分不同的電話號碼,系統會為不同的電話號碼分配不同的_id。
date:收到信息的時間(如果收到來自同一個phone number多條信息,并且有對于一條信息未讀,那么date表示收到的最后一條信息時的時間)
message_count:收到的信息的數目
read: 0. 代表未讀; 1.代表已讀
sms表的結構如下:
_id:用于區分不同的短信
date::該條短信接收的時間
read:0表未讀,1表已讀
body: 具體的短信內容
訪問手機短信的Uri,主要有以下這么幾個:
content://sms/ 所有短信
content://sms/inbox 收件箱
content://sms/sent 已發送
content://sms/draft 草稿
content://sms/outbox 發件箱
content://sms/failed 發送失敗
content://sms/queued 待發送列表
這里使用content://sms/,備份所有的短信。
首先要做的就是根據Uri獲取短信的列表,這里新建一個SmsManage類,將備份和恢復的方法放到這個類中。
//獲取短信列表
public List< SmsData> getSmsList() {
Uri uri = Uri. parse( “content://sms/”);
ContentResolver contentResolver = mContext.getContentResolver();
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if ( null != cursor) {
Log. i( TAG, “cursor.getCount():” + cursor.getCount());
//根據Cursor一條一條的添加到smsList中
while (cursor.moveToNext()) {
int _id = cursor.getInt( cursor.getColumnIndex("_id" ));
int type = cursor.getInt( cursor.getColumnIndex(“type” ));
String address = cursor.getString( cursor.getColumnIndex( “address”));
String body = cursor.getString( cursor.getColumnIndex(“body” ));
String date = cursor.getString( cursor.getColumnIndex(“date” ));
SmsData smsData = new SmsData(_id, type, address, body, date);
smsList.add(smsData);
}
cursor.close();
}
return smsList;
}
根據content://sms/這個Uri查詢手機中短信的數據庫,得到一個Cursor,這個Cursor就包含了一條一條的短信。然后去遍歷這個Cursor將需要的數據添加到smsList中。這樣短信數據就拿到了,因為要做的功能是短信備份,所以接下來需要將smsList這個集合中的數據保存到本地,以方便短信恢復的時候去讀取保存的這個文件,那么問題來了,怎樣將smsList這個集合以文件的形式保存到本地呢?當然方法有很多,這里采用的是使用XmlSerializer將其序列化,需要恢復的時候使用XmlPullParser 將其反序列化,就可以拿到備份的數據,聽起來感覺挺高大上的,其實很簡單就是對xml的操作。
下面來看看序列化的代碼,即將上面得到的集合smsList中的數據生成一個xml文件,并保存到本地。:
//將短信數據保存到 sd卡中
public void saveSmsToSdCard(){
smsList=getSmsList();
//獲得一個序列化對象
XmlSerializer xmlSerializer=Xml.newSerializer();
//xml文件保存到sd卡中名字為"sms.xml"
File file= new File(Environment.getExternalStora geDirectory(), “sms.xml”);
FileOutputStream fos;
try {
fos = new FileOutputStream(file);
xmlSerializer.setOutput(fos, “utf-8”);
xmlSerializer.startDocument( “utf-8”, true);
xmlSerializer.startTag( null, “smss”);
xmlSerializer.startTag( null, “count”);
xmlSerializer.text( smsList.size()+ “”);
xmlSerializer.endTag( null, “count”);
for(SmsData smsData: smsList){
xmlSerializer.startTag( null, “sms”);
xmlSerializer.startTag( null, “_id”);
xmlSerializer.text(smsData.get_id()+ “”);
xmlSerializer.endTag( null, “_id”);
xmlSerializer.startTag( null, “type”);
xmlSerializer.text(smsData.getType()+ “”);
xmlSerializer.endTag( null, “type”);
xmlSerializer.startTag( null, “address”); xmlSerializer.text(smsData.getAddress()+ “”);
xmlSerializer.endTag( null, “address”);
xmlSerializer.startTag( null, “body”);
xmlSerializer.text(smsData.getBody()+ “”);
xmlSerializer.endTag( null, “body”);
xmlSerializer.startTag( null, “date”);
xmlSerializer.text(smsData.getDate()+ “”);
xmlSerializer.endTag( null, “date”);
xmlSerializer.endTag( null, “sms”);
}
xmlSerializer.endTag( null, “smss”);
xmlSerializer.endDocument();
fos.flush();
fos.close();
Toast. makeText( mContext, “備份完成”, Toast.LENGTH_SHORT ).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
通過調用以上方法就將短信以xml的形式保存到了本地。
運行這個方法后可以在sd卡中看到sms.xml文件,將其導出來可以看到它的格式如下:
注:實際上xml文件中它是一行,這里為了讓大家更清楚的看清結構,手動將其改成上述格式了。
保存到本地后,工作就剩下最后一步了,那就是將這個xml文件反序列化,并將其中的數據一條一條插入到短信的數據庫中:
//將指定路徑的xml文件中的數據插入到短信數據庫中
public void restoreSms(String path) {
File file = new File(path);
//得到一個解析 xml的對象
XmlPullParser parser = Xml.newPullParser();
try {
fis = new FileInputStream(file);
parser.setInput( fis, “utf-8”);
ContentValues values = null;
int type = parser.getEventType();
while (type != XmlPullParser. END_DOCUMENT) {
switch (type) {
case XmlPullParser.START_TAG:
if (“count”.equals(parser.getName())) {
} else if (“sms”.equals( parser.getName())) {
values = new ContentValues();
} else if (“type” .equals( parser.getName())) {
values.put( “type”, parser.nextText());
} else if (“address” .equals( parser.getName())) {
values.put( “address”, parser.nextText());
} else if (“body” .equals( parser.getName())) {
values.put( “body”, parser.nextText());
} else if (“date” .equals( parser.getName())) {
values.put( “date”, parser.nextText());
}
break;
case XmlPullParser. END_TAG:
if ( “sms”.equals(parser.getName())) {
// 如果節點是 sms
Uri uri = Uri.parse( “content://sms/”);
ContentResolver resolver = mContext.getContentResolver();
resolver.insert(uri, values);
System. out.println( “插入成功” );
values = null;
}
break;
}
type=parser.next();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到,在上述方法中判斷xml的END_TAG是不是"sms",如果是的話說明一條短信的"type"、“address”、“body”、"date"這些信息已經拿到,然后就根據Uri將這條數據插入到數據庫中,就完成一條短信的恢復,待讀到 END_DOCUMENT說明xml文件已經讀完,此時所備份的短信就全部恢復了。
3.舉例—聯系人的讀寫
在Android中,聯系人的操作都是通過一個統一的途徑來讀寫數據的,打開
/data/data/com.android.providers.contacts,可以看到聯系人的數據源:
導出這個文件,用專業的工具軟件打開看一下表結構。
對這個SQLite類型的數據源進行封裝后,聯系人就以ContentProvider的形式為其他應用進程提供聯系人的讀寫服務,我們就可以操作自己的聯系人信息了。
先添加兩個聯系人到數據源中,如圖所示:
每個聯系人都有兩個電話號碼和兩個郵箱賬號,分別為家庭座機號碼、移動手機號碼、家庭郵箱賬號和工作郵箱賬號。當然在添加聯系人時有很多其他信息,我們這里沒有填寫,只選擇了最常用的電話和郵箱,主要是方便演示這個過程。
在演示代碼之前,需要了解一下android.provider.ContactsContract這個類(注:在較早的版本中是android.provider.Contacts這個類,不過現在已被廢棄,不建議使用),它定義了各種聯系人相關的URI和每一種類型信息的屬性信息:
下面通過一個項目,來演示一下聯系人操作的具體過程。
新建一個名為provider的項目,創建一個名為ContactsReadTest的測試用例,如下:
public class ContactsReadTest extends AndroidTestCase {
//[content://com.android.contacts/contacts]
private static final Uri CONTACTS_URI = ContactsContract.Contacts.CONTENT_URI;
//[content://com.android.contacts/data/phones]
private static final Uri PHONES_URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
//[content://com.android.contacts/data/emails]
private static final Uri EMAIL_URI = ContactsContract.CommonDataKinds.Email.CONTENT_URI;
private static final String _ID = ContactsContract.Contacts._ID;
private static final String DISPLAY_NAME = ContactsContract.Contacts.DISPLAY_NAME;
private static final String HAS_PHONE_NUMBER = ContactsContract. Contacts.HAS_PHONE_NUMBER;
private static final String CONTACT_ID = ContactsContract.Data.CONTACT_ID;
private static final String PHONE_NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
private static final String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
private static final String EMAIL_DATA = ContactsContract.CommonDataKinds.Email.DATA;
private static final String EMAIL_TYPE = ContactsContract.CommonDataKinds.Email.TYPE;
public void testReadContacts() {
ContentResolver resolver = getContext().getContentResolver();
Cursor c = resolver.query(CONTACTS_URI, null, null, null, null);
while (c.moveToNext()) {
int _id = c.getInt(c.getColumnIndex(_ID));
String displayName = c.getString(c.getColumnIndex(DISPLAY_NAME));
ArrayList< String> phones = new ArrayList< String>();
ArrayList< String> emails = new ArrayList< String>();
String selection = CONTACT_ID + “=” + _id;
//獲取手機號
int hasPhoneNumber = c.getInt( c.getColumnIndex(HAS_PHONE_NUMBER));
if (hasPhoneNumber > 0) {
Cursor phc = resolver.query(PHONES_URI, null, selection, null, null);
while (phc.moveToNext()) {
String phoneNumber = phc.getString( phc.getColumnIndex(PHONE_NUMBER));
int phoneType = phc.getInt( phc.getColumnIndex(PHONE_TYPE));
phones.add(getPhoneTypeNameById( phoneType) + " : " + phoneNumber);
}
phc.close();
}
Log.i(TAG, "phones: " + phones);
//獲取郵箱
Cursor emc = resolver.query(EMAIL_URI,null, selection, null, null);
while (emc.moveToNext()) {
String emailData = emc.getString( emc.getColumnIndex(EMAIL_DATA));
int emailType = emc.getInt( emc.getColumnIndex(EMAIL_TYPE));
emails.add(getEmailTypeNameById(
emailType) + " : " + emailData);
}
emc.close();
Log.i(TAG, "emails: " + emails);
}
c.close();
}
private String getPhoneTypeNameById(int typeId) {
switch (typeId) {
case ContactsContract.CommonDataKinds .Phone.TYPE_HOME:
return “home”;
case ContactsContract.CommonDataKinds .Phone.TYPE_MOBILE:
return “mobile”;
case ContactsContract.CommonDataKinds .Phone.TYPE_WORK:
return “work”;
default: return “none”;
}
}
private String getEmailTypeNameById(int typeId) {
switch (typeId) {
case ContactsContract.CommonDataKinds .Email.TYPE_HOME:
return “home”;
case ContactsContract.CommonDataKinds .Email.TYPE_WORK:
return “work”;
case ContactsContract.CommonDataKinds .Email.TYPE_OTHER:
return “other”;
default: return “none”;
}
}
}
為了使這個測試用例運行起來,我們需要在AndroidManifest.xml中配置一下測試設備的聲明,它與< application>元素處于同一級別位置:
然后再配置使用測試類庫聲明,它與< activity>元素處于同一級別位置:
< uses-library android:name="android.test.runner"/>最后,還有一個重要的聲明需要配置,就是讀取聯系人權限,聲明如下:
< uses-permission android:name=“android.permission.READ_CONTACTS”/>
經過以上準備工作,這個測試用例就可以運轉起來了,運行testReadContacts()方法,打印結果:
聯系人里的信息都準確無誤的讀取出來了。
如果在一個Activity里運行讀取聯系人的代碼,不僅可以使用ContentResolver直接進行讀取操作(即查詢),還可以使用Activity提供的managedQuery方法方便的實現同樣的效果,看一下這個方法的具體代碼:
public final Cursor managedQuery(Uri uri,String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
if (c != null) {
startManagingCursor?;
}
return c;
}
其實它還是使用了ContentResolver進行查詢操作,但是多了一步startManagingCursor的操作,它會根據Activity的生命周期對Cursor對象進行管理,避免了一些因Cursor是否釋放引起的問題,所以非常方便,大大簡化了我們的工作量。
接下來我們嘗試將一個聯系人信息添加到系統聯系人的數據源中,實現對聯系人的寫入操作。
新建一個名為ContactsWriteTest的測試用例:
public class ContactsWriteTest extends AndroidTestCase {
//[content://com.android.contacts/raw_contacts]
private static final Uri RAW_CONTACTS_URI = ContactsContract.RawContacts.CONTENT_URI;
//[content://com.android.contacts/data]
private static final Uri DATA_URI = ContactsContract.Data.CONTENT_URI;
private static final String ACCOUNT_TYPE = ContactsContract.RawContacts.ACCOUNT_TYPE;
private static final String ACCOUNT_NAME = ContactsContract.RawContacts.ACCOUNT_NAME;
private static final String RAW_CONTACT_ID = ContactsContract.Data.RAW_CONTACT_ID;
private static final String MIMETYPE = ContactsContract.Data.MIMETYPE;
private static final String NAME_ITEM_TYPE = ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE;
private static final String DISPLAY_NAME = ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME;
private static final String PHONE_ITEM_TYPE = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
private static final String PHONE_NUMBER = ContactsContract.CommonDataKinds.Phone.NUMBER;
private static final String PHONE_TYPE = ContactsContract.CommonDataKinds.Phone.TYPE;
private static final int PHONE_TYPE_HOME = ContactsContract.CommonDataKinds.Phone.TYPE_HOME;
private static final int PHONE_TYPE_MOBILE = ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE;
private static final String EMAIL_ITEM_TYPE = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
private static final String EMAIL_DATA = ContactsContract.CommonDataKinds.Email.DATA;
private static final String EMAIL_TYPE = ContactsContract.CommonDataKinds.Email.TYPE;
private static final int EMAIL_TYPE_HOME = ContactsContract.CommonDataKinds.Email.TYPE_HOME;
private static final int EMAIL_TYPE_WORK = ContactsContract.CommonDataKinds.Email.TYPE_WORK;
private static final String AUTHORITY = ContactsContract.AUTHORITY;
public void testWriteContacts() throws Exception {
ArrayList< ContentProviderOperation> operations = new ArrayList< ContentProviderOperation>();
ContentProviderOperation operation = ContentProviderOperation.newInsert(RAW_CONTACTS_URI).withValue(ACCOUNT_TYPE, null)
.withValue(ACCOUNT_NAME, null)
.build();
operations.add(operation);
//添加聯系人名稱操作
operation = ContentProviderOperation. newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, NAME_ITEM_TYPE)
.withValue(DISPLAY_NAME, “Scott Liu”)
.build();
operations.add(operation);
//添加家庭座機號碼
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, PHONE_ITEM_TYPE)
.withValue(PHONE_TYPE, PHONE_TYPE_HOME) .withValue(PHONE_NUMBER, “01034567890”)
.build();
operations.add(operation);
//添加移動手機號碼
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, PHONE_ITEM_TYPE)
.withValue(PHONE_TYPE,PHONE_TYPE_MOBILE) .withValue(PHONE_NUMBER, “13034567890”)
.build();
operations.add(operation);
//添加家庭郵箱
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, EMAIL_ITEM_TYPE)
.withValue(EMAIL_TYPE, EMAIL_TYPE_HOME)
.withValue(EMAIL_DATA, “scott@android.com”)
.build();
operations.add(operation);
//添加工作郵箱
operation = ContentProviderOperation.newInsert(DATA_URI) .withValueBackReference(RAW_CONTACT_ID, 0)
.withValue(MIMETYPE, EMAIL_ITEM_TYPE)
.withValue(EMAIL_TYPE, EMAIL_TYPE_WORK)
.withValue(EMAIL_DATA, “scott@msapple.com”)
.build();
operations.add(operation);
ContentResolver resolver = getContext().getContentResolver();
//批量執行,返回執行結果集
ContentProviderResult[] results = resolver.applyBatch(AUTHORITY, operations);
for (ContentProviderResult result : results) {
Log.i(TAG, result.uri.toString());
}
}
}
在上面的代碼中,把整個操作分為幾個ContentProviderOperation操作,并將他們做批處理操作,也許注意到,從第二個操作開始,每一項都有一個withValueBackReference(RAW_CONTACT_ID, 0)步驟,它參照了第一項操作新添加的聯系人的id,因為是批處理,我們插入數據前并不知道id的值,不過這個不用擔心,在進行批處理插入數據時,它會重新引用新的id值,不會影響最終的結果。
當然,這個也不能忘了配置寫入聯系人的權限聲明:
< uses-permission android:name=“android.permission.WRITE_CONTACTS” />
經過以上步驟之后,運行一下testWriteContacts()方法,看看聯系人是否添加進去了:
總結
以上是生活随笔為你收集整理的Android 调用系统的ContentProvider的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue百度地图API、获取当前经纬度以及
- 下一篇: dede模板php代码,织梦dede模板