Android基礎筆記(十三)- 內容提供者原理和簡單使用
- 為什麽要有內容提供者
- 內容提供者的工作原理
- 使用內容解析者對內容提供者進行增刪改查操作
- 利用內容提供者和內容解析者備份手機短信
- 利用內容提供者插入短信
為什麽要有內容提供者
- 內容提供者技術的目的是:
- 把私有數據庫的數據的內容暴露給外部使用;
我們知道,微信、QQ等應用都能夠讀取手機中聯系人和短信的數據。而聯系人和短信都是系統內置的應用,它們的數據都存儲在相應的數據庫中。
在com.android.provider.telephony/databases/mmssms.db
就是短信的數據庫,通過以下的圖能夠知道mmssms.db
的權限為-rw-rw---
相同的com.android.provider.contacts/databases/contact2.db
是聯系人的數據庫。相同也是私有的權限。
既然它們都是私有的。可是又有需求獲取裏面的數據應該怎麽辦?Google提供者了內容提供者的技術,就能夠解決這一需求。
內容提供者的工作原理
由於數據庫是私有的,外部應用不能夠直接訪問。那麽便在應用內部使用內容提供者,向外部暴露接口,這樣外部能夠借助“中間人”訪問到內部的數據庫。原理圖例如以下:
- 內容提供者的編寫過程例如以下:
- ①創建數據庫、表。由於內容提供者主要是把私有數據庫數據提供給外部訪問。
- ②寫一個類繼承
ContentProvider
類,並重寫當中的onCreate()
、query()
、insert()
、update()
、delete()
、getType()
等方法。 - ③確定主機名(
authority
),加入路徑匹配規則。此處會利使用到UriMatcher
類。 - ④在清單文件裏聲明內容提供者。並加入關鍵屬性
android:authorities
- ⑤編寫內容提供者的
onCreate()
、query()
、insert()
、update()
、delete()
等方法。
接下來就一步一步把全部步驟說清。
第一步,創建數據庫、表。非常easy寫一個繼承SQliteOpenHelper
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
public MySQLiteOpenHelper(Context context) {super(context, "account.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 創建數據庫,並初始化兩條語句
db.execSQL("create table account (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
db.execSQL("insert into account (‘name‘,‘money‘) values (‘張三‘,‘2000‘)");
db.execSQL("insert into account (‘name‘,‘money‘) values (‘李四‘,‘5000‘)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
}
第二步,寫一個類繼承ContentProvider
類,並重寫當中方法。主要都是一些骨架的代碼。也沒什麽難度。
public class CopyOfAccountContentProvider extends ContentProvider {
// 內容提供者初始化的時候調用
@Override
public boolean onCreate() {
return true;
}
// 提供給外部應用調用的查詢方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
// 提供給外部應用調用的添加方法
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
// 提供給外部應用調用的刪除方法
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
// 提供給外部應用調用的更新方法
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
// 沒有什麽用,此處就不寫了
return null;
}
}
第三步,從第三步開始就比較關鍵了。要確定主機名(authority
),加入路徑匹配規則,此處是使用內容解析者時。調用其它應用私有數據庫所使用的。代碼例如以下。對了這些代碼是寫在內容提供者類中的:
public static final int QUERY_SUCCESS = 0;
public static final int UPDATE_SUCCESS = 1;
public static final int DELETE_SUCCESS = 2;
public static final int INSERT_SUCCESS = 3;
// UriMatcher是一個工具類。用於幫助內容提供者匹配URIs
private static final UriMatcher MURI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
// 加入路徑匹配規則
static {
// 加入一個path(匹配規則),假設匹配成功會返回code值。
// authority:主機名(我們能夠自定義。任意寫,當使用內容解析者時會使用到)
// path:路徑。通常是操作名稱
// code:響應碼
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "insert", INSERT_SUCCESS);
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "delete", DELETE_SUCCESS);
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "update", UPDATE_SUCCESS);
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "query", QUERY_SUCCESS);
}
第四步。內容提供者是四大組件之中的一個,四大組件都須要在清單文件裏聲明。在清單文件裏聲明內容提供者。並加入關鍵屬性android:authorities
,這個屬性的內容和加入路徑匹配規則addURI()
的第一個參數必須一致。代碼例如以下:
<provider
android:name="com.bzh.contentprovider.AccountContentProvider"
android:authorities="com.bzh.account.contentprovider" >
</provider>
第五步,編寫內容提供者的onCreate()
、query()
、insert()
、update()
、delete()
等方法。這些方法的內容都是使用SQLiteDatabase
內部的方法直接調用,也比較簡單。
private SQLiteDatabase db;
private static final String ACCOUNT = "account";
// 內容提供者初始化的時候調用
@Override
public boolean onCreate() {
MySQLiteOpenHelper mySQLiteOpenHelper = new MySQLiteOpenHelper(getContext());
db = mySQLiteOpenHelper.getWritableDatabase();
return true;
}
// 提供給外部應用調用的查詢方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推斷路徑是否匹配成功
if (MATCH_CODE == QUERY_SUCCESS) {
// 調用SQLiteDatabase中的方法查詢私有的數據庫
return db.query(ACCOUNT, projection, selection, selectionArgs, null, null, null);
}
return null;
}
// 提供給外部應用調用的添加方法
@Override
public Uri insert(Uri uri, ContentValues values) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推斷路徑是否匹配成功
if (MATCH_CODE == INSERT_SUCCESS) {
long insertResult = db.insert(ACCOUNT, null, values);
// 此處的Uri能夠任意寫
return Uri.parse("com.bzh.account.contentprovider-result:" + insertResult);
}
return null;
}
// 提供給外部應用調用的刪除方法
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推斷路徑是否匹配成功
if (MATCH_CODE == DELETE_SUCCESS) {
return db.delete(ACCOUNT, selection, selectionArgs);
}
return 0;
}
// 提供給外部應用調用的更新方法
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推斷路徑是否匹配成功
if (MATCH_CODE == UPDATE_SUCCESS) {
return db.update(ACCOUNT, values, selection, selectionArgs);
}
return 0;
}
@Override
public String getType(Uri uri) {
// 沒有什麽用。此處就不寫了
return null;
}
至此,整個內容提供者就編寫完成了,接下就是怎樣使用了,詳細怎樣使用將在下一節講述。
使用內容解析者對內容提供者進行增刪改查操作
回憶一下上面的原理圖,訪問私有數據庫的內容提供者已經準備完成,家下來是使用內容解析者間接調用數據庫內容的時候了。
相對於創建內容提供者而言,使用解析者調用提供者提供的方法就簡單非常多了。
創建一個工程,並加入幾個增刪改查的button,布局之類的比較簡單不貼了。請看圖:
接下來要做的事情就是。當點擊button時調用內容提供者相應的方法。當中最值得註意的是Uri.parse()
內容的寫法。
Uri uri = Uri.parse("content://com.bzh.account.contentprovider/query");
對於這句內容而言,主要由三部分組成–①content://②主機地址/③路徑
,而這部分是固定的寫法,當中主機地址是內容提供者清單文件裏聲明的authorities
屬性;而路徑是內容提供者中UriMatcher.addURI()
第二個參數相應的值。
以下是代碼:
public void insert(View v) {
// 獲取到內容解析者
ContentResolver resolver = getContentResolver();
// 匹配路徑和數據
Uri url = Uri.parse("content://com.bzh.account.contentprovider/insert");
ContentValues values = new ContentValues();
values.put("name", "別誌華");
values.put("money", "999");
// 使用解析者借助內容提供者向私有數據庫插數據
Uri insert = resolver.insert(url, values);
System.out.println("數據插入---結果:" + insert.toString());
}
public void delete(View v) {
// 獲取到內容解析者
ContentResolver resolver = getContentResolver();
// 匹配路徑和數據
Uri url = Uri.parse("content://com.bzh.account.contentprovider/delete");
// 使用解析者借助內容提供者向私有數據庫插數據
int delete = resolver.delete(url, "name=?"
, new String[] { "別誌華" });
System.out.println("刪除數據---影響行數:" + delete);
}
public void update(View v) {
// 獲取到內容解析者
ContentResolver resolver = getContentResolver();
// 匹配路徑和數據
Uri uri = Uri.parse("content://com.bzh.account.contentprovider/update");
ContentValues values = new ContentValues();
values.put("money", "999");
// 使用解析者借助內容提供者向私有數據庫插數據
int update = resolver.update(uri, values, "name=?"
, new String[] { "別誌華" });
System.out.println("更新數據---影響行數:" + update);
}
public void query(View v) {
// 獲取內容解析者
ContentResolver resolver = getContentResolver();
// com.bzh.account.contentprovider是在上一個應用清單文件裏authorities的屬性值
// 固定寫法:content://主機地址/路徑
Uri uri = Uri.parse("content://com.bzh.account.contentprovider/query");
// 調用內容解析者的查詢方法
Cursor cursor = resolver.query(uri, null, null, null, null);
// 拿出遊標中的數據
if (cursor != null && cursor.getCount() > 0) {
while (cursor.moveToNext()) {
String name = cursor.getString(1);
String money = cursor.getString(2);
System.out.println("查詢數據---數據內容(姓名:" + name + ",錢:" + money + ")");
}
// 關閉遊標
cursor.close();
}
}
測試圖例如以下:
到此為止,怎樣定義一個內容提供者和怎樣使用內容提供者已經解說完了。
利用內容提供者和內容解析者備份手機短信
像“微信電話薄”應用。能夠讀取手機中的短信數據。我們知道,手機中的短信應用內部維護者一個數據庫,用於存儲短信數據。
而“微信電話薄”就是讀取系統短信應用的數據來達到自己的目的的,可是系統短信應用的數據庫是私有的。那麽外部的應用怎樣拿到私有數據庫的數據?你想的沒錯。Google工程師在短信應用中為我們提供了內容提供者,供我們開發者使用。
在上一節中,我們知道想要使用其它應用提供的內容提供者,須要知道主機名和路徑。那麽從哪裏獲取這些信息呢?這就須要去查看源代碼了。我這裏查看的是4.4的源代碼。源代碼的路徑為android4.4\packages\providers\TelephonyProvider
。
文件夾結構例如以下圖:
在上一節中。寫自己的提供提供者時都是在清單文件裏聲明內容提供者。並聲明主機名。讓我們來看一下吧。
<provider android:name="SmsProvider"
android:authorities="sms"
android:multiprocess="false"
android:exported="true"
android:readPermission="android.permission.READ_SMS"
android:writePermission="android.permission.WRITE_SMS" />
主機名是sms
。而且從中可一知道使用該內容提供者,須要提供讀取短信和寫短信的權限。
知道了主機名,接下來就是去查看路徑了。找到src\com\android\providers\telephony\SmsProvider.java
文件,並搜索UriMatcher
關鍵字,能夠得到例如以下的結果:
private static final UriMatcher sURLMatcher =
new UriMatcher(UriMatcher.NO_MATCH);
static {
sURLMatcher.addURI("sms", null, SMS_ALL);
sURLMatcher.addURI("sms", "#", SMS_ALL_ID);
sURLMatcher.addURI("sms", "inbox", SMS_INBOX);
sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID);
sURLMatcher.addURI("sms", "sent", SMS_SENT);
sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID);
sURLMatcher.addURI("sms", "draft", SMS_DRAFT);
sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID);
... ... ...
}
知道了主機名和路徑後。在看一下短信數據庫的表結構,要不然我們怎麽獲取數據!從中能夠發如今sms表中address
、date
、body
分別代表著發送人、時間、短信內容;
接下來。使用內容解析者查詢數據並使用XML序列化器,把數據序列化到XML文件裏。短信備份就算完成了,代碼例如以下:
// 內容解析者
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms");
Cursor cursor = resolver.query(uri, new String[] { "address", "date", "body" }, null, null, null);
if (cursor != null && cursor.getCount() > 0) {
// XML序列化器
XmlSerializer serializer = Xml.newSerializer();
FileOutputStream fos;
try {
fos = new FileOutputStream(new File(Environment.getExternalStorageDirectory().getPath() + "/backsms.xml"));
// 初始化XML序列化器參數
serializer.setOutput(fos, "UTF-8");
// 文檔開始
serializer.startDocument("UTF-8", true);
// 根節點開始
serializer.startTag(null, "smss");
while (cursor.moveToNext()) {
// 元素結點開始
serializer.startTag(null, "sms");
// 序列化發送人
serializer.startTag(null, "address");
String address = cursor.getString(0);
serializer.text(address);
serializer.endTag(null, "address");
serializer.startTag(null, "date");
String date = cursor.getString(1);
serializer.text(date);
serializer.endTag(null, "date");
serializer.startTag(null, "body");
String body = cursor.getString(2);
serializer.text(body);
serializer.endTag(null, "body");
System.out.println("短信發送人:" + address + ",日期:" + date + ",內容:" + body);
serializer.endTag(null, "sms");
}
serializer.endTag(null, "smss");
serializer.endDocument();
} catch (Exception e) {
e.printStackTrace();
}
cursor.close();
}
利用內容提供者插入短信
做完備份短信,再做一個插入短信玩玩吧。
首先提供一個界面:
代碼也比較簡單:
String body = et_body.getText().toString().trim();
String sender = et_sender.getText().toString().trim();
// 拿到內容解析者
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms");
ContentValues values = new ContentValues();
values.put("address", sender);
values.put("body", body);
// 插入數據
resolver.insert(uri, values);
測試結果:
利用提供者和解析這備份和插入短信還是比較簡單的,有難度的在於備份聯系人和插入聯系人。這些將在下一個博客講述。
Android基礎筆記(十三)- 內容提供者原理和簡單使用