編輯:關於Android編程
Content Providers是Android中四大組件之一,用於管理應用程序訪問結構化的數據。Content Providers可以壓縮數據(They encapsulate the data),並保護訪問數據的安全。Content Providers是應用程序訪問跨進程數據的標准接口(standard interface that connects data in one process with code running in another process)。
本文將介紹Content Providers的相關知識,如需訪問官方原文,您可以點擊這個鏈接:《Content Providers》。
使用content provider訪問數據,您應當在自己的應用程序的Context
環境中使用ContentResolver
對象,並將該對象作為client端,與content provider通信(實際是與ContentProvider
的子類對象通信):content provider接收clients端發送的請求,並根據請求執行操作,最後將結果返回給client端。
如果您不打算把自己應用程序的數據分享給其他應用,那麼無需創建自己的provider 。但是,如果需要在自己的程序中提供定制的搜索建議( to provide custom search suggestions in your own application)、或是從自己的應用向其他應用拷貝或粘貼大型而復雜的數據文件( copy and paste complex data or files from your application to other applications),那麼您需要創建provider。
Android系統提供了各式各樣的provider,如 音頻(audio)、 視頻(video)、 圖片(images) 、聯系人信息(personal contact information) 等。您可以在android.provider
包中查看系統提供的provider。當然,訪問部分provider需要系統提供訪問權限。
provider是Android應用程序的一部分。使用provider提供的數據可以進行UI展示。然而,provider最常用的情形是將本應用的數據提供給其他應用程序使用。使用provider的client端(實際上是content resolver)來訪問provider。也就是說,provider一般與resolver配對使用,provider提供了訪問數據的標准接口,可以方便resolver進行安全的跨進程通信(handles inter-process communication and secure data access)。
本節將討論如下內容:
content providers 是如何工作的;
從content provider中檢索數據API的使用;
content provider中增刪改查API的使用;
其他content provider的API;
content provider可以向外部應用程序提供關系型數據庫中的一張或多張表的信息。表中的每一行表示一種數據類型或某個人的信息詳情。每一列代表一個字段。
比如說,Android系統個內置了一個provider,該provider提供了一張用戶字典表(user dictionary),表中保存了用戶經常輸入的不標准的單詞,如下所示:
上表中,每一行表示一個單詞的所有信息。每列表示單詞的統計信息,如使用頻率 等。其中_ID
字段是表的主鍵,它被自動賦予。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KCjxocj4KCjxwPjxzdHJvbmc+o6HH69ei0uKjunByb3ZpZGVyzOG5qbXEse2yu9K7tqix2NDrzOG5qdb3vPyjrLy0sePM4bmp1ve8/KOsw/vX1tKysrvSu7aosdjQ68rHPGNvZGU+X0lEPC9jb2RlPqGjtavKx6OsyPTE+tDo0qq9q3Byb3ZpZGVyzOG5qbXEse2w87aotb08Y29kZT5MaXN0VmlldzwvY29kZT7Jz6OsxMfDtLjDse2x2NDrsPy6rNb3vPyjrMfSw/vX1rHY0OvKxzxjb2RlPl9JRDwvY29kZT6jqGlmIHlvdSB3YW50IHRvIGJpbmQgZGF0YSBmcm9tIGEgcHJvdmlkZXIgdG8gYSBMaXN0Vmlldywgb25lIG9mIHRoZSBjb2x1bW4gbmFtZXMgaGFzIHRvIGJlIF9JRKOpoaM8L3N0cm9uZz48L3A+Cgo8aHI+Cgo8aDMgaWQ9"訪問provideraccessing-a-provider">訪問provider(Accessing a provider)
在應用程序中使用ContentResolver
對象,可以訪問其他應用程序的provider,在該類中提供了與ContentProvider
類中命名相同的方法( identically-named methods),包括增刪改查( “CRUD” (create, retrieve, update, delete))。
ContentResolver
所在進程與ContentProvider
是不同的進程,所以這無形中實現了跨進程通信( inter-process communication)。而ContentProvider
實際上還充當了抽象層,該層介於數據倉庫和外部提供數據接口之間(acts as an abstraction layer between its repository of data and the external appearance of data as tables)。
!請注意:為了使ContentResolver
能訪問ContentProvider
的數據,您可能需要在manifest文件中加入訪問權限。
舉例來說,若需要獲得上表中word列和locale列的數據,需要在您的應用程序中調用ContentResolver.query()
方法,接著該方法又會調用ContentProvider.query()
方法,該方法由用戶字典provider提供。下面演示了訪問過程:
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table
mProjection, // The columns to return for each row
mSelectionClause // Selection criteria
mSelectionArgs, // Selection criteria
mSortOrder); // The sort order for the returned rows
下表解釋了query(Uri,projection,selection,selectionArgs,sortOrder)
方法中的參數含義:
內容URI指向了provider中提供的一組數據,內容URI由兩部分組成,其中authority部分指定了需要訪問的provider名,path部分指定了該provider中的某張表。
在上述代碼中,CONTENT_URI
指向了單詞表,ContentResolver
從CONTENT_URI
中解析出authority,並在系統中查找能與之匹配的provider。找到provider後,該provider利用URI的path部分尋找匹配的表。
所以,如上所述,words表應具有如下URI:
content://user_dictionary/words
其中user_dictionary
為provider的authority,words
為表的path,而前綴content://
(scheme)則恆定不變,表示這是一個內容URI。
您也可以在URI後綴一個_ID
來查詢表中的某一行數據:
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
這表示希望查詢單詞表中_ID
為4 的那一行數據。當您需要刪除或更新某一行數據時,應當使用這種加入了_ID
的URI。
!請注意:Uri類
與Uri.Builder類
提供了便捷的方法,把String
包裝為格式規范的URI
對象(well-formed URI objects from strings),而ContentUris類
同樣提供了便捷的方法,在URI後追加ID,上述代碼的withAppendedId()
就是為URI追加ID的方法。
本節中仍以字典數據表作為示例。
為了清晰起見,本節中調用ContentResolver.query()
方法查詢表中的數據都在UI線程中進行。但是在實際開發中,這應該是一個異步操作——方法應在子線程中進行(should do queries asynchronously on a separate thread)。方法之一是使用CursorLoader
類。有關CursorLoader
的相關內容,您可以參考我翻譯的文檔:《Android官方文檔之App Components(Loaders)》。
從provider中查詢數據,應按照如下操作進行:
聲明讀取數據的訪問權限(Request the read access permission for the provider);
發送查詢請求(Define the code that sends a query to the provider) ;
為了從provider中檢索數據,您的應用需要在manifest中聲明讀的權限(read access permission):使用
標簽包含,並指定需要訪問的provider。當程序安裝時,該權限被隱式授予。
在用戶字典provider所在應用程序的manifest中定義了android.permission.READ_USER_DICTIONARY
權限。若您的應用程序需要訪問該provider,則需要聲明這個權限。
參考第一段代碼片段,查詢條件可以按照如下方式定義:
// A "projection" defines the columns that will be returned for each row
String[] mProjection =
{
UserDictionary.Words._ID, // Contract class constant for the _ID column name
UserDictionary.Words.WORD, // Contract class constant for the word column name
UserDictionary.Words.LOCALE // Contract class constant for the locale column name
};
// Defines a string to contain the selection clause
String mSelectionClause = null;
// Initializes an array to contain selection arguments
String[] mSelectionArgs = {""};
下面的代碼片段演示了 從UI界面中獲取用戶輸入的單詞,並在字典表中查詢表中是否包含該單詞,若包含,用Cursor對象返回該單詞在表中所在行的信息(如鍵入頻率等):
/*
* This defines a one-element String array to contain the selection argument.
*/
String[] mSelectionArgs = {""};
// Gets a word from the UI
mSearchString = mSearchWord.getText().toString();
// Remember to insert code here to check for invalid or malicious input.
// If the word is the empty string, gets everything
if (TextUtils.isEmpty(mSearchString)) {
// Setting the selection clause to null will return all words
mSelectionClause = null;
mSelectionArgs[0] = "";
} else {
// Constructs a selection clause that matches the word that the user entered.
mSelectionClause = UserDictionary.Words.WORD + " = ?";
// Moves the user's input string to the selection arguments.
mSelectionArgs[0] = mSearchString;
}
// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table
mProjection, // The columns to return for each row
mSelectionClause // Either null, or the word the user entered
mSelectionArgs, // Either empty, or the string the user entered
mSortOrder); // The sort order for the returned rows
// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
/*
* Insert code here to handle the error. Be sure not to use the cursor! You may want to
* call android.util.Log.e() to log this error.
*
*/
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {
/*
* Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
* an error. You may want to offer the user the option to insert a new row, or re-type the
* search term.
*/
} else {
// Insert code here to do something with the results
}
上述查詢相當於使用如下的SQL語句:
SELECT _ID, word, locale FROM words WHERE word = ORDER BY word ASC
若由provider管理的是一個SQL數據庫,那麼用戶輸入的某些內容可能會對數據庫造成破壞。
考慮下面這種情況:
// Constructs a selection clause by concatenating the user's input to the column name
String mSelectionClause = "var = " + mUserInput;
上面是用戶輸入的需要在表中查詢的內容。
這種輸入方式會給數據庫帶來隱患:用戶可以輸入諸如"nothing; DROP TABLE *;"
的內容,當程序讀入用戶的輸入時,mSelectionClause 變量中的內容將是
var = nothing; DROP TABLE *;`,這會導致數據庫中的這張表被刪除!
為了避免上述情況發生,應使用"?"
作為占位符,以分離SQL語句和條件篩選參數,因為這種以輸入方式,系統將不再把用戶的輸入作為SQL語句的一部分,用戶無法再惡意破壞數據庫的內容( Because it’s not treated as SQL, the user input can’t inject malicious SQL)。方式如下:
// Constructs a selection clause with a replaceable parameter
String mSelectionClause = "var = ?";
// Defines an array to contain the selection arguments
String[] selectionArgs = {""};
// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;
client端的ContentResolver.query()
方法返回一個Cursor
對象,它指向了按篩選條件查詢的表的結果。調用Cursor
中的方法,您可以遍歷結果中的每一行,並通過表中的字段名得到該行中的每一列信息,當provider中的數據發生改變時,Cursor
中的數據自動改變(automatically update the object when the provider’s data changes),或者觸發observer 對象中的方法( trigger methods in an observer object when the Cursor changes)。
若沒有行(rows)能匹配查詢的條件,Cursor
對象的Cursor.getCount()
方法將返回0。
若查詢過程發生了錯誤,Cursor
對象將返回null,或者拋出一個異常。
由於Cursor返回的是一個列表,可以通過SimpleCursorAdapter
將列表的內容顯示在ListView上。如下所示:
/ Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
UserDictionary.Words.WORD, // Contract class constant containing the word column name
UserDictionary.Words.LOCALE // Contract class constant containing the locale column name
};
// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};
// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // The application's Context object
R.layout.wordlistrow, // A layout in XML for one row in the ListView
mCursor, // The result from the query
mWordListColumns, // A string array of column names in the cursor
mWordListItems, // An integer array of view IDs in the row layout
0); // Flags (usually none are needed)
// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);
!請注意:為了使ListView能顯示Cursor中的數據,Cursor中必須有一列為_ID
字段,即使ListView中不顯示該列,Cursor也必須包含該列。所以在建表時,應為表設置_ID
字段。
下面代碼演示了如何從Cursor
中獲取word字段中的數據。
// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers may throw an Exception instead of returning null.
*/
if (mCursor != null) {
/*
* Moves to the next row in the cursor. Before the first movement in the cursor, the
* "row pointer" is -1, and if you try to retrieve data at that position you will get an
* exception.
*/
while (mCursor.moveToNext()) {
// Gets the value from the column.
newWord = mCursor.getString(index);
// Insert code here to process the retrieved word.
...
// end of while loop
}
} else {
// Insert code here to report an error if the cursor is null or the provider threw an exception.
}
在應用程序中,需要為provider設置權限。其他應用程序只有獲得了權限,才能訪問provider。為provider設置權限保證了provider知曉訪問它的應用程序想要獲取什麼數據(ensure that the user knows what data an application will try to access)。這保證了provider所管理的數據的安全。
如果provider未指定任何權限,其他任何應用將無法訪問(If a provider’s application doesn’t specify any permissions, then other applications have no access to the provider’s data)。不過,無論provider是否制定了權限,與provider處於同一應用中的組件有完全的讀寫provider的權限(components in the provider’s application always have full read and write access, regardless of the specified permissions)。
正如上面提到的,用戶字典provider需要android.permission.READ_USER_DICTIONARY
權限才能被其他應用程序訪問,當然,該權限只是具有了讀取provider中數據的能力,如還需對provider所管理的數據進行增、刪、改 等操作( inserting, updating, or deleting data),還需要聲明android.permission.WRITE_USER_DICTIONARY
權限。
聲明這些權限需要在manifest中的
中進行。這些聲明的權限將在應用安裝時以列表的形式呈獻給用戶,若用戶安裝了應用,則用戶默許了列表中的權限;否則,用戶反對列表中的任何權限,程序都將安裝失敗。
聲明讀取(read)用戶字典provider的權限如下:
與查詢類似,使用resolver同樣可以對provider進行增刪改操作:
調用ContentResolver.insert()
方法可向provider中插入數據。該方法向provider中的某個表中插入一行數據,並將該行數據的內容URI(content URI)返回。下面演示了向用戶字典表中插入一條數據:
// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;
...
// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();
/*
* Sets the values of each column and inserts the word. The arguments to the "put"
* method are "column name" and "value"
*/
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
mNewUri = getContentResolver().insert(
UserDictionary.Word.CONTENT_URI, // the user dictionary content URI
mNewValues // the values to insert
);
如果您在添加一行時,不打算為某個字段添加數據,可以調用ContentValues.putNull()
傳入空值。
在增加一行數據時,無需指定_ID
字段的值,因為一般情況下,將_ID
字段設為主鍵(primary key)和自增長(auto increment)。該字段將被自動賦值。
返回的newUri
的格式如下:
content://user_dictionary/words/
上面的
表示新加入的_ID
號,如需從返回的URI
對象中獲取該_ID
值,可以調用ContentUris.parseId()
方法。
在client段調用ContentResolver.update()
方法可以修改一條數據,您同樣可以使用ContentValues
對象修改數據,如需將某一字段下的數據清空,直接傳入null
即可。
下面演示了將provider管理的用戶字典表中的locale
字段中含有"en"
的數據條目值清空,返回值是修改過的行數。
// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();
// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?";
String[] mSelectionArgs = {"en_%"};
// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;
...
/*
* Sets the updated value and updates the selected words.
*/
mUpdateValues.putNull(UserDictionary.Words.LOCALE);
mRowsUpdated = getContentResolver().update(
UserDictionary.Words.CONTENT_URI, // the user dictionary content URI
mUpdateValues // the columns to update
mSelectionClause // the column to select on
mSelectionArgs // the value to compare to
);
與查詢類似,調用ContentResolver.update()
方法刪除表中指定的數據,下面演示了刪除表的appid的字段中包含user
的條目。
// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};
// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;
...
// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
UserDictionary.Words.CONTENT_URI, // the user dictionary content URI
mSelectionClause // the column to select on
mSelectionArgs // the value to compare to
);
除了用戶字典表中的text類型,provider還支持下列類型的操作:
integer;
long integer (long);
floating point;
long floating point (double)。
provider還經常使用Binary Large OBject (BLOB)類型,它是一個最大容量為64KB的字節數組(64KB byte array)。您可以查詢Cursor
類中的若干”get”方法來查看provider支持的類型。
provider為每一個訪問或返回client端的內容URI(content URI
)對應了一個MIME數據類型(Providers also maintain MIME data type information for each content URI they define),通過該MIME數據類型可以判斷您的應用程序是否支持該類型。當provider提供了復雜的數據類型時,您可能需要MIME數據類型的幫忙(You usually need the MIME type when you are working with a provider that contains complex data structures or files)。如,通訊錄應用程序中的contact provider包含一張叫做ContactsContract.Data
的表,在該表中的每一行,就是通過MIME類型標記聯系人的數據信息。如需獲得content URI
中的MIME數據類型,可以調用ContentResolver.getType()
方法。
除了上面介紹的訪問content provider的方式以外,還有三種方式:
批量訪問(Batch access):您可以使用ContentProviderOperation
類對provider進行批量訪問。並在client調用ContentResolver.applyBatch()
方法實現批量操作。
CursorLoader
類來實現。
當需要對表中的多行數據、或需要對多表進行操作時,這需要批量操作。除此之外,實現數據庫的事務操作(原子操作,an atomic operation)也需要批量操作。為了實現批量模式,需要一組ContentProviderOperation
對象,並在client端調用ContentResolver.applyBatch()
方法,向該方法中傳入provider的authority,而不是content URI,因為這可以對不同表進行操作。ContentResolver.applyBatch()
方法將返回一組結果。
向應用程序發送intent,即便沒有訪問該應用程序中provider的權限,您依然可以訪問provider的數據。甚至通過返回的intent還能獲得URI權限( receiving back a result intent containing “URI” permissions)。您可以設置flag獲取返回的intent中的權限:
讀取權限:FLAG_GRANT_READ_URI_PERMISSION
;
寫入權限:FLAG_GRANT_WRITE_URI_PERMISSION
provider在manifest中通過android:grantUriPermission
屬性、以及provider的子標簽
定義了URI權限。
下面的例子演示了如何從通訊錄應用中的provider管理的表中獲取聯系人信息(無需聲明READ_CONTACTS
權限):
調用startActivityForResult()
方法,傳入Intent
,Intent
應包含ACTION_PICK
的action,和contact的MIME類型CONTENT_ITEM_TYPE
;
這會啟動通訊錄應用的selection
activity,該activity會切至前台;
在selection
activity中,用戶會選擇某個聯系人並修改其信息,此時,setResult(resultcode, intent)
方法將被回調,並將intent回傳至您的應用中,intent中包含了用戶選擇的聯系人的content URI
,並在extra中包含了FLAG_GRANT_READ_URI_PERMISSION
flag。該flag提供了您的應用程序訪問聯系人通訊中指定數據的權限(These flags grant URI permission to your app to read data for the contact pointed to by the content URI)。最後,selection
activity調用finish()
destroy。
您的activity切回前台並回調onActivityResult()
方法獲取回傳的intent,從Intent中獲取的content URI可以直接訪問聯系人應用中的provider數據而無需在manifest中聲明權限。
provider可以返回標准或定制的MIME類型的字符串:
MIME類型的標准形式:
type/subtype
比如,常用的MIME類型text/html
,表示其指向的數據包含text
類型和html
子類型。若provider返回的URI中包含的MIME類型為text/html
,那麼通過該URI返回的結果將包含HTML
標簽。
定制的MIME類型,也稱作vendor-specific MIME 類型,它具有更復雜的結構:
其中type類型中:
vnd.android.cursor.dir
表示URI指向的數據包含多行。而vnd.android.cursor.item
表示包含一行。
而subtype
時provider定制的(provider-specific),系統自帶的provider通常包含一個簡單的subtype
,如通訊錄應用中增加一行電話號碼的記錄,那麼指向它的URI中的MIME類型將為:
vnd.android.cursor.item/phone_v2
要求: 輸入文件名,文件內容分別存儲在手機內存和外存中,並且都可以讀去取出來。 步驟: 1.創建一個名為CDsaveFile的Android項目2.編寫布局文件activ
模仿小米安全中心檢測效果廢話少說,咱們先上效果圖:github地址: https://github.com/niniloveyou/GradeProgressView這個
SlidingMenu側滑菜單是一種比較新的設置界面或配置界面的效果,在主界面左滑或者右滑出現設置界面效果,能方便的進行各種操作。很多優秀的應用都采用了這種界面方案,像f
紅米pro和紅米note3哪個好?下面小編帶來了兩部手機的對比評測,一起來看看吧!紅米pro和紅米note3對比評測: 紅米pro介紹: 紅米pro采用