今日學習任務:以簡單的備忘錄應用程程序為例,實現ContentProvider,並測試。
涉及的主要內容:1) 創建ContentProvider所需的步驟 2)學習官方實例代碼(Note Pad)
1. 如何為自己的應用程序自定義ContentProvider
首先,我們得有數據。所以,需要創建一個SQLite數據庫來存儲數據。而為了訪問數據庫,我們需要提供訪問數據庫的各種接口,如創建,打開,升級等
其次,創建一個類,繼承ContentProvider類,並實現其中訪問數據的所有方法,包括:1)query():查詢 2) insert():插入 3)update():插入 4)delete():刪除 5)getType():獲得類型 6)onCreate():創建時調用。
最後,定義好的ContentProvider類必須在AndroidManifest.xml裡聲明後,才能使用。聲明中必須添加的參數是授權屬性“android:authorities”。
在實際的寫代碼過程中,目前只能好好研究developer.android.com上面的相關實例代碼,學習Google提供的良好規范的代碼:
http://developer.android.com/resources/samples/NotePad/src/com/example/android/notepad/index.html
在這個Note pad的例子中,Notepad.java和NotePadProvider.java兩個文件是與創建ContentProvider相關的兩個文件。其中Notepad.java類定義了用於可以訪問的ContentProvider的常量,之所以創建這個文件,我覺得是優化代碼架構而把常量寫在同一個類中方便調用和訪問。而NotePadProvider則主要涉及數據的相關操作,包括數據庫的創建,連接以及繼承實現ContenProvider類的6個方法。在閱讀這部分代碼時,對我來說最大的苦難是需要很好地理解URI的原理,只有這樣才能看懂Google提供的代碼的結構。關於URI的理解請參考官方文檔:
http://developer.android.com/guide/topics/providers/content-providers.html#creating 中最後部分:Content URI Summary。
令人欣慰的是,Google提供的實例代碼有詳細的注釋,這樣方便了我們去理解和學習。
2. 現在以備忘錄程序為例,來實現以上各個部分。
注:假設備忘錄程序中只有一張數據表,叫做Memo, 表中有兩個字段: _ID 和 MemoContent。
2.1 參考Notepad.java 創建 MemoContract.java 類,定義各種 主要的URI和數據表字段常量。之所以定義這個類,可以參考下面關於該類的注釋
package com.memo;import android.net.Uri;
import android.provider.BaseColumns;
/**
* Defines a contract between the Memo content provider and its clients.
* A contract defines the information that a client needs to access the provider as one or more data tables.
* A contract is a public, non-extendable (final) class that contains constants defining column names and URIs.
* A well-written client depends only on the constants in the contract.
*/
public final class MemoContract {
/**
* identification of the content provider.
*/
public static final String AUTHORITY = "com.memo.MemoProvider";
/**
* This class can not be instantiated
*/
private MemoContract(){
}
/**
* Memo table contract
*/
public static final class Memo implements BaseColumns{
/**
* This class can not be instantiated
*/
private Memo(){
}
/**
* The table name offered by this provider
*/
public static final String TABLE_NAME = "Memo";
/* URI definition */
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
/* MIME definitions */
/**
* The MIME type of {@link #CONTENT_URI} providing a directory of memo.
*/
public static final String CONTENT_TYPE= "vnd.android.cursor.dir/vnd.companyName.memo";
/**
* The MIME type of a {@link #CONTENT_URI} sub-directory of a single memo.
*/
public static final String CONTENT_ITEM_TYPE= "vnd.android.cursor.item/vnd.companyName.memo";
/* Default sort order and column definitions for this table */
/**
* The default sort order for this table
*/
public static final String DEFAULT_SORT_ORDER = _ID+" DESC";
/**
* Column name for the content of the memo
* <P>Type: TEXT</P>
*/
public static final String COLUMN_NAME_CONTENT = "MemoContent";
}
}
2.2 參考NotePadProvider.java創建了MemoProvider類,該類提供了訪問數據的主要接口,並繼承實現了ContenProvider的query()方法,其它方法類似。詳細代碼如下:
package com.memo;
import java.util.HashMap;
import com.memo.MemoContract.Memo;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
/**
* Provides the access to the data of Memo
*/
public class MemoProvider extends ContentProvider {
/**
* a new DbHelper
*/
private DbHelper dbHelper;
/**
* Create and initialize a UriMatcher instance
*/
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);;
// The incoming URI matches the Memo URI pattern
private static final int MEMOS = 1;
// The incoming URI matches the Memo ID URI pattern
private static final int MEMO_ID = 2;
static{
// Add a pattern that routes URIs terminated with "Memos" to a Memos operation
sUriMatcher.addURI(MemoContract.AUTHORITY, "Memo", MEMOS);
// Add a pattern that routes URIs terminated with "Memos" plus an integer
// to a memo ID operation
sUriMatcher.addURI(MemoContract.AUTHORITY, "Memo/#", MEMO_ID);
}
/**
* A projection map used to select columns from the database
*/
private static HashMap<String, String> ProjectionMap;
/* constants for whole database */
private static final String DATABASE_NAME= "db";
private static final int DATABASE_VERSION= 2;
/* table creation SQL statements */
public static final String CREATE_TABLE_MEMO= "create table "+Memo.TABLE_NAME+" ("
+Memo._ID+" integer primary key autoincrement, "
+Memo.COLUMN_NAME_CONTENT+" text)";
/**
* The helper class which manage the database creation and database version upgrade
*/
private class DbHelper extends SQLiteOpenHelper{
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* Create the data table by executing the SQL statement
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_MEMO);
}
/**
*
* Demonstrates that the provider must consider what happens when the
* underlying database is changed. In this sample, the database is upgraded
* by destroying the existing data.
* A real application should upgrade the database in place.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Logs that the database is being upgraded
Log.w(DATABASE_NAME, "Upgrading database from version " + oldVersion + " to " + newVersion
+ ", which will destroy all old data");
// Kills the table and existing data
db.execSQL("DROP TABLE IF EXISTS Memo");
// Recreates the database with a new version
onCreate(db);
}
}
/**
*
* Initializes the provider by creating a new DbHelper. onCreate() is called
* automatically when Android creates the provider in response to a resolver request from a
* client.
*/
@Override
public boolean onCreate() {
// Creates a new helper object. Note that the database itself isn't opened until
// something tries to access it, and it's only created if it doesn't already exist.
dbHelper=new DbHelper(getContext());
// Assumes that any failures will be reported by a thrown exception.
return true;
}
/**
* This method is called when a client calls
* {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)}.
* Queries the database and returns a cursor containing the results.
*
* @return A cursor containing the results of the query. The cursor exists but is empty if
* the query returns no results or an exception occurs.
* @throws IllegalArgumentException if the incoming URI pattern is invalid.
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Constructs a new query builder and sets its table name
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
/**
* Choose the projection and adjust the "where" clause based on URI pattern-matching.
*/
switch (sUriMatcher.match(uri)) {
// If the incoming URI is for menos, chooses the Memos projection
case MEMOS:
qb.setTables(Memo.TABLE_NAME);
qb.setProjectionMap(ProjectionMap);
break;
/* If the incoming URI is for a single memo identified by its ID, chooses the
* memo ID projection, and appends "_ID = <MemoID>" to the where clause, so that
* it selects that single memo
*/
case MEMO_ID:
qb.setTables(Memo.TABLE_NAME);
qb.setProjectionMap(ProjectionMap);
qb.appendWhere(
Memo._ID + // the name of the ID column
"=" +
// the position of the memo ID itself in the incoming URI
uri.getPathSegments().get(1));
break;
default:
// If the URI doesn't match any of the known patterns, throw an exception.
throw new IllegalArgumentException("Unknown URI " + uri);
}
String orderBy;
// If no sort order is specified, uses the default
if (TextUtils.isEmpty(sortOrder)) {
orderBy = Memo.DEFAULT_SORT_ORDER;
} else {
// otherwise, uses the incoming sort order
orderBy = sortOrder;
}
// Opens the database object in "read" mode, since no writes need to be done.
SQLiteDatabase db = dbHelper.getReadableDatabase();
/*
* Performs the query. If no problems occur trying to read the database, then a Cursor
* object is returned; otherwise, the cursor variable contains null. If no records were
* selected, then the Cursor object is empty, and Cursor.getCount() returns 0.
*/
Cursor c = qb.query(
db, // The database to query
projection, // The columns to return from the query
selection, // The columns for the where clause
selectionArgs, // The values for the where clause
null, // don't group the rows
null, // don't filter by row groups
orderBy // The sort order
);
// Tells the Cursor what URI to watch, so it knows when its source data changes
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getType(Uri arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
// TODO Auto-generated method stub
return null;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
// TODO Auto-generated method stub
return 0;
}
}