在OneDrive雲端空間建立Git遠端數據庫
1.在OneDrive目錄下新增新資料夾,命名為TestGit
2.進到TestGit點擊右鍵,開啟Git Bash
3.在Git Bash中輸入命令,git init --bare
4.執行指令後,TestGit內會建立一些資料如hook、info、object等(建立遠端數據庫)
5.在除了OneDrive目錄以外的目錄下點擊右鍵,開啟Git Bash
6.在Git Bash中輸入指令,git clone file///c/Users/user/OneDrive/TestGit
7.執行指令後,建立TestGit目錄(建立本地端數據庫)
8.完成以上步驟就可以進行push、pull等等將專案資料進行版本管理
除了OneDrive也可以選擇其他雲端空間如GoogleDrive、Dropbox,將遠端數據庫建立在雲端空間。
建立TestGit目錄(本地端數據庫)後,就可以將專案建立在TestGit目錄下,利用git完成版本控制。
星期五, 9月 25, 2015
星期日, 5月 24, 2015
Android Interface Definition Language (AIDL) (一)
Android Interface Definition Language (AIDL)
行程間通訊(IPC,Inter-Process Communication),指至少兩個行程或執行緒間傳送資料或訊號的一些技術和方法。在Android系統中提供一些方法進行IPC,AIDL為其中一種方法。
行程間通訊(IPC,Inter-Process Communication),指至少兩個行程或執行緒間傳送資料或訊號的一些技術和方法。在Android系統中提供一些方法進行IPC,AIDL為其中一種方法。
注意,只有在如果你允許來自其他應用程式的用戶端存取你的服務(service)及想在你的服務(service)中處理多執行緒,使用AIDL是必須的。如果你不需要在跨不同應用程式間實現並行IPC,你應該藉由實現一個Binder建立你的介面(interface),或者你想進行IPC,但不需要處理多執行緒,利用一個Messager實現你的介面(interface)。無論如何,在你實現AIDL前確保你理解bound service了。
在你開始設計你的AIDL介面(interface)前,注意調用AIDL介面(interface)是函式直接調用,你不應該做出假設在執行緒中調用函式的可能。發生情況不同依據是否來自在本地行程或遠端行程中的一個執行緒的調用。特別是
- 執行在相同執行緒中的本地行程產生的調用正在調用,如果此執行緒是你的主要使用者介面執行緒(UI thread),此執行緒繼續在AIDL介面中執行。如果此執行緒是其他執行緒,是在你的服務(service)中執行你的程式的其中之一執行緒。因此,如果只有本地的執行緒正在存取服務(service),你可以完全地控制在服務中執行的執行緒(但是,如果是這樣的話,你不應該使用AIDL,但應該實現一個Binder來建立介面來取代之)。
- 來自遠端行程的調用被一個執行緒池(thread pool)調度,該平台在你自己的行程中維護,你必須為來自未知執行緒的調用作準備,可能在同一時間許多調用發生,換句話說,AIDL的實現必須是完全地執行緒安全(thread-safe)。
- oneway關鍵字改變遠端調用的行為。當使用時,遠端調用不會阻塞,它會簡易地送交易資料並立即回傳,介面的實現終究會接收它如來自Binder執行緒池正規的遠端調用。如果oneway備用在本地調用,不會有任何影響且調用仍是同步的。
星期一, 9月 01, 2014
Creating a Content Provider (二)
Implementing the ContentProvider Class
ContentProvider實例藉由處理其他應用程式的請求,管理對結構化資料的存取。所有存取的格式最終呼叫ContentResolver,然後呼叫具體的ContentProvider的方法去取得存取。
Required methods
ContentProvider抽象類別定義了六個抽象方法,這些方法必須實作作為你的子類別的一部份。這些所有的方法除了onCreate()外都會被客戶端應用程式呼叫,以嘗試存取你的content provider。
query()
這些方法的實作應考慮以下內容:
Implementing the delete() method
delete()方法不必實際刪除來自資料儲存中的row,如果與你的provider正使用sync adapter,你應該考慮在被刪除的row標記"delete"標籤,而不是移除整個row。sync adapter可以確認被刪除的row並從provider刪除它們之前將它們從伺服器中移除。
Implementing the update() method
update()方法採用與insert()方法相同的ContentValues參數,相同於使用在delete()方法和ContentProvider.query()方法的selection和selection Args參數。如此可能允許你在這些方法中重複使用程式碼。
Implementing hte onCreate() method
當Android系統啟動provider,Android系統呼叫onCreate()。在此方法中,你應該只執行快速運行的初始化任務,並延遲資料庫的建立和資料的加載,直到provider實際接收到對於資料的請求。如果你在onCreate()方法中執行冗長任務,將會減緩provider的啟動,換言之,將會減緩provider對於其他應用程式的反應。
例如,如果你使用SQLite資料庫,可以在ContentProvider.onCreate()建立一個新的SQLiteOpenHelper物件,然後在你第一次打開資料庫時建立SQLite資料表。促使該事,第一時間你可以呼叫getWritableDatabase()方法,它會自動地呼叫SQLiteOpenHelper.onCreate()方法。
以下兩片段示範ContentProvider.onCreate()與SQLiteOpenHelper.onCreate()之間的互動。第一片段ContentProvider.onCreate()方法的實作。
ContentProvider實例藉由處理其他應用程式的請求,管理對結構化資料的存取。所有存取的格式最終呼叫ContentResolver,然後呼叫具體的ContentProvider的方法去取得存取。
Required methods
ContentProvider抽象類別定義了六個抽象方法,這些方法必須實作作為你的子類別的一部份。這些所有的方法除了onCreate()外都會被客戶端應用程式呼叫,以嘗試存取你的content provider。
query()
從你的content provider擷取資料。利用參數選擇查詢的資料表、回傳的行與列、結果的排序。回傳資料為一個Cursor物件。insert()
插入新的row到你的content provider。利用參數選擇目標資料表及獲取column值去使用。回傳薪插入的row的content URI。update()
更新已存在於你的content provider的row。利用參數選擇資料表與rows來更新以及取得被更新的column值。回傳被更新的row的數量。delete()
從你的content provider刪除rows。利用參數選擇資料表與rows來刪除。回傳被刪除rows的數量。getType()
回傳對應到一個content URI的MIME型態。詳細說明在Implementing Content Provider MIME Types。onCreate()
初始化你的content provider。Android系統建立你的content provider之後,立即呼叫此方法。注意到你的content provider不會被建立,直到ContentResolver嘗試存取它。注意到這些方法都有一個相同的特徵,與ContentResolver的方法同名。
這些方法的實作應考慮以下內容:
- 這些所有方法除了onCreate()以外都可以同時被多個執行續呼叫,所以它們必須是thread safe。詳細說明在Processes and Threads。
- 避免在onCreate()方法中執行冗長的操作。延遲初始化任務直到它們真的被需要,詳細於Implementing the onCreate() method討論。
- 雖然你必須值做這些方法,你的程式碼不必任何事都做,除了回傳預期的資料型態。例如,你想要預防其他應用程式在某些資料表上插入資料。達成方式可以忽略對insert()的呼叫並回傳0。
Implementing the query() method
ContentProvider.query()方法必須回傳Cursor物件,或是如果失敗,丟出一個Exception。如果你是使用SQLite資料庫做為資料的儲存,你可以簡單地回傳Cursor,此Cursor是SQLiteDatabase類別其中一個query()方法的回傳。如果查詢無符合條件的row,你應該回傳一個Cursor實例,它的getCount()方法回傳值為0。只在如果查詢過程中內部發生錯誤時,回傳null。
如果你不是使用SQLite資料庫做為資料儲存,使用具體的Cursor子類的其中之一。例如,MatrixCursor類別實作一個Cursor,在Cursor中每一row是一個Object陣列。用此類別,使用addRow()方法新增新的row。
記住了,Android系統必須能夠與Exception通信跨過處理程序的邊界。Android可以執行以下exceptions,它們對於處理查詢錯誤有幫助。
- IllegalArgumentException (如果你的provider接收到一個無效的content URI時,你可能選擇丟出此例外)
- NullPointerException
Implementing the insert() method
insert()方法用來插入新的row到適當的資料表,利用在ContentValues參數中的值。如果column名稱沒有在ContentValues參數中,你可想要在你的provider程式碼中或你的資料庫綱目中,為它提供預設值。
該方法應該回傳新的row的content URI。建構content URI,使用withAppendedId()方法,將新的row的_ID(或其他主鍵)值附加到資料表的content URI上。
Implementing the delete() method
delete()方法不必實際刪除來自資料儲存中的row,如果與你的provider正使用sync adapter,你應該考慮在被刪除的row標記"delete"標籤,而不是移除整個row。sync adapter可以確認被刪除的row並從provider刪除它們之前將它們從伺服器中移除。
Implementing the update() method
update()方法採用與insert()方法相同的ContentValues參數,相同於使用在delete()方法和ContentProvider.query()方法的selection和selection Args參數。如此可能允許你在這些方法中重複使用程式碼。
Implementing hte onCreate() method
當Android系統啟動provider,Android系統呼叫onCreate()。在此方法中,你應該只執行快速運行的初始化任務,並延遲資料庫的建立和資料的加載,直到provider實際接收到對於資料的請求。如果你在onCreate()方法中執行冗長任務,將會減緩provider的啟動,換言之,將會減緩provider對於其他應用程式的反應。
例如,如果你使用SQLite資料庫,可以在ContentProvider.onCreate()建立一個新的SQLiteOpenHelper物件,然後在你第一次打開資料庫時建立SQLite資料表。促使該事,第一時間你可以呼叫getWritableDatabase()方法,它會自動地呼叫SQLiteOpenHelper.onCreate()方法。
以下兩片段示範ContentProvider.onCreate()與SQLiteOpenHelper.onCreate()之間的互動。第一片段ContentProvider.onCreate()方法的實作。
public class ExampleProvider extends ContentProvider /* * Defines a handle to the database helper object. The MainDatabaseHelper class is defined * in a following snippet. */ private MainDatabaseHelper mOpenHelper; // Defines the database name private static final String DBNAME = "mydb"; // Holds the database object private SQLiteDatabase db; public boolean onCreate() { /* * Creates a new helper object. This method always returns quickly. * Notice that the database itself isn't created or opened * until SQLiteOpenHelper.getWritableDatabase is called */ mOpenHelper = new MainDatabaseHelper( getContext(), // the application context DBNAME, // the name of the database) null, // uses the default SQLite cursor 1 // the version number ); return true; } ... // Implements the provider's insert method public Cursor insert(Uri uri, ContentValues values) { // Insert code here to determine which table to open, handle error-checking, and so forth ... /* * Gets a writeable database. This will trigger its creation if it doesn't already exist. * */ db = mOpenHelper.getWritableDatabase(); } }下一片段是SQLiteOpenHelper.onCreate()實作,包含一個Helper類別。
.. // A string that defines the SQL statement for creating a table private static final String SQL_CREATE_MAIN = "CREATE TABLE " + "main " + // Table's name "(" + // The columns in the table " _ID INTEGER PRIMARY KEY, " + " WORD TEXT" " FREQUENCY INTEGER " + " LOCALE TEXT )"; ... /** * Helper class that actually creates and manages the provider's underlying data repository. */ protected static final class MainDatabaseHelper extends SQLiteOpenHelper { /* * Instantiates an open helper for the provider's SQLite data repository * Do not do database creation and upgrade here. */ MainDatabaseHelper(Context context) { super(context, DBNAME, null, 1); } /* * Creates the data repository. This is called when the provider attempts to open the * repository and SQLite reports that it doesn't exist. */ public void onCreate(SQLiteDatabase db) { // Creates the main table db.execSQL(SQL_CREATE_MAIN); } }
星期六, 8月 30, 2014
Creating a Content Provider (一)
Before You Start Building
在開始建立provider之前,有以下幾件事要做。
在開始建立provider之前,有以下幾件事要做。
- 決定是否需要一個content provider。如果你需要提供一個或多個以下的議題,你就需要建立一個content provider。
- 想提供複雜的資料或檔案給其他應用程式。
- 想允許從你的應用程式複製複雜資料到其他應用程式。
- 想利用搜尋框架提供客製化搜尋。
- 如果你還沒準備好,請先看Content Provider Basics。
接下來,跟隨以下這些步驟建立provider。
- 對於你的資料設計raw storage。Content provider有兩種方式提供資料。
- File data
資料通常進入檔案,如照片、視訊、音樂,儲存這些檔案在應用程式私有的空間。回應其它應用程式對於檔案的請求,你的provider可以提供檔案的處理。 - "Structured" data
資料通常進入資料庫、陣列或類似的結構。資料的儲存格式是行和列的資料表,行表示值組,如一個人或一個項目,列表示對於值組的一些資料,如人的姓名或項目的價錢。一般儲存此型態的資料的方式是在SQLite資料庫,但是你也可以使用任何型態的永久性儲存,詳細說明在Designing Data Storage。 - 定義實際的ContentProvider類別和它需要的方法的實作。該類別是你的資料和Android系統間的介面。
- 定義provider的authority字串、content URI、列名稱。如果你想要provider的應用程式能處理intent,也要定義intent action、extras data、flags。也要定義想存取你的資料的應用程式將請求的權限。你應該考慮定義這些所有數值為常數,放在獨立的contract類別,往後,你可以顯示該類別給其他開發者。
- 加入其他可選部分。例如樣品資料或是AbstractThreadedSyncAdapter的實作,AbstractThreadedSyncAdapter實作可以在provider與雲端資料之間同步資料。
Designing Data Storage
Content provider是資料儲存結構化模式的介面。建立介面之前,必須決定如何儲存資料。可以儲存成任何你喜歡的模式,然後再設計讀取和寫入資料所必要的介面。
以下是在Android下可用的資料儲存技術
- Android系統包含了SQLite資料庫API。SQLiteOpenHelper類別幫助你建立資料庫,且SQLiteDatabase類別是存取資料的基本類別。
- 對於儲存成檔案的資料,Android提供多種檔案導向的API。如果你正在設計提供多媒體相關資料如音樂視訊的provider,你可以結合資料表和檔案。
- 對於以網路為基礎的資料的運作,使用java.net及android.net中的類別。你也可以同步network-based資料到本地資料儲存如資料庫,然後提供資料表或檔案,
Data design considerations
以下有一些設計provider的資料結構的訣竅
- 資料表資料應該始終有一個"主鍵"欄位,用來讓provider維護每行。也可以使用此欄位row與其他資料表的相關的row連結(如使用外鍵一樣)。雖然主鍵可以用任何名字命名,使用BaseColumns._ID還是最佳的選擇,因為連結查詢結果到ListView需要有欄位名稱為_ID。
- 如果想要提供Bitmap圖片或其他非常大的檔案導向資料,應將資料儲存在檔案然後鍵接的提供它,而不是直接地儲存在資料表中。如果如此做的話,應該告訴你的provider的使用者,他們需使用ContentResolver檔案方法來存取資料。
- 使用Binary Large OBject(BLOB)資料型態,它有多種大小和不同的結構。例如,可以使用BLOB欄位儲存protocol buffer或JSON Structure。你也可以使用BLOB實現綱目獨立(schema independent)的資料表。在這種資料表中,你定義了一個關鍵欄位、一個MIME型態欄位及一個或多個通用欄位做為BLOB,意味著,在BLOB欄位中的資料是由在MIME型態的欄位的值表示的。Contacts Provider的"data"ContactsProvider.Data是schema-independent資料表的例子。
Designing Content URIs
content URI是一種URI用來辨識provider中的資料,Content URI包含整個provider的名稱(它的authority),只到的檔案或資料表的名稱(path),可選的id部分指出在資料表中個別row。ContentProvider的每一個資料存取的方法都有一個content URI作為參數,此允許你決定存取table row或檔案。
Designing an authority
一個provider通常有一個個別的authority,它作為Android內部的名稱。避免與其他provider衝突,你應該使用網路域名所有權作為你的authority的基礎,因為這與Android package名稱的建議一樣,你可以將你的provider authority定義為包含provider的package的名稱的延伸。例如,你的Android package名稱為com.example.<appname>,可以給你的provider authority為com.example.<appname>.provider。
Designing a path structure
開發人員通常藉由從authority附加路徑建立content URI,以其指出個別的資料表。例如,假如你有兩個資料表table1和table2,由先前的例子結合authority產生content URIs為com.example.<appnmae>.provider/table1及con.example.<appname>.provider.table2。
Handling content URI IDs
按照慣例,provider藉由在content URI末端附加上每個row的ID值,提供存取資料表中的個別row。provider的ID值與資料表的_ID欄位相配,並執行被請求的存取。
為應用程式存取provider,此慣例有助於一般設計樣式。應用程式針對provider查詢,並利用CursorAdapter將查詢結果Cursor顯示在ListView,CursorAdapter的定義是在Cursor中需要有一個欄位為_ID。
接著使用者從使用者介面中挑選被顯示的row,查看或修改資料。應用程式從支持ListView的Cursor取得相對應的row,取得此row的_ID值,附加到content URI並送出存取provider的請求。provider接著查詢或修改使用者挑出來的row。
Content URI patterns
幫助你針對傳入的content URI選擇採取動作,provider API包含了便利的類別UriMatcher,UriMatcher可以將content Uri樣式成整數值。你可以在switch描述中使用整數值,採取預期的動作。
content URI pattern匹配content URIs
Designing an authority
一個provider通常有一個個別的authority,它作為Android內部的名稱。避免與其他provider衝突,你應該使用網路域名所有權作為你的authority的基礎,因為這與Android package名稱的建議一樣,你可以將你的provider authority定義為包含provider的package的名稱的延伸。例如,你的Android package名稱為com.example.<appname>,可以給你的provider authority為com.example.<appname>.provider。
Designing a path structure
開發人員通常藉由從authority附加路徑建立content URI,以其指出個別的資料表。例如,假如你有兩個資料表table1和table2,由先前的例子結合authority產生content URIs為com.example.<appnmae>.provider/table1及con.example.<appname>.provider.table2。
Handling content URI IDs
按照慣例,provider藉由在content URI末端附加上每個row的ID值,提供存取資料表中的個別row。provider的ID值與資料表的_ID欄位相配,並執行被請求的存取。
為應用程式存取provider,此慣例有助於一般設計樣式。應用程式針對provider查詢,並利用CursorAdapter將查詢結果Cursor顯示在ListView,CursorAdapter的定義是在Cursor中需要有一個欄位為_ID。
接著使用者從使用者介面中挑選被顯示的row,查看或修改資料。應用程式從支持ListView的Cursor取得相對應的row,取得此row的_ID值,附加到content URI並送出存取provider的請求。provider接著查詢或修改使用者挑出來的row。
Content URI patterns
幫助你針對傳入的content URI選擇採取動作,provider API包含了便利的類別UriMatcher,UriMatcher可以將content Uri樣式成整數值。你可以在switch描述中使用整數值,採取預期的動作。
content URI pattern匹配content URIs
- * :Matches a string of any valid characters of any length
- # :Matches a string of numeric characters of any length
以設計及編碼content URI的處理作為例子,考慮有authority的provider-com.example.app.provider,用它來辨認以下的content URI以指出資料表。
- content://com.example.app.provider/table1:名稱為table1的資料表。
- content://com.example.app.provider/table2/dataset1:名稱為dataset1的資料表。
- content://com.example.app.provider/table2/dataset2:名稱為dataset2的資料表。
- content://com.example.app.provider/atble3:名稱為table3的資料表。
如果這些content URI有row id附加在它們上,provider也能辨認。例如,com.example.app.provider/table3/1被辨認為table3的id為1的row。
以下content URI patterns也是:
content://com.example.app.provider/* - 匹配在provider中的任何content URI。
content://com.example.app.provider/table2/* - 匹配在資料表dataset1及dataset2中的content URI,但不匹配在資料表table1及table3中的content URI。
content://com.example.app.provider/table3/# - 匹配在資料表table3中的單一row的content URI,例如,content://com.example.app.provider/table3/6為由6確定row。
以下程式片段顯示在UriMatcher中的方法如何運作,這程式處理對於整個資料表的content URI不同於處理資料表中單一row的content URI。藉由使用content URI patttern針對資料表的content://<authority>/<path>以及針對單一row的content://<authority>/<path>/<id>。
addURI()方法是用來將authority和path對應到整數數值,match()方法回傳對應到一個Uri其整數數值。
public class ExampleProvider extends ContentProvider { ... // Creates a UriMatcher object. private static final UriMatcher sUriMatcher; ... /* * The calls to addURI() go here, for all of the content URI patterns that the provider * should recognize. For this snippet, only the calls for table 3 are shown. */ ... /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path */ sUriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the "#" wildcard is * used. "content://com.example.app.provider/table3/3" matches, but * "content://com.example.app.provider/table3 doesn't. */ sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); ... // Implements ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown. */ switch (sUriMatcher.match(uri)) { // If the incoming URI was for all of table3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row case 2: /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query */ selection = selection + "_ID = " uri.getLastPathSegment(); break; default: ... // If the URI is not recognized, you should do some error handling here. } // call the code to actually do the query }
其他類別如contentUris,它提供了便利的方法,用來處理content URI的id部分。類別Uri及Uri.Builder,它提供便利方法,用來處理解析已存在的Uri物件及建立新的Uri。
星期四, 8月 28, 2014
Content Provider Basics (二)
Displaying query results
ContentResolver.query()客戶端方法始終回傳一個Cursor,cursor包含符合查詢條件的行和查詢中projection指定的欄位,Cursor物件提供隨機讀取它包含的行和欄位。使用Cursor方法,你可以遍歷在查詢結果中的各行、決定每個欄位的資料型態、從欄位中取值出來及檢查查詢結果的其他特性。一些Cursor在provider的資料改變時會自動地更新物件,或在Cursor改變時觸發一些方法,或兩者都會。
如果沒有行符合selection條件,provider回傳Cursor物件而Cursor.getCount()為0(一個空的Cursor)。
如果內部錯誤發生,查詢結果會根據特別的provider,它可能會選擇回傳null,或是丟出Exception。
因為一個Cursor是一個行列表,顯示Cursor內容的一個好方式是透過SimpleCursorAdapter將Cursor連結到ListView。
以下片段接續了上面的片段,建立SimpleCursorAdapter物件包含藉由查詢描述擷取出的Cursor,接著設定此物件為ListView的Adapter。
// 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);注意 -回到有Cursor的ListView,cursor必須包含名稱為_ID的欄位。因為如此,查詢描述顯示擷取"words"資料表的_ID欄位,儘管如此,ListView不會顯示它。此限制也說明了為什麼大部分的provider對於每個資料表都有_ID欄位了。
Getting data from query results
比起簡單地顯示查詢結果,妳可能會使用它們完成其他任務。例如,你可以從user dictionary擷取拼法,接著在其他provider中尋找它們。要完成這項功能,你要遍歷Cursor中的所有行。
// 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. }Cursor實現包含許多"get"方法擷取來自物件不同資料型態的資料。例如,前面的片段使用getString()。它們也有getType()方法,回傳欄位的資料型態。
Content Provider Permissions
一個provider的應用程式可以指定權限讓其他應用程式要存取provider資料必須有該權限,這些權限確保了使用者知道應用程式將會試著存取什麼樣的資料。根據provider的需求,其他應用程式為了存取provider資料,請求它們需要的權限。終端使用者在安裝應用程式時會看到請求的權限。
取得存取provider所需的權限,應用程式在它的manifest文件中用<uses-permission>標籤請求。
以下為請求讀取User Dictionary Provider權限
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
Inserting, Updating, and Deleting Data
如同我們從provider擷取資料出來的方法,你也可以使用provider客戶端與provider的ContentProvider間的互動修改資料。你以參數呼叫ContentResolver方法,參數會被傳遞到相對應的ContentProvider方法。provider和provider客戶端會自動地處理安全性與處理程序間的溝通。
Inserting data
將資料插入provider,呼叫ContentResolver.insert()方法。此方法插入新的行到provider並回傳該行的content Uri。以下片段插入新的單字到User Dictionary Provider。
// 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 );新行的資料進入一個單一ContentValue物件,它格式有點類似包含一行的cursor。此物件中的欄位不需要有相同的資料型態,且如果不想要指定數值,你可以使用ContentValue.putNull()設定欄位為null。
不需要加入_ID欄位,因為此欄位是自動維護的。provider會分配給被加入的每一行一個_ID唯一值。provider通常使用此值作為資料表的主鍵。
回傳到mNewValues的content Uri用來辨識新加入的行,如以下格式
content://user_dictionary/words/<id_value><id_value>為新行的_ID值,大部分的provider能自動地偵測此格式的content Uri,然後執行在特定行上的操作。
從回傳的Uri中取得_ID值,呼叫ContentUris,parseId()。
Updating data
更新一個row,你可以使用ContentValues物件與更新資料一起,就如同在插入資料時所做的一樣,selection條件也和在query時一樣。在客戶端使用的方法是ContentResolver.update(),只需要針對欲更新欄位,加數值到ContentValues物件。如果想要清除欄位的內容,把數值設為null。
以下片段將所有的row它的locale為"en"改變為null,回傳值為有改變的row個數。
// 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()時,也應該將使用者輸入做消毒,就像在Protecting against malicious input。
Deleting data
刪除rows類似於擷取row資料,針對想刪除的row指定selection條件,且客戶端方法回傳被刪除的row的號碼。以下片段為刪除appid符合"user"的row,方法回傳被刪除的row的號碼。
// 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 );當呼叫ContentResolver.delete()時,也應該將使用者輸入做消毒,就像在Protecting against malicious input。
Provider Data Types
Content providers提供許多不同的資料型態。User Dictionary Provider只有文字型態,但provider可以提供以下型態
- integer
- long integer (long)
- floating point
- long floating point (double)
provider常使用的它資料型態是Binary Large OBject (BLOB)被實作為64KB的byte array。可用的資料型態可到Cursor類別看看"get"方法。
一個provider中,每個欄位的資料型態通常被列在它的文件中。
provider也為每個content URI維護MIME型態資訊。
Alternative Forms of Provider Access
應用程式發展過程中,以下三種provider存取的替代形式是重要的:
- Batch access - 你可以在ContentProviderOperation類別中建立呼叫存取方法的批次處理,接著運用在ContentResolver.applyBatch()。
- Asynchronous queries - 應該獨立的執行緒中執行查詢,一種達成方式是使用CursorLoader物件。
- Data access vis intents - 雖然你無法直接地傳送一個intent,你可以傳送intent到provider的應用程式,應用程式有最佳的條件去修改provider資料。
星期三, 8月 27, 2014
Content Providers Basics (一)
如何存取Content provider中的資料
Overview
一個content provider提供資料給外部應用程式是一個或多個資料表,類似關聯式資料庫的資料表。一行表示是provider蒐集某些類型資料的一個實例,一行中的每個列表示對於一個實例資料的個別片段。
例如,在Android平台中的內建的provider之一的user dictionay,user dictionary儲存了使用者想保留的非標準單字的拼法,以下表格說明在provider的資料表中資料的樣子。
在資料表中,每一行表示每個單字的實例,單字可能不存在標準字典中。每一列表示對於此單字的一些資料,如locale。列的標頭表示列的名稱,儲存在provider中。參考row的locale,就參考到locale列。對於此資料表來說,_ID列就如"主鍵"列一樣,由provider自動地維護。
注意 - provider不要求要有主鍵,且如果資料表有主鍵也不一定要用_ID為名稱。然而,如果你想要從provider將資料綁定在ListView上,其中一個列名稱一定要是_ID。詳細原因請繼續往下到Displaying query results。
Accessing a provider
應用程式用一個ContentResolver客戶端物件來存取provider中的資料。ContentResovler物件有方法,它們會呼叫在provider物件中同名的方法。ContentResolver方法提供基本的"CRUD"(create、read、update及delete)功能。
在客戶端應用程式的處理程序中的ContentResolver物件和在擁有provider的應用程式中的ContentProvider物件會自動地處置處理程序間的通信,ContentProvider也會扮演一個在資料的資料庫與資料的外部顯示如資料表之間的抽象層。
注意 -存取provider時,你的應用程式必須在manifest文件中請求特定的權限,詳細說明在繼續往下到Content Providers Permission。
舉例,從User Dictionary Provider中得到單字及它們的locale的列表,你會呼叫ContentResolver.query()。此query()方法呼叫由User Dictionary Provider定義的query()方法。以下顯示ContentResolver.query()的呼叫。
Content URIs
content URI是辨識provider中資料的一種URI,Content URIs包含整個provider的符號名稱(它的authority)且一個名稱點出一個資料表(一個path)。當你呼叫一個客戶端方法存取一個provider的一個資料表時,對於資料表來說,content URI是其中一個參數。
在先前的程式碼中,常數CONTENT_URI含有user dictionary的"word"資料表的content URI,ContentResolver物件解析出URI的authority,並利用它藉由與已知的provider的系統資料表的authority比較去"Resolve"provider,接著ContentResolver發送查詢參數給正確的provider。
ContentResolver利用content URI的一部分路徑來選擇存取的資料表。一個provider通常為每個資料表有一個path。
在先前的程式碼中,"word"資料表的URI為
許多provider允許你藉由在URI的末端附加上ID值來存取資料表中的單一行。例如,擷取自user dictionary它的_ID為4的一整行,可以使用以下的content URI
注意 -Uri和Uri.Binder類別包含從字串建立良好格式的Uri的便利方法,ContentUris包含在一個Uri上附加id值得便利方法,閒錢的片段是使用withAppendedId()方法附加id值到USER DICTIONARY的content URI。
Retrieving Data from the Provider
接下來是要了解如何從一個provider中擷取資料。
擷取來自provider的資料,遵循這些基本步驟
此權限。當使用者安裝你的應用程式,他們默默地授予此請求。
找出對於你使用的provider的read access permission的確切名稱,以及provider的其他存取權限名稱,請查看p該rovider的文件。
詳細在存取provider的權限的角色細節,請往下到Content Provider Permissions。
USER DICTIONARY PROVIDER定義了android.permission.READ_USER_DICTIONARY權限在manifeest文件中,所以應用成是想要讀取來自provider的資料必須請求此權限。
Constructing the query
擷取來自provider的資料的下一步是建購查詢(query),第一個片段是為存取User Dictionary Provider而定義一些變數。
查詢回傳的欄位集合是被稱為projection(如變數mProjection)。
擷取指定的行的表示式被分為selection clause和selection argument,selection clause是結合了邏輯和布林的表示式、欄位名稱及值(如變數mSelectionClause),如果你只訂了可更換參數?取代值,query方法就會從selection argument陣列中取出值來取代它(如變數mSelectionArgs)。
下一個片段,如果使用者不輸入單字,selection clause設定為null,查詢會回傳所有在provider中的單字。如果使用者輸入單字,selection clause設定為UserDictionary.Words.WORD + " = ?"且selection argument陣列的第一個元素設定為使用者輸入的單字。
Protecting against malicious input
如果由content provider管理的資料是在SQL資料庫中,包含外部不可信的資料進入SQL描述可以導致SQL injection。
考慮此selection clause
為了避免此問題,使用有利用?作為可更換變數的selection clause和selection arguments的獨立陣列。當你這麼做時,使用者輸入是直接綁在查詢上,而不是被直譯為SQL描述的一部份。因為它不被視為SQL,使用者輸入無法注入惡意SQL,取代使用串連方式來包含使用者輸入,使用此selection clause。
Overview
一個content provider提供資料給外部應用程式是一個或多個資料表,類似關聯式資料庫的資料表。一行表示是provider蒐集某些類型資料的一個實例,一行中的每個列表示對於一個實例資料的個別片段。
例如,在Android平台中的內建的provider之一的user dictionay,user dictionary儲存了使用者想保留的非標準單字的拼法,以下表格說明在provider的資料表中資料的樣子。
Table 1: Sample user dictionary table.
word | app id | frequency | locale | _ID |
---|---|---|---|---|
mapreduce | user1 | 100 | en_US | 1 |
precompiler | user14 | 200 | fr_FR | 2 |
applet | user2 | 225 | fr_CA | 3 |
const | user1 | 255 | pt_BR | 4 |
int | user5 | 100 | en_UK | 5 |
注意 - provider不要求要有主鍵,且如果資料表有主鍵也不一定要用_ID為名稱。然而,如果你想要從provider將資料綁定在ListView上,其中一個列名稱一定要是_ID。詳細原因請繼續往下到Displaying query results。
Accessing a provider
應用程式用一個ContentResolver客戶端物件來存取provider中的資料。ContentResovler物件有方法,它們會呼叫在provider物件中同名的方法。ContentResolver方法提供基本的"CRUD"(create、read、update及delete)功能。
在客戶端應用程式的處理程序中的ContentResolver物件和在擁有provider的應用程式中的ContentProvider物件會自動地處置處理程序間的通信,ContentProvider也會扮演一個在資料的資料庫與資料的外部顯示如資料表之間的抽象層。
注意 -存取provider時,你的應用程式必須在manifest文件中請求特定的權限,詳細說明在繼續往下到Content Providers Permission。
舉例,從User Dictionary Provider中得到單字及它們的locale的列表,你會呼叫ContentResolver.query()。此query()方法呼叫由User Dictionary Provider定義的query()方法。以下顯示ContentResolver.query()的呼叫。
// 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表格2顯示query()中的參數對應到SQL SELECT描述。
Table 2: Query() compared to SQL query.
query() argument | SELECT keyword/parameter | Notes |
---|---|---|
Uri | FROM table_name | Uri maps to the table in the provider named table_name. |
projection | col,col,col,... | projection is an array of columns that should be included for each row retrieved. |
selection | WHERE col =value | selection specifies the criteria for selecting rows. |
selectionArgs | (No exact equivalent. Selection arguments replace? placeholders in the selection clause.) | |
sortOrder | ORDER BYcol,col,... | sortOrder specifies the order in which rows appear in the returnedCursor . |
Content URIs
content URI是辨識provider中資料的一種URI,Content URIs包含整個provider的符號名稱(它的authority)且一個名稱點出一個資料表(一個path)。當你呼叫一個客戶端方法存取一個provider的一個資料表時,對於資料表來說,content URI是其中一個參數。
在先前的程式碼中,常數CONTENT_URI含有user dictionary的"word"資料表的content URI,ContentResolver物件解析出URI的authority,並利用它藉由與已知的provider的系統資料表的authority比較去"Resolve"provider,接著ContentResolver發送查詢參數給正確的provider。
ContentResolver利用content URI的一部分路徑來選擇存取的資料表。一個provider通常為每個資料表有一個path。
在先前的程式碼中,"word"資料表的URI為
content://user_dictionary/wordsuser_dictionary字串是provider的authority,且words字串是資料表的路徑,字串content://(the scheme)總是存在的,且用來識別這是一個content URI。
許多provider允許你藉由在URI的末端附加上ID值來存取資料表中的單一行。例如,擷取自user dictionary它的_ID為4的一整行,可以使用以下的content URI
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);當你已經擷取出一組行並想要更新或刪除它們其中的一行時,你可以使用id值達成。
注意 -Uri和Uri.Binder類別包含從字串建立良好格式的Uri的便利方法,ContentUris包含在一個Uri上附加id值得便利方法,閒錢的片段是使用withAppendedId()方法附加id值到USER DICTIONARY的content URI。
Retrieving Data from the Provider
接下來是要了解如何從一個provider中擷取資料。
擷取來自provider的資料,遵循這些基本步驟
- 對provider請求讀取權限
- 定義寄給provider的查詢的程式碼
Requesting read access permission
擷取來自provider的資料,對於provider你的應用程式需要"read access permission",無法在執行時請求此權限,你必須使用<uses-permission>標籤及確切由provider定義的名稱,在manifest文件指定你需要此權限。當你在manifest文件中指定此標籤,你實際上為你的應用程式"請求"
此權限。當使用者安裝你的應用程式,他們默默地授予此請求。
找出對於你使用的provider的read access permission的確切名稱,以及provider的其他存取權限名稱,請查看p該rovider的文件。
詳細在存取provider的權限的角色細節,請往下到Content Provider Permissions。
USER DICTIONARY PROVIDER定義了android.permission.READ_USER_DICTIONARY權限在manifeest文件中,所以應用成是想要讀取來自provider的資料必須請求此權限。
Constructing the query
擷取來自provider的資料的下一步是建購查詢(query),第一個片段是為存取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 = {""};下一個片段顯示如何利用ContentResolver.query(),以User Dictionary Provider為例。provider客戶端查詢與SQL查詢相似,它包含一組回傳欄位集合,一組selection條件及排序。
查詢回傳的欄位集合是被稱為projection(如變數mProjection)。
擷取指定的行的表示式被分為selection clause和selection argument,selection clause是結合了邏輯和布林的表示式、欄位名稱及值(如變數mSelectionClause),如果你只訂了可更換參數?取代值,query方法就會從selection argument陣列中取出值來取代它(如變數mSelectionArgs)。
下一個片段,如果使用者不輸入單字,selection clause設定為null,查詢會回傳所有在provider中的單字。如果使用者輸入單字,selection clause設定為UserDictionary.Words.WORD + " = ?"且selection argument陣列的第一個元素設定為使用者輸入的單字。
/* * 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 = <userinput> ORDER BY word ASC;
Protecting against malicious input
如果由content provider管理的資料是在SQL資料庫中,包含外部不可信的資料進入SQL描述可以導致SQL injection。
考慮此selection clause
// Constructs a selection clause by concatenating the user's input to the column name String mSelectionClause = "var = " + mUserInput;如果你這麼做,你正允許使用者串連惡意SQL到你的SQL描述上。例如,使用者可以對mUserInput輸入"nothing; DROP TABLE *;",它會導致selection clause為var = nothing; DROP TABLE *;。因為selection clause被視為SQL描述,這可能導致provider刪除在底層SQLite資料庫中的所有資料表(除非provider被設定嘗試捕捉SQL injection)。
為了避免此問題,使用有利用?作為可更換變數的selection clause和selection arguments的獨立陣列。當你這麼做時,使用者輸入是直接綁在查詢上,而不是被直譯為SQL描述的一部份。因為它不被視為SQL,使用者輸入無法注入惡意SQL,取代使用串連方式來包含使用者輸入,使用此selection clause。
// Constructs a selection clause with a replaceable parameter String mSelectionClause = "var = ?";設定selection arguments陣列如下
// Defines an array to contain the selection arguments String[] selectionArgs = {""};
訂閱:
文章 (Atom)