6.ContentProvider存儲
ContentProvider(內(nèi)容提供者)是Android 的四大組件之一。主要用于對外共享數(shù)據(jù),也就是說通過將ContentProvider將數(shù)據(jù)中的資源,分享給其他應(yīng)用訪問。其他應(yīng)用可以通過ContentProvider對指定應(yīng)用中的數(shù)據(jù)進(jìn)行操作。ContentProvider分為系統(tǒng)的和自定義的,系統(tǒng)的也就是例如聯(lián)系人,圖片等數(shù)據(jù)。交互關(guān)系如圖所示:
??看了這個會覺得很奇怪,為什么不直接和數(shù)據(jù)庫交互呢?還要通過ContentProvider進(jìn)行交互。主要是Android的安全性問題決定的,它的數(shù)據(jù)庫是私有的,所以外部數(shù)據(jù)無法直接訪問這個數(shù)據(jù)庫。所以提供了這個ContentProvider 內(nèi)容提供者,將數(shù)據(jù)庫的內(nèi)容提供給外部應(yīng)用,同時將外部應(yīng)用的數(shù)據(jù)存儲到數(shù)據(jù)庫中。如果外部應(yīng)用想要操作數(shù)據(jù)庫暴露的數(shù)據(jù)時,需要ContentResolver來操作ContentProvider暴露的數(shù)據(jù)。
??一旦某個應(yīng)用通過ContentProvider暴露了數(shù)據(jù),那么不管該應(yīng)用程序是否啟動,其他的應(yīng)用都能通過該接口操作暴露的數(shù)據(jù),對數(shù)據(jù)進(jìn)行增刪查改的操作。
6.1 ContentProvider的核心類
1、ContentProvider:(A應(yīng)用暴露數(shù)據(jù))
● 一個程序可以通過實現(xiàn)一個ContentProvider的抽象接口將自己的數(shù)據(jù)暴露出去;
● 外界根本看不到,也不用看到這個應(yīng)用暴露的數(shù)據(jù)在應(yīng)用當(dāng)中是如何存儲的,是用數(shù)據(jù)庫存儲還是用文件存儲,還是通過網(wǎng)上獲得,這些一切都不重要,重要的是外界可以通過這一套標(biāo)準(zhǔn)及統(tǒng)一的接口和程序里的數(shù)據(jù)打交道,可以讀取程序的數(shù)據(jù),也可以修改程序的數(shù)據(jù)。
2、ContentResolver:(操作A應(yīng)用所暴露的數(shù)據(jù))
● 外界的程序通過ContentResolver接口可以訪問ContentProvider提供的數(shù)據(jù);
● ContentResolver 可以理解成是HttpClient的作用。
3、 Uri:Uri是ContentResolver和ContentProvider進(jìn)行數(shù)據(jù)交換的標(biāo)識。
● 每個ContentProvider提供公共的URI來唯一標(biāo)識其數(shù)據(jù)集。管理多個數(shù)據(jù)集的(多個表)的 ContentProvider 為每個數(shù)據(jù)集提供了單獨的URI。
● Uri 的標(biāo)準(zhǔn)前綴:以“content://”作為前綴,這個是標(biāo)準(zhǔn)的前綴,表示該數(shù)據(jù)由ContentProvider 管理。Android所提供的ContentProvider都存放在andriod.provider這個包里面 Android的ContentProvider URI有固定的形式:content://contract/people。
● Uri 的authority部分:該部分是完整的類名。(使用小寫形式)。
● Uri 的path部分(資源部分、數(shù)據(jù)部分): 用于決定哪類數(shù)據(jù)被請求。
● 被請求的特定記錄的id值。如果請求不僅限于某個單條數(shù)據(jù),該部分及其前面的斜線應(yīng)該刪除。
● 為了將一個字符串轉(zhuǎn)換成Uri,Android中提供了Uri的parse()靜態(tài)方法來實現(xiàn)。
通配符的使用
這個 * :表示匹配任意長度的任意字符
這個 # :表示匹配任意長度的數(shù)字
【備注:】URI、URL、URN的區(qū)別:
● 首先,URI,是uniform resource identifier,統(tǒng)一資源標(biāo)識符,用來唯一的標(biāo)識一個資源。
● URL是uniform resource locator,統(tǒng)一資源定位器,它是一種具體的URI,即URL可以用來標(biāo)識一個資源,而且還指明了如何locate這個資源。
● URN,uniform resource name,統(tǒng)一資源命名,是通過名字來標(biāo)識資源,比如mailto:java-net@java.sun.com。
也就是說,URI是以一種抽象的,高層次概念定義統(tǒng)一資源標(biāo)識,而URL和URN則是具體的資源標(biāo)識的方式。URL和URN都是一種URI。
總結(jié)一下:URL是一種具體的URI,它不僅唯一標(biāo)識資源,而且還提供了定位該資源的信息。URI是一種語義上的抽象概念,可以是絕對的,也可以是相對的,而URL則必須提供足夠的信息來定位,所以,是絕對的。
6.2 ContentProvider 訪問其他程序中的數(shù)據(jù)
ContentProvider 的用法有兩種:一種是使用現(xiàn)有的內(nèi)容提供器來讀取和操作相應(yīng)的程序中的數(shù)據(jù),另一種是創(chuàng)建者自己的內(nèi)容提供器暴露程序的外部數(shù)據(jù)。Android 系統(tǒng)中自帶的電話簿,短信,媒體庫等程序都提供了類似的訪問接口,這就使得第三應(yīng)用程序可以充分利用這部分?jǐn)?shù)據(jù)來實現(xiàn)更好的功能。
6.2.1 ContentResolver的基本用法
如果一個外部應(yīng)用程序想訪問內(nèi)容提供器中暴露的數(shù)據(jù),那么就一定要借助ContentResolver()方法。Context類中提供了getContentResolver()的方法獲取該類的實例。此外ContentResolver還提供了一系列的方法對數(shù)據(jù)進(jìn)行CRUD操作。和SQLiteDatabase的使用的方法一樣,insert()插入數(shù)據(jù),update()更新數(shù)據(jù),delete()刪除數(shù)據(jù),query()查詢數(shù)據(jù)。不同的是ContentResolver中的增刪查改方法方法都是不接受表名的,而是用Uri來替代。
??上面說了Uri的一些知識點,這里再詳細(xì)說一下。Uri給內(nèi)容提供器的數(shù)據(jù)創(chuàng)建了一個唯一的標(biāo)識符,它由兩部分組成:authority 和path。authority是對不同應(yīng)用程序做區(qū)分的,一般為了避免沖突,設(shè)置為包名。eg:我的項目包名為 com.demo.filesavedemo,那么這個程序的authority 就設(shè)置為com.demo.filesavedemo.provider。path是在該應(yīng)用程序內(nèi)的對不同的表的區(qū)分。通常加到authority的后面。eg:數(shù)據(jù)庫里面有兩張表 tab1和tab2,然后path 分別命名為/tab1和/tab2,最后別忘了在頭部加上前綴content:// 。所以Uri最標(biāo)準(zhǔn)的寫法為:
content://com.demo.filesavedemo.provider/tab1
content://com.demo.filesavedemo.provider/tab2
??通過這樣的Uri,我們就可以很清楚的表達(dá)了我們想要訪問的哪個程序里面的哪張表的數(shù)據(jù)了。如果你還想訪問表tab1里面的某個數(shù)據(jù)的話,可以在/tab1的后面加上你想訪問的數(shù)據(jù)。eg:加入你想訪問表tab1里面的person變量的某一個值,那么首先找到它對應(yīng)的id 的值,比如是10,所以Uri的寫法為:content://com.demo.filesavedemo.provider/tab1/person/10。這樣你就能找到對應(yīng)的值了。如果要找10這個id對應(yīng)的name字段,再加上/name。content://com.demo.filesavedemo.provider/tab1/person/10/name。依次類推
6.2.1.1 查詢數(shù)據(jù)的操作
得到Uri對應(yīng)的字符串之后,還要將它解析成Uri對象才可以作為參數(shù)傳入。需要調(diào)用ri.parse()方法。
Uri uri=Uri.parse("content://com.demo.filesavedemo.provider/tab1")
//查詢tabl
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
query() 方法參數(shù) | 對應(yīng)的SQL部分 | 描 述 |
---|---|---|
uri | from table_name | 指定查詢某個應(yīng)用的表名 |
projection | select column1 ,column2 | 指定查詢的列名 |
selection | where column1 = value | 指定where的約束條件 |
selectionArgs | - | 為where的占位符提供具體的值 |
sortOrder | order by column1,colimn2 | 指定查詢結(jié)果的排序方式 |
查詢完成之后還是返回一個Cursor對象。取出每行中相應(yīng)列的數(shù)據(jù)。
if(cursor!=null){
while(cursor.moveToNext()){
String column1=cursor.getString(cursor.getColumnIndex("列的名字"));
int column2=cursor.getInt(cursor.getColumnIndex("列的名字"));
}
cursor.close();
}
方法上大致和SQLDatabase差不多。
6.2.1.2 添加數(shù)據(jù)的操作
SQLDatabase的添加數(shù)據(jù)庫操作,需要用上ContentValues對象,這里也是一樣:
ContentValues values=new ContentValues();
values.put("name","小王");
values.put("age",18);
getContentResolver().insert(uri,values);
6.2.1.3 更新數(shù)據(jù)庫操作
如果想要更新這新添加的數(shù)據(jù)庫的數(shù)據(jù),把name的值改為小六,然后借用update()的方法。
ContentValues values=new ContentValues();
values.put("name","小六");
getContentResolver().update(uri,values,"name=? and age = ?",new String[]{"小王","18"});
這里指定了selection 和selectionArgs,控制范圍,放置別的行受影響。
6.2.1.4 刪除數(shù)據(jù)庫的操作
加入需要刪除掉 age=18 這一列
getContentResolver.delete(uri,"age=?",new String []{"18"});
到這里,增刪查改都過了一遍,和之前的差別不大,很容易理解,不過要注意參數(shù)的意思。
6.3 創(chuàng)建自己的內(nèi)容提供器
6.3.1 UriMatcher類的介紹
我們知道Uri是ContentProvider暴露數(shù)據(jù)的關(guān)鍵,通過Uri可以指定需要查詢的表。假設(shè)有這么一個場景,在一個數(shù)據(jù)源包含有多個內(nèi)容(多張表),那么我們查詢的時候就需要多個Uri來指向?qū)?yīng)的表。那么在查詢了tab1之后還需要查詢tab2怎么辦呢?可不可以從tab1過濾到tab2呢?顯然是可以的。這時候使用UriMatcher就可以幫助我們方便的過濾到TableA還是TableB, 然后進(jìn)行下一步查詢, 如果不用UriMatcher也可以,我們就需要手動過濾字符串,用起來有點麻煩,可維護(hù)性也不好。
6.3.2 UriMatcher的使用
在使用之前可以看下UriMatcher的源碼。
private static final int PEOPLE = 1;
private static final int PEOPLE_ID = 2;
private static final int PEOPLE_PHONES = 3;
private static final int PEOPLE_PHONES_ID = 4;
private static final int PEOPLE_CONTACTMETHODS = 7;
private static final int PEOPLE_CONTACTMETHODS_ID = 8;
private static final int DELETED_PEOPLE = 20;
private static final int PHONES = 9;
private static final int PHONES_ID = 10;
private static final int PHONES_FILTER = 14;
private static final int CONTACTMETHODS = 18;
private static final int CONTACTMETHODS_ID = 19;
private static final int CALLS = 11;
private static final int CALLS_ID = 12;
private static final int CALLS_FILTER = 15;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
sURIMatcher.addURI("contacts", "people", PEOPLE);
sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);
sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);
sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);
sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE);
sURIMatcher.addURI("contacts", "phones", PHONES);
sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER);
sURIMatcher.addURI("contacts", "phones/#", PHONES_ID);
sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS);
sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID);
sURIMatcher.addURI("call_log", "calls", CALLS);
sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER);
sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);
}
很簡單,首先是實例化UriMatcher ,然后是addURI()注冊需要的Uri。源碼中是放在一個靜態(tài)塊中的,我們在開發(fā)的時候只需要這樣寫就好,后面的PEOPLE,PEOPLE_ID這些都是在返回碼。有了這些返回碼,我們才能區(qū)別Uri,也可以自己定義。
總之,UriMatcher本質(zhì)上是一個文本過濾器,用在contentProvider中幫助我們過濾,分辨出查詢者想要查詢哪個數(shù)據(jù)表。
6.3.3 創(chuàng)建自己的內(nèi)容提供器
首先點擊包->New->Other->Content Provider ,彈出的頁面上,如下所示,新建MyContentProvider 類繼承 ContentProvider,然后重寫這6個方法,再借助UriMatch類就可以實現(xiàn)匹配Uri了。
Exported屬性,表示是否允許外部程序訪問ContentProvider;Enable表示是否啟用這個ContentProvider。這樣就會自動在AndroidManifest.xml自動注冊
<provider
android:name=".ContentProvider.MyContentProvider"
android:authorities="com.demo.filesavedemo"
android:enabled="true"
android:exported="true"></provider>
下面對著幾個方法做一個簡單的介紹:
1.onCreate() ,初始化ContentProvider 的時候調(diào)用,在這里完成數(shù)據(jù)庫的創(chuàng)建和升級。返回true表示成功,返回false 表示失敗。注意只有當(dāng)ContentResolver嘗試訪問成熟中的數(shù)據(jù)的時候,內(nèi)容提供器才會初始化。
2.query() 查詢數(shù)據(jù) ,之前提過。
3.insert() 插入數(shù)據(jù) ,之前提過。
4.delete() 刪除數(shù)據(jù) , 之前提過。
5.update() 更新數(shù)據(jù),之前提過。
6.getType() 這個方法有點特別,它根據(jù)傳入的URI來返回相應(yīng)的MIME類型( MIME (Multipurpose Internet Mail Extensions) 是描述消息內(nèi)容類型的因特網(wǎng)標(biāo)準(zhǔn)。 MIME 消息能包含文本、圖像、音頻、視頻以及其他應(yīng)用程序?qū)S玫臄?shù)據(jù)。)。可以看到幾乎每個方法都帶有Uri這個參數(shù),這個參數(shù)也正是調(diào)用ContentResolver的增刪查改方法傳過來的。現(xiàn)在需要對Uri進(jìn)行解析,從中分析出希望訪問的表和數(shù)據(jù)。一個Uri所對應(yīng)的MIME字符串主要由3部分組成。Android 對這3個部分做了格式規(guī)定:
- 必須以 vnd開頭
- 如果URI以路徑結(jié)尾,則后接android.cursor.dir/,如果URI以id結(jié)尾,則后接上android.cursor.item/
-
最后接上vnd.<authority>.<path>
例如:1. content://com.demo.filesavedemo.provider/tab1 ,所對應(yīng)的MIME類型為:
???vnd.android.cursor.dir/vnd.com.demo.filesavedemo.provider.tab1
??2. content://com.demo.filesavedemo.provider/tab1/person/10,所對應(yīng)的MIME類型為:
???vnd.android.cursor.item/vnd.com.demo.filesavedemo.provider.tab1
public class Myprovider extends ContentProvider {
private static final int TABLE_DIR = 0;
private static final int TABLE_ITEM = 1;
private static final int TABLE2_DIR = 2;
private static final int TABLE2_ITEM = 3;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table1",TABLE_DIR);
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table1/#",TABLE_ITEM);
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table2",TABLE2_DIR);
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table2/#",TABLE2_ITEM);
}
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (sURIMatcher.match(uri)){
case TABLE_DIR:
return "vnd.android.cursor.dir/vnd.com.demo.filesavedemo.ContentProvider.table1";
case TABLE_ITEM:
return "vnd.android.cursor.item/vnd.com.demo.filesavedemo.ContentProvider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.demo.filesavedemo.ContentProvider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.demo.filesavedemo.ContentProvider.table2";
default:
break;
}
return null;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
switch (sURIMatcher.match(uri)){
case TABLE_DIR:
//查詢table1表中的所有的數(shù)據(jù)
break;
case TABLE_ITEM:
//查詢table1表中的單條的數(shù)據(jù)
break;
case TABLE2_DIR:
//查詢table2表中的所有的數(shù)據(jù)
break;
case TABLE2_ITEM:
//查詢table2表中的單條的數(shù)據(jù)
break;
}
return null;
}
//其他的方法和query方法一樣,都會攜帶uri,然后根據(jù)UriMatcher的match()方法來判斷,
// 出調(diào)方希望返回哪張表,再根據(jù)表來操作
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}
7.網(wǎng)絡(luò)存儲
網(wǎng)絡(luò)存儲一般都是將數(shù)據(jù)存儲在服務(wù)器或者第三方服務(wù)器等,然后android 端通過http協(xié)議/Tcp協(xié)議等網(wǎng)絡(luò)請求來獲取數(shù)據(jù)。因為首先需要自己準(zhǔn)備服務(wù)器,所以這里給一個網(wǎng)上的例子。
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import andorid.os.Bundle;
public class MyAndroidWeatherActivity extends Activity{
private static final String SERVER_URL="http://www.webservicex.net/WeatherForecast.asmx/GetWeatherByPlaceName";
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
HttpPost request=new HttpPost(SERVER_URL);//根據(jù)內(nèi)容來源地址創(chuàng)建一個Http請求
//添加一個變量
List<NameValuePair> params=new ArrayList<NameValuePair>();
//設(shè)置一個地區(qū)名稱
params.add(new BasicNameValuePair("PlaceName","NewYork"));//添加必須的參數(shù)
try{
//設(shè)置參數(shù)的編碼
request.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8));
//發(fā)送請求并獲取反饋
HttpResponse httpResponse=new DefaultHttpClient().execute(request);
//解析返回的內(nèi)容
if(httpResponse.getStatusLine().getStatusCode()!=404){
String result=EntityUtils.toString(httpResponse.getEntity());
System.out.printlin(result);
}
}catch(Exception e){
e.printStackTrace();
}}}