隨筆 - 87  文章 - 0  評論 - 217  0

加拿大快乐8开奖号码: 一起學Android之ContentProvider

韩国快乐8开奖结果查询 www.wcdoq.com 本文主要講解在Android開發中ContentProvider的常規用法,僅供學習分享使用,如有不足之處,還請指正。

訪問一個ContentProvider

Android開發中,應用程序通過ContentResolver(內容解析器)ContentProvider(內容提供者)中獲取數據,ContentResolver提供訪問ContentProvider中同名方法,ContentProvider包括ContentProvider和它的子類,ContentResolverContentProvider的持久層存儲提供了基本的CRUDCreate,Retrieve,Update,Delete)方法進行訪問??突Ф?/span>AppContentResolver對象自動處理和ContentProviderApp之間的進程間通信。ContentProvider還充當數據庫和外部數據視圖表現之間的抽象層。

備注:如果要訪問一個ContentProvider,App需要在清單文件中請求對應的權限。

例如:從User Dictionary Provider中獲取單詞和區域的列表,可以調用ContentResolver.query()方法,如下圖所示:

1 // 查詢用戶定義字典并返回結果
2 mCursor = getContentResolver().query(
3     UserDictionary.Words.CONTENT_URI,   // 單詞表的內容URI
4     mProjection,                        // 查詢的數據列名數組
5     mSelectionClause                    //查詢條件,可以為null
6     mSelectionArgs,                     // 查詢參數,可以為null
7     mSortOrder);                        // 返回數據對象的排序條件

下表顯示了query(Uri,projection,selection,selectionArgs,sortOrder) 如何與SQL語句進行匹配:

Content URIs

Content URIProvider中標識數據的URI,包括整個Provider(其權限)的符號名和指向表(或路徑)的名稱,Content URI是訪問ContentProvider的參數之一。

在前面的代碼行中,常量_uri包含了用戶詞典“Word”表的Content URI。ContentResolver對象通過將權限與已知提供者的系統表進行比較,將查詢參數發送到正確的Provider。 

ContentProvider使用URI的路徑部分來選擇要訪問的表,通常為公開的每個表設置路徑。

在前面的代碼行中,Word”表的全稱為:

1 content://user_dictionary/words

其中user_dictionary 字符串是Provider的權限,words是表的路徑。content:// (the scheme)始終存在,并將其標識為Content URI。

許多Provider允許將id值附加到URI的末尾來訪問表中的單個行。例如,要從User Dictionary中檢索_id4的行,可以使用Content URI

1 Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

當要修改或刪除其中一行時,經常使用ID值。

備注:Uri Uri.Builder類包含了用字符串構造形式良好的uri對象的方法。ContentUris包含了將ID附加到uri的方法。前面片段使用withAppendedId() ID附加到UserDictionary.Words.CONTENT_URI。

Provider獲取數據

本節介紹如何使用User Dictionary Provider作為示例,從中檢索數據。

為了清晰起見,本節中的代碼段調用UI線程”上的ContentResolver.query()。在實際代碼中,應該在非UI線程上異步地進行查詢。

要從Provider獲取數據,請遵循以下基本步驟:

  1. 請求讀取Provider的訪問權限。
  2. 定義查詢Provider的代碼。

訪問權限

要從Provider中檢索數據,應用程序需要Provider的“讀取訪問權限”。不能在運行時請求此權限;必須在您的清單中指定需要此權限,使用<uses-permission>元素和由Provider定義的權限名稱。當在清單中指定此元素時,實際上是在為App“請求”此權限。當用戶安裝App時,會隱式地批準這個請求。

User Dictionary Provider在清單文件中定義的權限名稱為android.permission.READ_USER_DICTIONARY,所以App中想要從Provider中獲取數據,需要請求這個權限。

構造查詢

查詢數據的下一步是構造查詢。以下片段定義了訪問User Dictionary Provider的一些變量:

 1 //  "projection" 定義每行返回的列名數組
 2 String[] mProjection =
 3 {
 4     UserDictionary.Words._ID,    //  _ID column name
 5     UserDictionary.Words.WORD,   //  word column name
 6     UserDictionary.Words.LOCALE  // locale column name
 7 };
 8 
 9 // 定義查詢條件
10 String mSelectionClause = null;
11 
12 // 定義查詢條件參數
13 String[] mSelectionArgs = {""};

下一個片段顯示如何使用ContentResolver.query(),以User Dictionary Provider 為例,查詢類似于sql查詢,它包含要返回的列名、查詢條件和排序。

查詢返回的列集合稱為投影(變量投影)。

查詢條件表達式被拆分為選擇子句和選擇參數。選擇子句是邏輯表達式和布爾表達式、列名稱和值的組合。如果指定可替換參數?”,查詢條件不再是一個值,而是從條件參數數組(mSelectionArgs)中查詢該值。

如果用戶沒有輸入一個單詞,則選擇子句設置為空,查詢返回Provider中的所有單詞。

如果用戶輸入了一個單詞,查詢條件將設置UserDictionary.Words.WORD + " = ?"。參數數組的第一個元素設置為用戶輸入的單詞。

 1 /*
 2  * 定義查詢參數
 3  */
 4 String[] mSelectionArgs = {""};
 5 
 6 // 獲取界面輸入的查詢條件
 7 mSearchString = mSearchWord.getText().toString();
 8 
 9 //此處插入代碼校驗數據是否有效
10 //如果條件為空,則查詢所有
11 if (TextUtils.isEmpty(mSearchString)) {
12     // 如果查詢條件為空,則返回所有內容
13     mSelectionClause = null;
14     mSelectionArgs[0] = "";
15 } else {
16     // 構造查詢條件,匹配用戶輸入的數據.
17     mSelectionClause = UserDictionary.Words.WORD + " = ?";
18     // 查詢參數.
19     mSelectionArgs[0] = mSearchString;
20 }
21 
22 // 對表進行查詢并返回Cursor對象
23 mCursor = getContentResolver().query(
24     UserDictionary.Words.CONTENT_URI,  // URI
25     mProjection,                       // 查詢數據列
26     mSelectionClause                   // 查詢條件
27     mSelectionArgs,                    // 查詢參數
28     mSortOrder);                       // 返回結果排序行
29 
30 // 如果出現查詢異常,則返回空
31 if (null == mCursor) {
32     /*
33      * 插入代碼捕獲異常
34      */
35     // 如果返回為空,則沒有匹配的內容
36 } else if (mCursor.getCount() < 1) {
37     /*
38      * 通知用戶查詢不成功. 但這不是必須的*/
39 } else {
40     // 插入代碼處理結果
41 }

類似于sql語句:

1 SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

在這個sql語句中,使用的是實際的列名稱,而不是Contract類常量。

防止惡意輸入

如果content provider管理的數據在sql數據庫中,外部不受信任的數據輸入到原始sql語句中,就會導致sql注入。

考慮這個查詢條件:

1 //通過拼接用戶輸入和列名的方式構造查詢條件
2 String mSelectionClause =  "var = " + mUserInput;

如果這樣做,用戶可能將惡意sql連接到您的sql語句中。例如,用戶可以輸入"nothing; DROP TABLE *;"用于mUserInput,這將導致選擇子句var = nothing; DROP TABLE *;。由于選擇條件被視為sql語句,這可能會導致Provider刪除sqlite數據庫中的所有表。

為了避免此問題,請使用可替換的參數和單獨的選擇參數數組的查詢條件。采用這種方式,用戶輸入將直接綁定到查詢,而不是被解釋為sql語句的一部分,用戶無法注入惡意sql。如下所示:

1 // 用一個可替換參數來包含用戶輸入
2 String mSelectionClause =  "var = ?";

如下設置查詢參數數組:

1 // 定義一個查詢條件的數組
2 String[] selectionArgs = {""};

在查詢參數數組中進行賦值:

1 // 將用戶數據作為參數數據
2 selectionArgs[0] = mUserInput;

顯示查詢結果

ContentResolver.query()客戶端方法總是返回一個Cursor。Cursor對象提供對其包含的行和列的讀取訪問權。使用Cursor中的方法可以迭代行數據,確定每列的數據類型,將數據從列中取出,并檢查結果的其他屬性。有些Cursor實現會在提供者的數據變更時自動更新,或在Cursor變更時觸發對應的事件,或兩者兼而有之。

如果沒有行符合查詢條件,provider將返回一個Cursor, 其Cursor.getCount()0(空光標)。

如果發生內部錯誤,查詢的結果取決于特定的Provider。它可以返回null,也可以拋出異常。

由于Cursor是行的“列表”,顯示Cursor內容的一個好方法是通過SimpleCursorAdapter綁定到ListView。

如下代碼所示:它創建一個SimpleCursorAdapter對象,包含查詢到的Cursor,并將此對象設置到ListView的適配器

 1 // 定義從Cursor中檢索并加載到輸出行的列名
 2 String[] mWordListColumns =
 3 {
 4     UserDictionary.Words.WORD,   // word column name
 5     UserDictionary.Words.LOCALE  // locale column name
 6 };
 7 
 8 //定義一個視圖ID列表,該列表將接收每行的Cursor列
 9 int[] mWordListItems = { R.id.dictWord, R.id.locale};
10 
11 // 創建一個SimpleCursorAdapter對象
12 mCursorAdapter = new SimpleCursorAdapter(
13     getApplicationContext(),               // 應用程序上下文對象
14     R.layout.wordlistrow,                  // ListView單行配置文件 
15     mCursor,                               // query函數返回的結果
16     mWordListColumns,                      // Cursor中的列名數組
17     mWordListItems,                        // ListView中Item項的布局文件
18     0);                                    // Flags (usually none are needed)
19 
20 // 設置 adapter到ListView
21 mWordList.setAdapter(mCursorAdapter);

備注:要使用Cursor支持ListView,Cursor必須包含一個名為_id的列。這個限制也解釋了為什么大多數Provider的每個表都有一個_id列。

從查詢結果中獲取數據

您可以將查詢結果用于其他任務,而不是簡單地顯示查詢結果。要做到這一點,需要迭代Cursor中的行:

 1 // 定義"word"列的索引
 2 int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
 3 
 4 /*
 5  * 當cursor有效的時候才執行.  User Dictionary Provider如果發生內部錯誤,將返回null,其他provider可能會拋出異常
 6  */
 7 
 8 if (mCursor != null) {
 9     /*
10      * 移動到cursor的下一行.在第一行移動之前, 行指向是-1,如果試圖去查詢此位置上的內容,將會拋出一個異常
11      */
12     while (mCursor.moveToNext()) {
13         //獲取對應的列的值.
14         newWord = mCursor.getString(index);
15         // 插入代碼處理獲取的值.
16         ...
17         // while 循環結束
18     }
19 } else {
20     // 展示錯誤和異常信息
21 }

Cursor實現包含檢索不同類型數據的幾種“get”方法。例如,上一個片段使用getString()。同時也有一個gettype()方法,該方法返回列的數據類型。

Content Provider權限

訪問Provider中的數據,調用方必須具有相應的權限,這些權限確保用戶知道應用程序試圖訪問哪些數據,用戶在安裝App時會看到請求的權限。

如前所述,User Dictionary Provider要求使用android.permission.READ_USER_DICTIONARY權限獲取數據。Provider需要android.permission.WRITE_USER_DICTIONARY權限來插入、更新或刪除數據。

為了獲得訪問provider所需的權限,App在其清單文件中以<uses-permission>元素請求它們。當安裝App時,用戶必須允許應用程序請求的所有權限。如果用戶全部允許,將繼續安裝;如果用戶不允許,Package Manager將中止安裝。

以下<uses-permission>元素請求讀取 User Dictionary Provider的訪問權限:

 

1 <uses-permission android:name="android.permission.READ_USER_DICTIONARY">

Inserting, Updating, and Deleting Data

與從provider獲取數據的方式相同,還可以使用provider客戶端與provider's 提供方之間的交互來修改數據。provider provider客戶端自動處理安全以及進程間通信。

插入數據(Inserting data

將數據插入到provider中,請調用ContentResolver.insert()。此方法將新行插入到provider中,并返回新增行的 content URI。此片段顯示如何將新詞插入到User Dictionary Provider中:

 1 // 定義一個新的 Uri對象,接收插入新行放回的內容
 2 Uri mNewUri;
 3 
 4 // 要插入的新值
 5 ContentValues mNewValues = new ContentValues();
 6 
 7 /*
 8  * 設置每列對應的值
 9  */
10 mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
11 mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
12 mNewValues.put(UserDictionary.Words.WORD, "insert");
13 mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
14 
15 mNewUri = getContentResolver().insert(
16     UserDictionary.Word.CONTENT_URI,   // 內容 URI
17     mNewValues                          // 插入的值
18 );

新行的數據對應單個ContentValues對象,該對象在形式上類似于單行cursor。此對象中的列不需要具有相同的數據類型,如果不想指定值,則可以使用ContentValues.putNull()設置列為空。

代碼段不會添加_id列,因為此列是自動維護的。provider為添加的每一行指定一個唯一_id,通常使用_id作為表的主鍵。

返回的新行的newUri,格式如下:

1 content://user_dictionary/words/<id_value>

<id_value>是新行的_id。大多數provider可以自動檢測到這種形式的內容,然后在該特定行上執行請求的操作。

若要從返回的Uri中得到_id值,請調用ContentUris.parseId()。

更新數據(Updating data

要更新行,將使用帶有更新值的ContentValues對象,就像使用插入時一樣,選擇條件也與使用查詢時一樣。調用方法是ContentResolver.update()。您只需要為需要更新的列向ContentValues對象添加值。如果要清除列的內容,請將值設置為null。

下面的片段將locale設置有語言"en"的所有行更改為locale為空。返回值是更新的行數:

 1 // 包含更新的內容的對象
 2 ContentValues mUpdateValues = new ContentValues();
 3 
 4 // 定義需要更新的查詢條件
 5 String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
 6 String[] mSelectionArgs = {"en_%"};
 7 
 8 // 定義更新行得到的行數
 9 int mRowsUpdated = 0;
10 
11 /*
12  * 設置更新的內容.
13  */
14 mUpdateValues.putNull(UserDictionary.Words.LOCALE);
15 
16 mRowsUpdated = getContentResolver().update(
17     UserDictionary.Words.CONTENT_URI,   // URI
18     mUpdateValues                       // 更新的內容
19     mSelectionClause                    //查詢條件
20     mSelectionArgs                      // 查詢內容參數
21 );

在調用ContentResolver.update()時,對用戶輸入進行處理。

刪除數據(Deleting data

刪除行類似于查詢行數據:為要刪除的行指定選擇條件,而客戶端方法返回已刪除行的數目如下所示:

 1 // 定義需要刪除的條件
 2 String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
 3 String[] mSelectionArgs = {"user"};
 4 
 5 //定義刪除掉行數
 6 int mRowsDeleted = 0;
 7 
 8 // 刪除匹配條件的內容
 9 mRowsDeleted = getContentResolver().delete(
10     UserDictionary.Words.CONTENT_URI,   //  URI
11     mSelectionClause                    // 刪除條件
12     mSelectionArgs                      // 刪除參數
13 );

 

在調用 ContentResolver.delete()方法時,對用戶輸入進行處理。

Provider數據類型

Content providers可以提供許多不同的數據類型。User Dictionary Provider只提供文本,但也可以提供以下格式:

  •  integer
  •  long integer (long)
  •  floating point
  •  long floating point (double)

providers經常使用的另一種數據類型是Binary Large OBject (BLOB),它是64kb字節數組。通過查看Cursor類“get”方法,可以看到可用的數據類型。

provider中每一列的數據類型通常在其文檔中列出。User Dictionary Provider 的數據類型在其contractUserDictionary.Words的參考文檔中列出。也可以通過Cursor.getType()來確定數據類型。

Provider訪問的替代形式

在應用程序開發中,三種可供選擇的Provider訪問形式非常重要:

  1. 批量訪問:可以在ContentProviderOperation中使用方法創建批量處理訪問調用,然后用ContentResolver.applyBatch()應用它們。
  2. 異步查詢:應該在單獨的線程中進行查詢,其中一種方法是使用CursorLoader對象。
  3. 通過intents訪問數據:雖然不能直接向提供者發送intent,但可以向provider's application發送intent,而provider's application序通常是最適合修改provider數據的應用程序。

批量訪問(Batch access

provider的批量訪問用于插入多行,或在同一方法中在多個表中插入行,或通常用于作為事務(原子操作)執行一組跨進程的操作。

要以batch mode”訪問provider,您可以創建一組 ContentProviderOperation 對象,然后通過ContentResolver.applyBatch()方法將對象分發到provider。將provider的權限傳遞給此方法,而不是特定的內容。這允許數組中的每個ContentProviderOperation對象對不同的表操作。ContentResolver.applyBatch() 返回結果數組。

通過Intent進行數據訪問(Data access via intents

Intents可以提供對 content provider的間接訪問。允許用戶訪問provider中的數據,即使您的App沒有訪問權限,也可以從有權限的App獲得結果Intent,或者通過激活有權限的App并在其中工作。

合同類別(Contract Classes

contract類定義了幫助App處理content URIs、列名稱、意圖操作和 content provider的其他特性的常量。Contract類不自動包含在provider中;provider的開發人員必須定義它們,然后將其提供給其他開發人員。android平臺中的許多提供商在android.provider中都有相應的contract類。

例如,User Dictionary Provider有一個包含內容URI和列名常量的contract類用戶詞典。“單詞”表的內容以“常量”為定義。UserDictionary.Words.CONTENT_URI,在以下示例片段中使用。例如,查詢投影可以定義為:

1 String[] mProjection =
2 {
3     UserDictionary.Words._ID,
4     UserDictionary.Words.WORD,
5     UserDictionary.Words.LOCALE
6 };

 ContentProvider示例

讀取通話記錄

 1 //通訊記錄URI
 2     private String call_uri = "content://call_log/calls";
 3 
 4     //內容解析器
 5     private ContentResolver mResolver;
 6 
 7     //列表
 8     private ListView lvCall;
 9 
10     //獲取的通訊記錄的列名
11     private String[] columns = new String[]{
12             CallLog.Calls._ID, CallLog.Calls.CACHED_NAME, CallLog.Calls.NUMBER, CallLog.Calls.TYPE, CallLog.Calls.DATE,CallLog.Calls.DURATION
13     };
14 
15     @Override
16     protected void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.activity_main);
19         //初始化內容解析器
20         mResolver = getContentResolver();
21         lvCall = (ListView) this.findViewById(R.id.lv_call);
22     }
23 
24     /**
25      * 獲取通訊記錄事件
26      * @param v
27      */
28     public void bn_call(View v) {
29         List<Map<String, String>> list = new ArrayList<>();
30         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
31         Cursor cursor = mResolver.query(Uri.parse(call_uri), columns, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
32         //以下是為了轉換數據格式
33         if(cursor!=null){
34             while (cursor.moveToNext()){
35                 long dt=cursor.getLong(cursor.getColumnIndex("date"));
36                 Date callDate = new Date(dt);
37                 String callDateStr = sdf.format(callDate);
38                 String name=cursor.getString(cursor.getColumnIndex("name"));
39                 String number=cursor.getString(cursor.getColumnIndex("number"));
40                 String duration =cursor.getString(cursor.getColumnIndex("duration"))+"s";
41                 Map<String, String> map=new HashMap<String, String>() ;
42                 map.put("name",name);
43                 map.put("number",number);
44                 map.put("date",callDateStr);
45                 map.put("duration",duration);
46                 list.add(map);
47             }
48         }
49         //將數據填充到Adapter
50         SimpleAdapter adapter=new SimpleAdapter(this,list,R.layout.list_item,
51                 new String[]{"name", "number", "date","duration"},
52                 new int[]{R.id.tv_name, R.id.tv_number, R.id.tv_time,R.id.tv_duration});
53 
54         /*SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.list_item, cursor,
55                 new String[]{"name", "number", "date"},
56                 new int[]{R.id.tv_name, R.id.tv_number, R.id.tv_time},
57                 CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);*/
58         //綁定Adapter到ListView
59         lvCall.setAdapter(adapter);
60     }

讀取短信記錄

 1 private String sms_uri="content://sms";
 2 
 3     private String[] columns=new String[]{
 4             Telephony.Sms._ID, Telephony.Sms.ADDRESS,Telephony.Sms.CREATOR, Telephony.Sms.BODY, Telephony.Sms.DATE, Telephony.Sms.PERSON, Telephony.Sms.STATUS, Telephony.Sms.DATE_SENT
 5     };
 6 
 7     private ContentResolver mResolver;
 8 
 9     private ListView lvMsg;
10 
11     @Override
12     protected void onCreate(Bundle savedInstanceState) {
13         super.onCreate(savedInstanceState);
14         setContentView(R.layout.activity_main2);
15         mResolver=getContentResolver();
16         lvMsg= (ListView) this.findViewById(R.id.lv_sms);
17     }
18 
19     public void bn_sms(View view) {
20         List<Map<String, String>> list = new ArrayList<>();
21         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
22         Cursor cursor = mResolver.query(Uri.parse(sms_uri), columns, null, null, Telephony.Sms.DEFAULT_SORT_ORDER);
23         if (cursor != null) {
24             while (cursor.moveToNext()) {
25                 Log.e("TAG", "bn_sms: "+cursor.getColumnIndex("person")+"---"+ cursor.getColumnIndex("date")+"---"+cursor.getColumnIndex("creator")+"---"+cursor.getColumnIndex("address"));
26                 long dt = cursor.getLong(cursor.getColumnIndex("date"));
27                 Date callDate = new Date(dt);
28                 String callDateStr = sdf.format(callDate);
29                 String person = cursor.getString(cursor.getColumnIndex("address"));
30                 String creator = cursor.getString(cursor.getColumnIndex("creator"));
31                 //String duration =cursor.getString(cursor.getColumnIndex("duration"))+"s";
32                 String body = cursor.getString(cursor.getColumnIndex("body"));
33                 Map<String, String> map = new HashMap<String, String>();
34                 map.put("person", person);
35                 map.put("creator", creator);
36                 map.put("date", callDateStr);
37                 //map.put("duration",duration);
38                 map.put("body", body);
39                 list.add(map);
40             }
41         }
42         //將數據填充到Adapter
43         SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.msg_item,
44                 new String[]{"person", "creator", "date", "body"},
45                 new int[]{R.id.tv_name, R.id.tv_number, R.id.tv_time, R.id.tv_msg});
46 
47         //綁定Adapter到ListView
48         lvMsg.setAdapter(adapter);
49     }

備注

千里之行,始于足下。

posted on 2019-06-18 23:10 Alan.hsiang 閱讀(...) 評論(...) 編輯 收藏