ContentProvider

#

適用場景和概念介紹
摘自
http://www.cnblogs.com/devinzhang/archive/2012/01/20/2327863.html(不得不說,這是一篇很好的文章)

  1. 適用場景

    1) ContentProvider爲存儲和讀取數據提供了統一的接口

    2) 使用ContentProvider,應用程序可以實現數據共享

    3) android內置的許多數據都是使用ContentProvider形式,供開發者調用的(如視頻,音頻,圖片,通訊錄等)

  2. 相關概念介紹

    1)ContentProvider簡介

    當應用繼承ContentProvider類,並重寫該類用於提供數據和存儲數據的方法,就可以向其他應用共享其數據。雖然使用其他方法也可以對外共享數據,但數據訪問方式會因數據存儲的方式而不同,如:採用文件方式對外共享數據,需要進行文件操作讀寫數據;採用sharedpreferences共享數據,需要使用sharedpreferences API讀寫數據。而使用ContentProvider共享數據的好處是統一了數據訪問方式。

    2)Uri類簡介

    Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")

    在Content Provider中使用的查詢字符串有別於標準的SQL查詢。很多諸如select, add, delete, modify等操作我們都使用一種特殊的URI來進行,這種URI由3個部分組成, “content://”, 代表數據的路徑,和一個可選的標識數據的ID。以下是一些示例URI:

     content://media/internal/images  這個URI將返回設備上存儲的所有圖片
     content://contacts/people/  這個URI將返回設備上的所有聯繫人信息
     content://contacts/people/45 這個URI返回單個結果(聯繫人信息中ID爲45的聯繫人記錄)
    

    儘管這種查詢字符串格式很常見,但是它看起來還是有點令人迷惑。爲此,Android提供一系列的幫助類(在android.provider包下),裏面包含了很多以類變量形式給出的查詢字符串,這種方式更容易讓我們理解一點,因此,如上面content://contacts/people/45這個URI就可以寫成如下形式:

    Uri person = ContentUris.withAppendedId(People.CONTENT_URI,  45);

要創建我們自己的Content Provider的話,我們需要遵循以下幾步:

a. 創建一個繼承了ContentProvider父類的類

b. 定義一個名爲CONTENT_URI,並且是public static final的Uri類型的類變量,你必須爲其指定一個唯一的字符串值,最好的方案是以類的全名稱, 如:

public static final Uri CONTENT_URI = Uri.parse( “content://com.google.android.MyContentProvider”);

c. 定義你要返回給客戶端的數據列名。如果你正在使用Android數據庫,必須爲其定義一個叫_id的列,它用來表示每條記錄的唯一性。

d. 創建你的數據存儲系統。大多數Content Provider使用Android文件系統或SQLite數據庫來保持數據,但是你也可以以任何你想要的方式來存儲。

e. 如果你要存儲字節型數據,比如位圖文件等,數據列其實是一個表示實際保存文件的URI字符串,通過它來讀取對應的文件數據。處理這種數據類型的Content Provider需要實現一個名爲_data的字段,_data字段列出了該文件在Android文件系統上的精確路徑。這個字段不僅是供客戶端使用,而且也可以供ContentResolver使用。客戶端可以調用ContentResolver.openOutputStream()方法來處理該URI指向的文件資源;如果是ContentResolver本身的話,由於其持有的權限比客戶端要高,所以它能直接訪問該數據文件。

f. 聲明public static String型的變量,用於指定要從遊標處返回的數據列。

g. 查詢返回一個Cursor類型的對象。所有執行寫操作的方法如insert(), update() 以及delete()都將被監聽。我們可以通過使用ContentResover().notifyChange()方法來通知監聽器關於數據更新的信息。

h. 在AndroidMenifest.xml中使用<provider>標籤來設置Content Provider。

i. 如果你要處理的數據類型是一種比較新的類型,你就必須先定義一個新的MIME類型,以供ContentProvider.geType(url)來返回。MIME類型有兩種形式:一種是爲指定的單個記錄的,還有一種是爲多條記錄的。這裏給出一種常用的格式:

vnd.android.cursor.item/vnd.yourcompanyname.contenttype (單個記錄的MIME類型)

比如, 一個請求列車信息的URI如

content://com.example.transportationprovider/trains/122 

可能就會返回typevnd.android.cursor.item/vnd.example.rail這樣一個MIME類型。

vnd.android.cursor.dir/vnd.yourcompanyname.contenttype (多個記錄的MIME類型)

比如, 一個請求所有列車信息的URI如

content://com.example.transportationprovider/trains 

可能就會返回vnd.android.cursor.dir/vnd.example.rail這樣一個MIME 類型。

  


栗子

講解Provider之前,我們把例子的準備工作做好。
一個是獲取數據的API
一個是數據庫中的表格
一個是訪問數據庫的DbHelper
最後纔是Provider。


1. API

使用openweathermap的API獲取天氣

http://api.openweathermap.org/data/2.5/forecast/daily?q=xxx&mode=xxx&units=xxx&cnt=xxx&APPID=xxxxxxxxxxx";

一個例子中的幾個類:

WeatherDbHelper:extends SQLiteOpenHelper  數據庫操作
WeatherContract:類中對應的表及一些操作
WeatherProvider:extends ContentProvider

2. SQLite

存儲,表

table: location

Name 類型 對應LocationEntry類中的String值
location_setting COLUMN_LOCATION_SETTING
coord_lat COLUMN_COORD_LAT
coord_long COLUMN_COORD_LONG
city_name COLUMN_CITY_NAME

主要記錄 城市名、經緯度、setting

table:weather

Name 類型 備註
location_id location表與weather表的連接處 ,Foreign Key
date long 存儲格式是毫秒級(後期需要處理)
weather_condition_id int 由API返回的,主要用於確定用什麼天氣圖標
short_desc varchar 簡短描述天氣狀況,API返回的有短有長,eg: “clear” VS “sky is clear”
min float 最低溫
max float 最高溫
humidity float 溼度
pressure float 氣壓
wind float 風力
degrees float 風向

WeatherContract

這個類中有WeatherEntry和LocationEntry倆類,對應表中各項。

BASE

//impaments BaseColumns,這個類裏定義了 _ID = "_id";
public static final class LocationEntry implements BaseColumns {

    public static final String TABLE_NAME = "location" ;

    public static final String COLUMN_LOCATION_SETTING = "location_setting";
    public static  final String COLUMN_COORD_LAT = "coord_lat";
    public static final String COLUMN_COORD_LONG = "coord_long";
    public static final String COLUMN_CITY_NAME = "city_name";

}

public static final class WeatherEntry implements BaseColumns {

    public static final String TABLE_NAME = "weather";

    // Column with the foreign key into the location table.
    public static final String COLUMN_LOC_KEY = "location_id";

    public static final String COLUMN_DATE = "date";

    public static final String COLUMN_WEATHER_CONTIDITION_ID = "weather_condition_id";
    public static final String COLUMN_SHORT_DESC = "short_desc";


    public static final String COLUMN_MIN_TEMP = "min";
    public static final String COLUMN_MAX_TEMP = "max";

    public static final String COLUMN_HUMIDITY = "humidity";
    public static final String COLUMN_PRESSURE = "pressure";
    public static final String COLUMN_WIND_SPEED = "wind";

    // Degrees are meteorological degrees (e.g, 0 is north, 180 is south).  Stored as floats.
    public static final String COLUMN_DEGREES = "degrees";

}

MORE

WeatherContract中更多定義:

//根據Uri簡介中介紹,我們需要設置一個這樣的常量,便於進行查詢等功能。
public static final String CONTENT_AUTHORITY = "com.xxxx.app";
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

public static final String PATH_WEATHER = "weather";
public static final String PATH_LOCATION = "location";

在各自類(表)中也需要有一些常量的定義,以及 必要的方法

// LocationEntry中
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_LOCATION).build();

//DIR 代表 返回的Cursor中包含0或多條記錄
public static final String CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;
//ITEM 代表 返回的Cursor中爲特定ID的一條記錄
public static final String CONTENT_ITEM_TYPE =
                ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;


// WeatherEntry中
public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_WEATHER).build();

public static final String CONTENT_TYPE =
                ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
public static final String CONTENT_ITEM_TYPE =
                ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER; 

由於Location中作爲存儲Weather的location部分,(Location附屬於Weather)
故而Location中只用一個根據id取location數據的方法就夠了

public static Uri buildLocationUri(long _id)
{
    return ContentUris.withAppendedId(CONTENT_URI, 
}

對於Weather表,則需要較多方法

//返回根據某id進行查詢的Uri
public static Uri buildWeatherUri(long id){
    return ContentUris.withAppendedId(CONTENT_URI , id);
}
//返回根據某位置查詢的Uri。(locationSetting爲右上角菜單中的Setting的location)
public static Uri buildWeatherLocation(String locationSetting){
    return CONTENT_URI.buildUpon().appendPath(locationSetting).build();
}


public static Uri buildWeatherLocationWithStartDate(String locationSetting , long startDate){
    long normalizedDate = normalizeDate(startDate); //
    return CONTENT_URI.buildUpon().appendPath(locationSetting).appendQueryParameter(COLUMN_DATE , Long.toString(normalizedDate)).build();

}


public static Uri buildWeatherLocationWithDate(String locationSetting , long date){

    return CONTENT_URI.buildUpon().appendPath(locationSetting).appendPath(Long.toString(normalizeDate(date)).build();

}

// 獲取Uri中的部分數據
public static String getLocationSettingFromUri(Uri uri){
    //  content://com.xxx.app/location/...
    return uri.getPathSegments().get(1);
}
public static long getStartDateFromUri(Uri uri){

    String dateString = uri.getQueryParameter(COLUMN_DATE);
    if(null != dateString && dateString.length() > 0)
        return Long.parseLong(dateString);
    else return 0;  
}
public static long getDateFromUri(Uri uri){
    return Long.parseLong(uri.getPathSegments().get(2)); 
}


WeatherDbHelper

在Android中要想使用Sqlite數據庫,首先應該創建一個類繼承SQLiteOpenHelper類,我們把這個類命名爲DatabaseHelper,它作爲一個訪問Sqlite的助手類,提供了兩方面的功能:
第一 getReadableDatabase()/getWritableDatabase()可以獲得SQLiteDatabase對象,通過該對象可以對數據庫進行操作;
第二 提供OnCreate()和onUpgrade()兩個回調函數,允許我們在創建和升級數據庫時,進行自己的操作;

public class WeatherDbHelper extends SQLiteOpenHelper{

    //打Log時,通常會用的標記 實用
    private static final String LOG_TAG = WeatherDbHelper.class.getSimpleName();

    //if you change the database schema , you must increment the database version
    private static final int DATABASE_VERSION = 2 ; 
    static final String DATABASE_NAME = "weather.db";

    public WeatherDbHelper(Context context){
        super(context , DATABASENAME , null  , DATABASE_VERSION);
    }
}

注:在繼承SQLiteOpenHelper類時,必須要有public DatabaseHelper(Context context, String name, CursorFactory factory,int version) 這個構造方法。

不得不說這個DATABASE_VERSION很重要,當數據庫已經成功建立以後,如果修改了數據庫中的表等,需要改變這個VERSION值,不然程序會崩潰的。

Override SQLiteOpenHelper中的onCreate等方法

onCreate(SQLiteDatabase):
該函數是在第一次創建數據庫的時候執行,實際上是在第一次得到SQLiteDatabse對象的時候,纔會調用這個方法


@Override
public void onCreate(SQLiteDatabase sqLiteDatabase){
    // weather表
    // FOREIGN KEY (location) REFERENCES location (xxid)

    //編輯器還有待改進啊,引號是怎麼了,不起作用了。(這裏的引號有些問題,我先用單引號替換一下,莫怪)
    final String SQL_CREATE_WEATHER_TABLE = 'CREATE TABLE' + 
        WeatherEntry.TABLE_NAME + " ( " +
        WeatherEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT ," +
        WeatherEntry.COLUMN_LOC_KEY + " INTEGER NOT NULL, " +
        WeatherEntry.COLUMN_DATE + " INTEGER NOT NULL, " +
        WeatherEntry.COLUMN_SHORT_DESC + " TEXT NOT NULL, " +
        WeatherEntry.COLUMN_WEATHER_CONTIDITION_ID + " INTEGER NOT NULL," +

        WeatherEntry.COLUMN_MIN_TEMP + " REAL NOT NULL, " +
        WeatherEntry.COLUMN_MAX_TEMP + " REAL NOT NULL, " +

        WeatherEntry.COLUMN_HUMIDITY + " REAL NOT NULL, " +
        WeatherEntry.COLUMN_PRESSURE + " REAL NOT NULL, " +
        WeatherEntry.COLUMN_WIND_SPEED + " REAL NOT NULL, " +
        WeatherEntry.COLUMN_DEGREES + " REAL NOT NULL, " +

        // Set up the location column as a foreign key to location table.
        " FOREIGN KEY (" + WeatherEntry.COLUMN_LOC_KEY + ") REFERENCES " +
        LocationEntry.TABLE_NAME + " (" + LocationEntry._ID + "), " +

        // To assure the application have just one weather entry per day per location, it's created a UNIQUE constraint with REPLACE strategy
        " UNIQUE (" + WeatherEntry.COLUMN_DATE + ", " +
        WeatherEntry.COLUMN_LOC_KEY + ") ON CONFLICT REPLACE);";

    //location表

    final String SQL_CREATE_LOCATION_TABLE = "CREATE TABLE " + LocationEntry.TABLE_NAME + "(" +

       LocationEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "+

       LocationEntry.COLUMN_LOCATION_SETTING + " TEXT UNIQUE NOT NULL , " +
       LocationEntry.COLUMN_CITY_NAME + " TEXT NOT NULL, "+
       LocationEntry.COLUMN_COORD_LAT + " REAL NOT NULL, "+
       LocationEntry.COLUMN_COORD_LONG + " REAL NOT NULL ); "
    ;

     sqLiteDatabase.execSQL(SQL_CREATE_WEATHER_TABLE);
     sqLiteDatabase.execSQL(SQL_CREATE_LOCATION_TABLE);

}

onUpgrade(SQLiteDatabase , int oldVersion , int newVersion)

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {

        sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + LocationEntry.TABLE_NAME);
        sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + WeatherEntry.TABLE_NAME);
        onCreate(sqLiteDatabase);

    }

WeatherProvider

Uri都已經在WeatherContract中定義好了,現在就開始利用這些準備工作了。

public class WeatherProvider extends ContentProvider {

    //The URI Matcher used by this content provider
    private UriMatcher sUriMatcher = buildUriMatcher();//自行將自定義的Uri註冊好
    private WeatherDbHelper mOpenHelper ;


    //重寫getType()方法 
    //Use the Uri Matcher to determine what kind of URI this is
    @Override
    public String getType(Uri uri){

    }

    @Override
    public Cursor query(Uri uri , String[] projection , String selection , String[] selectionArgs, String sortOrder){

    }
    @Override
    public boolean onCreate(){
            mOpenHelpre = new WeatherDbHelper(getContext());
            return true;    
    }


    @Override
    public Uri insert(Uri uri , ContentValues values){

    }

    @Override int delete(Uri uri ,String selection , String[] selectionArgs){

    }

    @Override
    public Uri update(
        Uri uri , ContentValues values , String selection , String[] selectionArgs){


    }

    @Override
    public int bulkInsert(Uri uri , ContentValues[] values){

    }


}

buildUriMatcher()方法就是需要註冊自定義的URI等,返回一個URIMatcher對象。這是一個靜態方法。
註冊了4個Uri

static UriMatcher buildUriMatcher() {

    final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    final String authority = WeatherContract.CONTENT_AUTHORITY;

    //使用addURI方法,將自定義的Uri添加,也就是註冊吧。從WeatherContract中獲取定義好的常量。(String authority , String path ,int code)
    sURIMatcher.addURI(authority , WeatherContract.PATH_WEATHER , WEATHER);
    sURIMatcher.addURI(authority , WeatherContract.PATH_WEATHER+"/*" , WEATHER_WITH_LOCATION);
    sURIMatcher.addURI(authority , WeatherContract.PATH_WEATHER + "/*/#" ,WEATHER_WITH_LOCATION_AND_DATE);
    sURIMatcher.addURI(authority , WeatherContract.PATH_LOCATION , LOCATION);

    return sURIMatcher;
}

關於UriMatcher類
https://developer.android.com/reference/android/content/UriMatcher.html

addURI方法中的第三個參數 code的定義爲:

    static final int WEATHER = 100;
    static final int WEATHER_WITH_LOCATION = 101;
    static final int WEATHER_WITH_LOCATION_AND_DATE = 102;
    static final int LOCATION = 300;

完成了Uri的註冊後,還需要重寫getType(Uri uri)方法:
對應於註冊,根據解析出來的code(match),返回是Location表的內容,還是Weather表的內容,dir(多個)還是item(單個)。

官方文檔中這麼說的

Then when you need to match against a URI, call match(Uri), providing the URL that you have been given. You can use the result to build a query, return a type, insert or delete a row, or whatever you need, without duplicating all of the if-else logic that you would otherwise need. For example:

    @Override
    public String getType(Uri uri){
        final int match = sUriMatcher.match(uri);

        switch (match) {
            case WEATHER_WITH_LOCATION_AND_DATE:
                return WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
            case WEATHER_WITH_LOCATION:
                return WeatherContract.WeatherEntry.CONTENT_TYPE;
            case WEATHER:
                return WeatherContract.WeatherEntry.CONTENT_TYPE;
            case LOCATION:
                return WeatherContract.LocationEntry.CONTENT_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri:" + uri); 
        }

    }

OK,現在來做查詢這個步驟
查詢數據庫的結果數據通常存儲在Cursor中
根據所給Uri match的結果,分別執行對應的查詢,
SQLiteDatabase中,query方法

query(table , columns , selection , selectionArgs , groupBy , having , sortOrder, limit)
參數 類型 說明
table String 表名
columns String[] 返回的數據列的名字,如果是全部列的話,則置爲null即可
selection String 對行的過濾,相當於sql的where語句,如果返回全部行,則置爲null
selectionArgs String[] 過濾行的條件參數
groupBy String sql基本語法
having String sql基本語法
sortOrder String 對返回行進行排序
limit String

這裏列出了較多的參數部分,不用的參數設置爲null即可,或者重寫參數較少的query方法。

    @Override
    public Cursor query(Uri uri , String[] projection , String selection , String[] slectionArgs, String sortOrder){

        Cursor retCursor;

        switch (sUriMatcher.match(uri)) {
            // "weather/*/#"
            case WEATHER_WITH_LOCATION_AND_DATE: {
                retCursor = getWeatherByLocationSettingAndDate(uri , projection, sortOrder);
                break;
            }   
            // "weather/*"
            case WEATHER_WITH_LOCATION: {
                retCursor = getWeatherByLocationSetting(uri , projection , sortOrder);
                break;
            }
            // "weather"
            case WEATHER: {
                retCursor = mOpenHelper.getReadableDatabase().query(
                        WeatherContract.WeatherEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            }
            // "location"
            case LOCATION: {
                retCursor = mOpenHelper.getReadableDatabase().query(
                        WeatherContract.LocationEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            }
        }
        // **************NOTICE*****************        
        retCursor.setNotificationUri(getContext().getContentResolver() , uri);
        return retCursor;
    }

NOTICE的內容,按住Ctrl ,鼠標點擊進去,可以看到源碼部分
裏面註釋說:Uri有改變時,對該Cursor發出通知。
查看AbstractCursor.setNotificationUri源碼時,你會發現,這個方法的作用就是在ContentResolver上註冊了一個Observer。
好吧,Java內置的觀察者模式。。。(跳過,再分析吧)

由於前兩個case中包含了一些對於location和date的操作,所以呢,單獨提取出來,作爲一個函數,會清晰一些;另外,對於這兩個case,需要同時對兩個表(weather & location)操作,需要用到 SQLiteQueryBuilder。

SQLiteQueryBuilder代碼部分,這裏麪包含有對靜態塊的使用

Java靜態變量的初始化,這個鏈接講的挺好的。
http://blog.csdn.net/lihongye_10/article/details/16844543

 private static final SQLiteQueryBuilder sWeatherByLocationSettingQueryBuilder;

 static{
        sWeatherByLocationSettingQueryBuilder = new SQLiteQueryBuilder();

        //This is an inner join which looks like
        //weather INNER JOIN location ON weather.location_id = location._id
        sWeatherByLocationSettingQueryBuilder.setTables(
                WeatherContract.WeatherEntry.TABLE_NAME + " INNER JOIN " +
                        WeatherContract.LocationEntry.TABLE_NAME +
                        " ON " + WeatherContract.WeatherEntry.TABLE_NAME +
                        "." + WeatherContract.WeatherEntry.COLUMN_LOC_KEY +
                        " = " + WeatherContract.LocationEntry.TABLE_NAME +
                        "." + WeatherContract.LocationEntry._ID);
    }

使用SQLiteQueryBuilder進行查詢,相應的,SQLiteQueryBuilder的query方法也對應有其參數。

有一點需要說明,這個方法沒有用到提供的selection和selectionArgs,因爲這個方法名本身就包含它的查詢含義了

    //location.location_setting = ?
    private static final String sLocationSettingSelection =
            WeatherContract.LocationEntry.TABLE_NAME+
                    "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? ";

    //location.location_setting = ? AND date >= ?
    private static final String sLocationSettingWithStartDateSelection =
            WeatherContract.LocationEntry.TABLE_NAME+
                    "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND " +
                    WeatherContract.WeatherEntry.COLUMN_DATE + " >= ? ";

    //location.location_setting = ? AND date = ?
    private static final String sLocationSettingAndDaySelection =
            WeatherContract.LocationEntry.TABLE_NAME +
                    "." + WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ? AND " +
                    WeatherContract.WeatherEntry.COLUMN_DATE + " = ? ";

    //以上三條變量就對應着與 Location表和Weather表 間需要進行查詢的selection。

//固定地點和日期,返回零條或一條數據 item
private Cursor getWeatherByLocationSettingAndDate(
        Uri uri , String[] projection , String sortOrder){

    String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
    long date = WeatherContract.WeatherEntry.getDateFromUri(uri);

    return sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
            projection,
            sLocationSettingAndDaySelection,
            new String[]{locationSetting , Long.toString(date)},
            null,
            null,
            sortOrder
    );
}

//固定地點,沒有date限制,或大於某date。返回0條或多條數據 dir
private Cursor getWeatherByLocationSetting(Uri uri,String[] projection, String sortOrder){

        String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
        long startDate = WeatherContract.WeatherEntry.getStartDateFromUri(uri);

        String[] selectionArgs;
        String selection;

        if (startDate == 0) {
            selection = sLocationSettingSelection;
            selectionArgs = new String[]{locationSetting};
        } else {
            selectionArgs = new String[]{locationSetting, Long.toString(startDate)};
            selection = sLocationSettingWithStartDateSelection;
        }

        return sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
                projection,
                selection,
                selectionArgs,
                null,
                null,
                sortOrder
        );

}

接下來的 插入 ,刪除,更新 等方法,執行完畢,需要notify一下,這是觀察者模式下的一個步驟。

insert方法:要麼給location表添加數據,要麼給weather表添加數據

 @Override
    public Uri insert(Uri uri, ContentValues values) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        Uri returnUri;

        switch (match) {
            case WEATHER: {
                normalizeDate(values);
                long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, values);
                if ( _id > 0 )
                    returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
                else
                    throw new android.database.SQLException("Failed to insert row into " + uri);
                break;
            }
            case LOCATION:{
                long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME , null , values);
                if (_id > 0)
                    returnUri = WeatherContract.LocationEntry.buildLocationUri(_id);
                else
                    throw new android.database.SQLException("Failed to insert row into " + uri);
                break;
            }
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        //****************NOTICE********************
        getContext().getContentResolver().notifyChange(uri, null);
        return returnUri;
    }

delete方法:

@Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Student: Start by getting a writable database
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        int rowsDeleted ;

        if(null == selection) selection ="1";//see db.delete,
        switch (match){
            case WEATHER:{
                rowsDeleted = db.delete(WeatherContract.WeatherEntry.TABLE_NAME,selection , selectionArgs);
                break;
            }
            case LOCATION:{
                rowsDeleted = db.delete(WeatherContract.LocationEntry.TABLE_NAME , selection , selectionArgs);
                break;
            }
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        // Student: Use the uriMatcher to match the WEATHER and LOCATION URI's we are going to
        // handle.  If it doesn't match these, throw an UnsupportedOperationException.

        // Student: A null value deletes all rows.  In my implementation of this, I only notified
        // the uri listeners (using the content resolver) if the rowsDeleted != 0 or the selection
        // is null.
        // Oh, and you should notify the listeners here.

        // Student: return the actual rows deleted
        if(rowsDeleted != 0)
        {
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return rowsDeleted;
    }

update方法:

@Override
public int update(

        Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        // Student: This is a lot like the delete function.  We return the number of rows impacted by the update.

        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        int rowsUpdated;

        switch (match){
            case WEATHER:
                normalizeDate(values);
                rowsUpdated = db.update(WeatherContract.WeatherEntry.TABLE_NAME , values , selection , selectionArgs);
                break;
            case LOCATION:
                rowsUpdated = db.update(WeatherContract.LocationEntry.TABLE_NAME , values , selection, selectionArgs);
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: "+ uri);
        }
        if(rowsUpdated != 0){
            getContext().getContentResolver().notifyChange(uri , null);
        }
        return rowsUpdated;
    }

值得一提的是bulkInsert方法,當一次性存儲多條數據時,如果使用insert方法,則會多次打開關閉數據庫,這個時候,應該採用事務,Transaction。(這裏就不多說了)
bulkInsert方法

  @Override
    public int bulkInsert(Uri uri, ContentValues[] values) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case WEATHER:
                db.beginTransaction();
                int returnCount = 0;
                try {
                    for (ContentValues value : values) {
                        normalizeDate(value);
                        long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, value);
                        if (_id != -1) {
                            returnCount++;
                        }
                    }
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
                getContext().getContentResolver().notifyChange(uri, null);
                return returnCount;
            case LOCATION:
                db.beginTransaction();
                returnCount = 0;
                try {
                    for (ContentValues value : values) {
                        normalizeDate(value);
                        long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, value);
                        if (_id != -1) {
                            returnCount++;
                        }
                    }
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
                getContext().getContentResolver().notifyChange(uri, null);
                return returnCount;
            default:
                return super.bulkInsert(uri, values);
        }
    }

這幾個方法中,有調用一個對date操作的函數,normalizeDate,其實就是WeatherContract當中 normalizeDate的方法的調用。

這裏給出代碼

 private void normalizeDate(ContentValues values) {
        // normalize the date value
        if (values.containsKey(WeatherContract.WeatherEntry.COLUMN_DATE)) {
            long dateValue = values.getAsLong(WeatherContract.WeatherEntry.COLUMN_DATE);
            values.put(WeatherContract.WeatherEntry.COLUMN_DATE, WeatherContract.normalizeDate(dateValue));
        }
 }

調用Provider

前面給出了一系列的Provider的講解,那麼問題來了,什麼時候調用自定義的Provider呢?我查了一下資料。

之後,讓我們來使用這個定義好的Content Provider:

1)爲應用程序添加ContentProvider的訪問權限。

2)通過getContentResolver()方法得到ContentResolver對象。

3)調用ContentResolver類的query()方法查詢數據,該方法會返回一個Cursor對象。

4)對得到的Cursor對象進行分析,得到需要的數據。

5)調用Cursor類的close()方法將Cursor對象關閉。

其中通過ContentResolver的調用,在源碼當中,就是對Provider的調用

不好意思,我又來粘鏈接了,但是人家寫的還是挺好懂的
http://aijiawang-126-com.iteye.com/blog/656488


總結

寫得還是有點亂了,這個其實是Udacity中的Sunshine的例子,我覺得講的挺不錯的

https://www.udacity.com/course/viewer#!/c-ud853/l-1614738811/e-1664298679/m-1664298681
可以選擇課程1,2等等。

獲取數據庫數據等內容,就下節說。


Content Provider(這也是摘自別處的內容)

  1. ContentProvider簡介

    在Android官方指出的Android的數據存儲方式共有五種,分別是:SharedPreferences、網絡存儲、文件存儲、外存儲、SQLite。但是我們知道一般這些存儲都只是在單獨的一個應用程序之中達到一個數據的共享,有時候我們需要操作其他應用程序的一些數據,例如我們需要操作系統裏的媒體庫、通訊錄等,這時我們就可能通過ContentProvider來滿足我們的需求了。

  2. 爲什麼選擇ContentProvider?

    ContentProvider向我們提供了在應用程序之間共享數據的一種機制。
    (1)ContentProvider爲存儲和獲取數據提供了統一的接口。ContentProvider對數據進行封裝,不用關心數據存儲的細節。使用表的形式存儲數據
    (2)Android爲常見的一些數據提供了默認的ContentProvider(包括音頻、視頻、圖片和通訊錄等)

  3. Uri介紹

    Uri類似於URL請求
    格式:
    content://com.example.android.sunshine.app/location/94043
    content:// 是固定的
    com.example.android.sunshine.app URI的標識,Authority:一般是定義該ContentProvider的包名。
    /location 路徑,通俗來說就是你要操作的數據庫中表的名字
    /94043 如果URI中包含表示需要獲取的記錄的ID;則就返回該ID對應的數據,如果沒有ID,則表示返回全部;

  4. UriMatcher類

    因爲Uri代表了要操作的數據,所以我們經常需要解析Uri,並從Uri中獲取數據。Android系統提供了兩個用於操作Uri的工具類,分別爲UriMatcher和ContentUris。
    UriMatcher類用於匹配Uri。

    首先第一步把你需要匹配Uri路徑全部給註冊上,如下:

    //常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
    UriMatcher  sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    //如果match()方法匹配content://com.bing.procvide.personprovider/person路徑,返回匹配碼爲1
    sMatcher.addURI("com.bing.procvide.personprovider", "person", 1);
    //添加需要匹配uri,如果匹配就會返回匹配碼
    sMatcher.addURI("com.bing.provider.personprovider", "person/#", 2);//#號爲通配符
    
    switch (sMatcher.match(Uri.parse("content://com.ljq.provider.personprovider/person/10"))) { 
       case 1
         break;
       case 2
         break;
       default://不匹配
         break;
    }
  5. ContentUris類使用介紹

    ContentUris類用於操作Uri路徑後面的ID部分,它有兩個比較實用的方法:
    withAppendedId(uri, id)用於爲路徑加上ID部分:

    Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")
    Uri resultUri = ContentUris.withAppendedId(uri, 10); 
    //生成後的Uri爲:content://com.bing.provider.personprovider/person/10
    parseId(uri)方法用於從路徑中獲取ID部分:
    Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person/10")
    long personid = ContentUris.parseId(uri);//獲取的結果爲:10
  6. 使用ContentProvider共享數據

    ContentProvider類主要方法

    public boolean onCreate():
        在ContentProvider創建後就會被調用,Android開機後,ContentProvider在其它應用第一次訪問它時纔會被創建。
    public Uri insert(Uri uri, ContentValues values):
        供外部應用向ContentProvider中添加數據
    public int delete(Uri uri , String[] selectionArgs):
        供外部應用從ContentProvider刪除數據
    public int update(Uri uri , ContentValues values,String selection , String[] selectionArgs):
        供外部應用更新ContentProvider中的數據。
    public Cursor query(Uri uri ,String[] projection,String selection , String[] selectionArgs,String sortOrder):
        供外部應用從ContentProvider中獲取數據。
    public String getType(Uri uri):
        用於返回當前Uri所代表數據的MIME類型。

    針對MIME類型:
    集合類型:MIME類型字符串應該以vnd.android.cursor.dir/開頭,
    ContentResolver.CURSOR_DIR_BASE_TYPE
    非集合類型:MIME類型字符串應該以vnd.android.cursor.item/開頭,
    ContentResolver.CURSOR_ITEM_BASE_TYPE

    • CursorResolver
    • SimpleCursorAdapter

Tips
Android Studio 快捷鍵:

Ctrl + 斜槓:
註釋或取消註釋當前行或選中的代碼塊,雙斜槓的方式 “//”

Ctrl + shift + 斜槓:
註釋或取消註釋當前行或選中的代碼塊, “/……/”

發佈了59 篇原創文章 · 獲贊 16 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章