文件存儲和SharePreference存儲以及數據存儲通常爲了安全,最好用於當前應用程序中訪問和存儲數據。內容提供器(Content Provider)主要用於在不一樣的應用程序之間實現數據共享的功能,它提供了一套完整的機制,贊成一個程序訪問還有一個程序中的數據,同一時候還能保證被訪問數據的安全性。眼下使用內容提供器是android實現跨程序共享數據的標準方式。內容提供器可以選擇僅僅對一部分數據進行共享。從而保證咱們的程序中的隱私數據不會有泄漏的風險。html
內容提供器的使用方法通常有兩種,一種是使用現有的內容提供器來讀取和操做對應程序中的數據。還有一種是建立本身的內容提供器給咱們程序的數據提供外部訪問接口。java
訪問其它程序中的數據android
當一個應用程序經過內容提供器對其數據了外部訪問接口,不論什麼其它的應用程序就都可以對這部分數據進行訪問了。android系統中自帶的電話薄,短信,媒體等程序都提供了相似的訪問接口。這就使得第三方應用程序可以充分地利用這部分數據來實現更好的功能。sql
ContentResolver的基本使用方法數據庫
對於一個應用程序來講。假設想要訪問內容提供器中共享的數據,就必定要藉助ContentResolver類。可以經過Context中的getContentResolver()方法獲取到該類的實例。ContentResolver中提供了一系列的方法用於對數據進行CRUD操做,當中insert()方法用於加入數據,update()方法用於更新數據,delete()方法用於刪除數據。query()方法用於查詢數據。安全
不一樣於SQLiteDatabase,ContentResolver中的增刪改查方法都是不接收表名參數的。而是使用一個Uri參數替代,這個參數被稱爲內容URI。app
內容URI給內容提供器中的數據創建了惟一標識符,它主要由2部分組成。權限(authority)和路徑(path)。權限是用於對不一樣的應用程序作區分的,通常爲了不衝突,都會採用程序包名的方式來進行命名。ide
比方某個程序的包名是com.example.app,那麼該程序相應的權限就可以命名爲com.example.app.provider.路徑則是用於對同一應用程序中不一樣的表作區分的,一般都會加入到權限的後面。佈局
比方某個程序的數據庫裏存在兩張表,table1和table2,這時就可以將路徑分別命名爲/table1和table2,而後把權限和路徑進行組合,內容URI就變成了com.example.app.provider/table1和com.example.app.provider/table2.只是眼下還很是難辨認出這兩個字符串是兩個內容URI,咱們還需要在字符串的頭部加上協議聲明。所以,內容提供器最標準的格式寫法例如如下:post
content://com.example.app.provider/table1
content://com.example.app.provider/table2
這時候URI就可以很清楚地表達咱們想要訪問哪一個程序中哪張表裏的數據。
在獲得了內容URI字符串以後。咱們還需要將他解析成Uri對象才幹夠做爲參數傳入。解析的方法也至關簡單。代碼例如如下:
Uri uri=Uri.parse("content://com.example.app.provider/table1");
僅僅需要調用Uri.parse()方法,就可以將內容URI字符串解析成Uri對象了。
現在就可以使用這個Uri對象來查詢table1表中的數據了。代碼例如如下所看到的:
Cursor cursor=getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder
);
參數的詳解見如下的內容:
query() | 相應SQL部分 |
描寫敘述 |
uri |
from table_name | 指定查詢某個應用程序下的某一張表 |
projection |
selection column1。column2 | 指定查詢的列名 |
selection |
where column=value | 指定where的約束條件 |
selectionArgs |
- | 爲where中的佔位符提供詳細的值 |
sortOrder |
order by column1。column2 | 指定查詢結果的排序方式 |
查詢完畢後返回的仍然是一個Cursor對象,這時咱們就可以將數據從Cursor對象中逐個讀取出來了。
讀取的思路仍然是經過移動遊標的位置來遍歷Cursor的所有行。而後再取出每一行中對應列的數據,代碼例如如下所看到的:
if(cursor!=null){
while(cursor.moveToNext()){
String column1=cursor.getString(cursor.getColumnIndex("column1"));
String column2=cursor.getString(cursor.getColumnIndex("column2"));
}
cursor.close();
}
掌握了查詢操做。如下咱們來看看怎樣向table1中加入一條數據。代碼例如如下:
ContentValues values=new ContentValues();
values.put("column1"."text");
values.put("column2",1);
getContentResolver().insert(uri,values);
可以看到仍然是將待加入的數據組裝到ContentValues中,而後調用ContentResolver的insert()方法,將Uri和ContentValues做爲參數傳入就能夠。
假設咱們想要更新這條新加入的數據,把column1的值清空。可以藉助ContentResolver的update()方法實現,代碼例如如下:
ContentValues values=new ContentValues();
values.put("column1"."");
getContentResolver().update(uri,values,"column1=? and column2=?",new String[]{"text","1"});
注意上述代碼使用了selection和selectionArgs參數來對想要更新的數據進行約束,以防止所有行都會受影響。
最後。可以調用ContentResolver的delete()方法將這條數據刪除掉,代碼例如如下:
getContentResolver().delete(uri,values,"column2=?
",new String[]{"1"});
主要的操做,已經作了一個小結了,如下進行實戰。
讀取系統聯繫人
由於要讀取系統的聯繫人信息,而模擬器中沒有聯繫人信息的存在,因此先手動加入幾個聯繫人信息。打開電話簿程序,界面例如如下所看到的:
眼下電話簿裏面沒有不論什麼聯繫人,現在點擊create a new contactbutton來對聯繫人進行建立。
這裏先建立2個聯繫人,分別輸入他們的姓名和手機號碼,例如如下:
作好了準備工做了,現在新建一個ContactsTest項目。
改動activity_main.xml佈局文件。使讀取出來的聯繫人信息能夠在ListView中顯示,所以改動代碼例如如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:id="@+id/contacts_view" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView> </LinearLayout>
<pre name="code" class="java">package com.jack.contactstest; import java.util.ArrayList; import java.util.List; import android.os.Bundle; import android.provider.ContactsContract; import android.app.Activity; import android.database.Cursor; import android.view.Menu; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private ListView contactsView; private ArrayAdapter<String> adapter; private List<String> contactsList=new ArrayList<String>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //得到ListView組件 contactsView=(ListView) findViewById(R.id.contacts_view); adapter=new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList); contactsView.setAdapter(adapter);//設置適配器 readContacts(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } private void readContacts(){ Cursor cursor=null;//定義遊標 try{ //查詢聯繫人數據 cursor=(Cursor) getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); /* * 上面可以看到query()方法中沒有使用Uri.parse()方法去解析一個內容URI字符串,這是因爲 * ContactsContract.CommonDataKinds.Phone類已經幫咱們作好了封裝,提供了一個CONTENT_URI常量 * ,而這個常量就是使用Uri.parse()方法解析出來的結果,接着對Cursor對象進行遍歷。將聯繫人姓名和手機號碼這些數據 * 逐個取出來。聯繫人姓名相應的常量是ContactsContract.CommonDataKinds.Photo.DISPLAY_NAME。 * 聯繫人手機號碼這一列相應的常量是ContactsContract.CommonDataKinds.Phone.NUMBER。這兩個數據取出來後 * 放ListView中。最後記得關閉Cursor對象。 * */ //遍歷cursor取出數據 while(cursor.moveToNext()){ //獲取聯繫人姓名 String name=cursor.getString(cursor.getColumnIndex( ContactsContract.CommonDataKinds.Photo.DISPLAY_NAME)); //獲取聯繫人手機號碼 String number=cursor.getString(cursor.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER)); //把數據加入到contactsList中 contactsList.add(name+"\n"+number); } }catch(Exception e){ e.printStackTrace(); }finally{ if(cursor!=null){ cursor.close();//關閉遊標 } } } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jack.contactstest" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="13" android:targetSdkVersion="17" /> <!--添加權限 --> <uses-permission android:name="android.permission.READ_CONTACTS"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.jack.contactstest.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
剛剛在模擬器中加入的2個聯繫人數據都已經成功讀取出來了。跨程序訪問的功能的確實現了。
建立本身的內容提供器
假設想實現跨程序共享數據的功能,官方推薦的方式就是使用內容提供器,可以經過新建一個類去繼承ContentProvider的方式來建立一個本身的內容提供器。ContentProvider類中有6個抽象方法,咱們在使用子類繼承它時候,需要將這六個方法全部重寫。
新建一個MyProvider繼承自ContentProvider,代碼例如如下所看到的:
package com.jack.contactstest; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; public class MyProvider extends ContentProvider { /* * delete()方法從內容提供器中刪除數據。使用uri參數來肯定刪除哪一張表中的數據,selection和 * selectionArgs參數用於約束刪除哪些行,被刪除的行將做爲返回值返回。* */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } /* * getType()依據傳入的內容URI來返回對應的MIME類型。 * */ @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } /* * insert()方法向內容提供器中加入一條數據。使用uri參數來肯定加入到的表。待加入的數據保存在values參數中。 * 加入完畢後,返回一個用於表示這條新記錄的URI * */ @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub return null; } /* * onCreate()方法初始化內容提供器的時候調用。通常會在這裏完畢對數據庫的建立和升級等操做,返回 * true表示內容提供器初始化成功。返回false則表示失敗。
注意僅僅有當存在ContentResolver嘗試訪問 * 咱們程序中的數據時。內容提供器纔會被初始化。 * */ @Override public boolean onCreate() { // TODO Auto-generated method stub return false; } /* * query()方法從內容提供器中查詢數據。
使用uri參數來肯定查詢哪張表,projection參數用於肯定查詢 * 哪些列。selection和selectionArgs參數用於約束查詢哪些行。sortOrder參數用於對結果進行排序。 * 查詢的結果存放在Cursor對象中返回。 * */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub return null; } /* * update()更新內容提供器中已有的數據。使用uri參數來肯定更新哪一張表中的數據。更新數據保存 * 在values中。selection和selectionArgs參數用於約束更新哪些行,受影響的行將做爲返回值返回。 * */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } }
可以看到很是多的方法都需要Uri這個參數,這個參數正是調用ContentResolver的增刪改查方法時傳遞過來的。
而現在。咱們需要對傳入的Uri參數進行解析。從中分析出調用方指望訪問的表和數據。
一個標準的URI寫法例如如下:
content://com.example.app.provider/table1
這就表示調用方指望訪問的是com.example.app這個應用的table1表中的數據。
除此以外,咱們還可以在這個內容URI的後面加入一個id,例如如下所看到的:
content://com.example.app.provider/table1/1
這就表示調用方指望訪問的是com.example.app這個應用的table1表中id爲1的數據。
內容URI的格式主要就僅僅有以上2種,以路徑結尾就表示指望訪問該表中所有的數據,以id結尾就表示指望訪問該表中擁有對應id的數據。咱們可以使用通配符的方式來分別匹配這兩種格式的內容URI。規則例如如下:
1. *:表示匹配隨意長度的隨意字符
2. # :表示匹配隨意長度的數字
因此一個能夠匹配隨意表的內容URI格式就行寫出:
content://com.example.app.provider/*
而一個能夠匹配table1表中隨意一行數據的內容URI格式就行寫成:
content://com.example.app.provider/table1/#
接着咱們再借助UriMatcher這個類就可以輕鬆的實現匹配內容URI的功能。
UriMatcher中提供了一個addURI()方法,這種方法接收三個參數,可以分別把權限,路徑和本身定義代碼傳進去。
這樣,當調用UriMatcher的match()方法時就可以將一個Uri對象傳入,返回值是某個可以匹配這個Uri對象所相應的本身定義代碼,利用這個代碼,咱們就可以推斷出調用方指望訪問的是哪一張表中的數據了。改動MyProvider中的代碼。例如如下所看到的:
package com.jack.contactstest; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; public class MyProvider extends ContentProvider { /* * MyProvider中新增四個整形常量,當中TABLE1_DIR表示訪問table1表中的所有數據, * TABLE1_ITEM表示訪問的table1表中的單條數據,TABLE2_DIR表示訪問table2表中的所有數據。 * TABLE2_ITEM表示訪問的table2表中的單條數據。 * */ public static final int TABLE1_DIR=0; public static final int TABLE1_ITEM=1; public static final int TABLE2_DIR=2; public static final int TABLE2_ITEM=3; private static UriMatcher uriMatcher; /* * 上面定義常量之後,接着在靜態代碼塊裏,建立UriMatcher的實例。並調用addURI()方法,將指望匹配的內容 * URI格式傳遞進去。注意這裏傳入的路徑參數是可以使用通配符的。而後當query()方法被調用的時候。就會經過UriMatcher * 的match()方法對傳入的Uri對象進行匹配,假設發現UriMatcher中某個內容URI格式成功匹配了該Uri對象。則 * 返回對應的本身定義代碼,而後就可以推斷指望訪問的到底是什麼數據了。這裏僅僅使用query()方法作了一個示範,事實上 * insert(),update(),delete()這幾個方法的實現也是差點兒相同的,它們都會攜帶Uri這個參數,而後相同利用 * UriMatcher的match()方法推斷出調用指望訪問的是哪一張表,在對該表中的數據進行對應的操做就可以了。 * */ static{ uriMatcher=new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("com.jack.contactstest.provider", "table1", TABLE1_DIR); uriMatcher.addURI("com.jack.contactstest.provider", "table1/#", TABLE1_ITEM); uriMatcher.addURI("com.jack.contactstest.provider", "table2", TABLE2_DIR); uriMatcher.addURI("com.jack.contactstest.provider", "table2/#", TABLE2_ITEM); } /* * delete()方法從內容提供器中刪除數據。使用uri參數來肯定刪除哪一張表中的數據,selection和 * selectionArgs參數用於約束刪除哪些行,被刪除的行將做爲返回值返回。 * */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } /* * getType()依據傳入的內容URI來返回對應的MIME類型。
* */ @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } /* * insert()方法向內容提供器中加入一條數據。使用uri參數來肯定加入到的表,待加入的數據保存在values參數中。 * 加入完畢後。返回一個用於表示這條新記錄的URI * */ @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub return null; } /* * onCreate()方法初始化內容提供器的時候調用。通常會在這裏完畢對數據庫的建立和升級等操做,返回 * true表示內容提供器初始化成功。返回false則表示失敗。注意僅僅有當存在ContentResolver嘗試訪問 * 咱們程序中的數據時。內容提供器纔會被初始化。
* */ @Override public boolean onCreate() { // TODO Auto-generated method stub return false; } /* * query()方法從內容提供器中查詢數據。
使用uri參數來肯定查詢哪張表。projection參數用於肯定查詢 * 哪些列,selection和selectionArgs參數用於約束查詢哪些行。sortOrder參數用於對結果進行排序。 * 查詢的結果存放在Cursor對象中返回。 * */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub switch(uriMatcher.match(uri)){ case TABLE1_DIR: //查詢table1表中的所有數據 break; case TABLE1_ITEM: //查詢table1表中的單條數據 break; case TABLE2_DIR: //查詢table2表中的所有數據 break; case TABLE2_ITEM: //查詢table2表中的單條數據 break; } return null; } /* * update()更新內容提供器中已有的數據。使用uri參數來肯定更新哪一張表中的數據,更新數據保存 * 在values中。selection和selectionArgs參數用於約束更新哪些行,受影響的行將做爲返回值返回。 * */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } }
現在可能就對getType()方法比較陌生了。
getType()方法是所有內容提供器都必須提供的一個方法,用於獲取Uri對象所相應的MIME類型。
一個內容URI所相應的MIME字符串主要由三部分組成,android對這三個部分作了例如如下格式的規定:
1.必須以vnd開頭。
2.假設內容URI以路徑結尾,則後接android.cursor.dir/,假設內容URI以id結尾,則後接android.cursor.item/。
3.最後接上vnd.<authority>.<path>。
因此,對於content://com.example.app.provider/table1這個內容URI,它所相應的MIME類型就可以寫成:
vnd.android.cursor.dir/vnd.com.example.app.provider/table1
對於content://com.example.app.provider/table1/1這個內容URI,它所相應的MIME類型就可以寫成:
vnd.android.cursor.item/vnd.com.example.app.provider/table1
現在咱們繼續無缺MyProvider中的內容,此次來實現getType()方法中的邏輯,代碼例如如下所看到的:
package com.jack.contactstest; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; public class MyProvider extends ContentProvider { /* * MyProvider中新增四個整形常量,當中TABLE1_DIR表示訪問table1表中的所有數據, * TABLE1_ITEM表示訪問的table1表中的單條數據,TABLE2_DIR表示訪問table2表中的所有數據, * TABLE2_ITEM表示訪問的table2表中的單條數據。* */ public static final int TABLE1_DIR=0; public static final int TABLE1_ITEM=1; public static final int TABLE2_DIR=2; public static final int TABLE2_ITEM=3; private static UriMatcher uriMatcher; /* * 上面定義常量之後,接着在靜態代碼塊裏,建立UriMatcher的實例,並調用addURI()方法,將指望匹配的內容 * URI格式傳遞進去,注意這裏傳入的路徑參數是可以使用通配符的。而後當query()方法被調用的時候,就會經過UriMatcher * 的match()方法對傳入的Uri對象進行匹配。假設發現UriMatcher中某個內容URI格式成功匹配了該Uri對象,則 * 返回對應的本身定義代碼,而後就可以推斷指望訪問的到底是什麼數據了。這裏僅僅使用query()方法作了一個示範,事實上 * insert(),update(),delete()這幾個方法的實現也是差點兒相同的,它們都會攜帶Uri這個參數,而後相同利用 * UriMatcher的match()方法推斷出調用指望訪問的是哪一張表,在對該表中的數據進行對應的操做就可以了。 * */ static{ uriMatcher=new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI("com.jack.contactstest.provider", "table1", TABLE1_DIR); uriMatcher.addURI("com.jack.contactstest.provider", "table1/#", TABLE1_ITEM); uriMatcher.addURI("com.jack.contactstest.provider", "table2", TABLE2_DIR); uriMatcher.addURI("com.jack.contactstest.provider", "table2/#", TABLE2_ITEM); } /* * delete()方法從內容提供器中刪除數據。使用uri參數來肯定刪除哪一張表中的數據。selection和 * selectionArgs參數用於約束刪除哪些行,被刪除的行將做爲返回值返回。
* */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } /* * getType()依據傳入的內容URI來返回對應的MIME類型。 * */ @Override public String getType(Uri uri) { // TODO Auto-generated method stub switch(uriMatcher.match(uri)){ case TABLE1_DIR: //查詢table1表中的所有數據 return "vnd.android.cursor.dir/vnd.com.jack.contactstest.table1"; case TABLE1_ITEM: //查詢table1表中的單條數據 return "vnd.android.cursor.item/vnd.com.jack.contactstest.table1"; case TABLE2_DIR: //查詢table2表中的所有數據 return "vnd.android.cursor.dir/vnd.com.jack.contactstest.table2"; case TABLE2_ITEM: //查詢table2表中的單條數據 return "vnd.android.cursor.item/vnd.com.jack.contactstest.table2"; default:break; } return null; } /* * insert()方法向內容提供器中加入一條數據。使用uri參數來肯定加入到的表,待加入的數據保存在values參數中。 * 加入完畢後,返回一個用於表示這條新記錄的URI * */ @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub return null; } /* * onCreate()方法初始化內容提供器的時候調用。通常會在這裏完畢對數據庫的建立和升級等操做,返回 * true表示內容提供器初始化成功,返回false則表示失敗。
注意僅僅有當存在ContentResolver嘗試訪問 * 咱們程序中的數據時。內容提供器纔會被初始化。 * */ @Override public boolean onCreate() { // TODO Auto-generated method stub return false; } /* * query()方法從內容提供器中查詢數據。使用uri參數來肯定查詢哪張表,projection參數用於肯定查詢 * 哪些列。selection和selectionArgs參數用於約束查詢哪些行,sortOrder參數用於對結果進行排序, * 查詢的結果存放在Cursor對象中返回。
* */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub switch(uriMatcher.match(uri)){ case TABLE1_DIR: //查詢table1表中的所有數據 break; case TABLE1_ITEM: //查詢table1表中的單條數據 break; case TABLE2_DIR: //查詢table2表中的所有數據 break; case TABLE2_ITEM: //查詢table2表中的單條數據 break; } return null; } /* * update()更新內容提供器中已有的數據。使用uri參數來肯定更新哪一張表中的數據。更新數據保存 * 在values中,selection和selectionArgs參數用於約束更新哪些行。受影響的行將做爲返回值返回。 * */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } }
到這裏。一個完整的內容提供器就建立完畢了,現在不論什麼一個應用程序都可以使用ContentResolver來訪問咱們程序中的數據。
那麼怎樣才幹保證隱私數據不會泄漏出去呢?事實上多虧了內容提供器的良好機制。這個問題已經已經在不知不覺中被攻克了。
因爲所有的CRUD操做都必定要匹配到對應的內容URI格式才幹進行,而咱們固然不可能向UriMatcher中加入隱私數據的URI,因此這部分數據根本沒法被外部程序訪問到,安全問題也就不存在了。如下進行實戰。體驗一下跨程序共享的功能。
實現跨程序數據共享
簡單起見,咱們使用上一篇博客的DatabaseTest的項目,在該項目的基礎上進行改動繼續開發,經過內容提供器給它加入外部訪問接口。打開DatabaseTest項目。首先將MyDatabaseHelper中使用Toast彈出建立數據成功的提示去掉,因爲跨程序訪問時咱們不能直接使用Toast。
而後加入一個DatabaseProvider類,代碼例如如下所看到的:
package com.jack.databasetest; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; public class DatabaseProvider extends ContentProvider { //本身定義代碼 public static final int BOOK_DIR=0; public static final int BOOK_ITEM=1; public static final int CATEGORY_DIR=2; public static final int CATEGORY_ITEM=3; //權限 public static final String AUTHORITY="com.jack.databasetest.provider"; private static UriMatcher uriMatcher; private MyDatabaseHelper dbHelper; //靜態代碼塊進行初始話 static { uriMatcher=new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR); uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM); uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR); uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub //刪除數據 SQLiteDatabase db=dbHelper.getWritableDatabase(); int deleteRows=0; switch(uriMatcher.match(uri)){ case BOOK_DIR: deleteRows=db.delete("book", selection, selectionArgs); break; case BOOK_ITEM: String bookId=uri.getPathSegments().get(1); deleteRows=db.delete("book", "id=?", new String[]{bookId}); break; case CATEGORY_DIR: deleteRows=db.delete("category", selection, selectionArgs); break; case CATEGORY_ITEM: String categoryId=uri.getPathSegments().get(1); deleteRows=db.delete("category", "id=?",new String[]{categoryId}); break; default: break; } return deleteRows;//被刪除的行數做爲返回值返回 } @Override public String getType(Uri uri) { // TODO Auto-generated method stub switch(uriMatcher.match(uri)){ case BOOK_DIR: return "vnd.android.cursor.dir/vnd.com.jack.databasetest.provider.book"; case BOOK_ITEM: return "vnd.android.cursor.item/vnd.com.jack.databasetest.provider.book"; case CATEGORY_DIR: return "vnd.android.cursor.dir/vnd.com.jack.databasetest.provider.category"; case CATEGORY_ITEM: return "vnd.android.cursor.item/vnd.com.jack.databasetest.provider.category"; } return null; } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub //加入數據 SQLiteDatabase db=dbHelper.getWritableDatabase(); Uri uriReturn=null; switch(uriMatcher.match(uri)){ case BOOK_DIR: case BOOK_ITEM: long newBookId=db.insert("book", null, values); uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newBookId); break; case CATEGORY_DIR: case CATEGORY_ITEM: long newCategoryId=db.insert("category", null, values); uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newCategoryId); break; default: break; } /* * insert()方法要求返回一個能夠表示這條新增數據的URI。因此需要調用Uri.parse()方法來將一個內容 * URI解析成Uri對象,固然這個內容是以新增數據的id結尾的。
* */ return uriReturn; } @Override public boolean onCreate() { // TODO Auto-generated method stub dbHelper=new MyDatabaseHelper(getContext(), "BookStore.db", null, 2); return true;//返回true表示內容提供器初始化成功,這時數據庫就已經完畢了建立或升級操做 } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub //查詢數據 SQLiteDatabase db=dbHelper.getReadableDatabase();//得到SQLiteDatabase對象 Cursor cursor=null; switch(uriMatcher.match(uri)){ case BOOK_DIR: //進行查詢 cursor=db.query("book", projection, selection, selectionArgs, null, null, sortOrder); break; case BOOK_ITEM: //進行查詢 /*Uri對象的getPathSegments()方法會將內容URI權限以後的部分以「、」符號進行切割。並把切割後的結果 * 放入到一個字符串列表中。那這個列表的第0個位置存放的就是路徑,第1個位置存放的就是id,獲得id後,在經過 * selection和selectionArgs參數就實現了查詢單條數據的功能。 * */ String bookId=uri.getPathSegments().get(1); cursor=db.query("book", projection, "id=?", new String[]{bookId}, null, null, sortOrder); break; case CATEGORY_DIR: //進行查詢 cursor=db.query("category", projection, selection, selectionArgs, null, null, sortOrder); break; case CATEGORY_ITEM: //進行查詢 String categoryId=uri.getPathSegments().get(1); cursor=db.query("book", projection, "id=?
", new String[]{categoryId}, null, null, sortOrder); break; default: break; } return cursor; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub SQLiteDatabase db=dbHelper.getWritableDatabase(); int updatedRows=0; //更新數據 switch(uriMatcher.match(uri)){ case BOOK_DIR: updatedRows=db.update("book", values, selection,selectionArgs); break; case BOOK_ITEM: String bookId=uri.getPathSegments().get(1); updatedRows=db.update("book", values, "id=?", new String[]{bookId}); break; case CATEGORY_DIR: updatedRows=db.update("category", values, selection,selectionArgs); break; case CATEGORY_ITEM: String categoryId=uri.getPathSegments().get(1); updatedRows=db.update("book", values, "id=?", new String[]{categoryId}); break; default: break; } return updatedRows;//受影響的行數做爲返回值 } }
上面的功能,在凝視已經說名了,就很少說了。通過上面的步驟,內容提供器的代碼全部編寫完了。只是離跨實現程序數據共享的功能還差了一小步,因爲還需要將內容提供器在AndroidManifest.xml文件裏註冊才幹夠,例如如下所看到的:
<pre name="code" class="html"><?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.jack.databasetest" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="13" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.jack.databasetest.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:name="com.jack.databasetest.DatabaseProvider" android:authorities="com.jack.databasetest.provider" android:exported="true" ></provider> </application> </manifest>
android:exported="true"上面三個屬性需要加入的。否則。就不能實現跨程序訪問了。我剛開始沒加android:exported="true"這個屬性,值加了上面的android:name="com.jack.databasetest.DatabaseProvider"
現在DatabaseTest這個項目就已經擁有了跨程序共享數據的功能了,現在咱們來試試。首先需要將DatabaseTest程序從模擬器中刪除掉,以防止曾經的遺留數據對咱們產生影響。
而後執行下項目,將DatabaseTest程序重寫安裝在模擬器上。接着關閉這個項目,並建立一個新項目ProviderTest,咱們就經過這個程序去訪問DatabaseTest中的數據。
先改動下ProviderTest的佈局文件activity_main.xml中的代碼,例如如下所看到的:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/add_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="add data to book" /> <Button android:id="@+id/query_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="query from book" /> <Button android:id="@+id/update_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="update book" /> <Button android:id="@+id/delete_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="delete data from book" /> </LinearLayout>
放置了四個button。分別用來加入數據,查詢。改動和刪除數據。而後在改動MainActivity中的代碼,例如如下所看到的:
package com.jack.providertest; import android.net.Uri; import android.os.Bundle; import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { private String newId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button addData=(Button) findViewById(R.id.add_data); addData.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub //加入數據 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book"); ContentValues values=new ContentValues(); values.put("name", "a clash of kings"); values.put("author", "george martin"); values.put("pages", 1050); values.put("price", 88.9); Uri newUri=getContentResolver().insert(uri, values);//插入數據 newId=newUri.getPathSegments().get(1); } }); Button queryData=(Button) findViewById(R.id.query_data); queryData.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub //查詢數據 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book"); Cursor cursor=getContentResolver().query(uri, null, null, null, null); if(cursor!=null){ while(cursor.moveToNext()){ String name=cursor.getString(cursor.getColumnIndex("name")); String author=cursor.getString(cursor.getColumnIndex("author")); int pages=cursor.getInt(cursor.getColumnIndex("pages")); double price=cursor.getDouble(cursor.getColumnIndex("price")); Log.d("MainActivity", "book name is "+name); Log.d("MainActivity", "book author is "+author); Log.d("MainActivity", "book pages is "+pages); Log.d("MainActivity", "book price is "+price); } cursor.close(); } } }); Button updateData=(Button) findViewById(R.id.update_data); updateData.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub //更新數據 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book/"+newId); ContentValues values=new ContentValues(); values.put("name", "a storm of swords"); values.put("pages", 1216); values.put("price", 77.8); getContentResolver().update(uri, values, null, null); } }); Button deleteData=(Button) findViewById(R.id.delete_data); deleteData.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub //刪除數據 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book/"+newId); getContentResolver().delete(uri, null, null); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
點擊add data to book,此時數據應該已經加入到DatabaseTest程序的數據庫中了 。咱們經過點擊query form bookbutton來檢查下,打印日誌例如如下:
而後點擊下update bookbutton來更新數據,在點擊下query from bookbutton進行檢查,結果例如如下:
最後再點擊delete data frombook按刪除數據,此時再點擊query from bookbutton就查詢不到數據了。
經過上面的測試。咱們的跨程序共享數據功能已經成功實現了。!
不只是ProviderTest程序。不論什麼一個程序都可以輕鬆訪問DatabaseTest中的數據,而且咱們還絲絕不用操心隱私數據泄漏的問題。
轉載請註明來自:http://blog.csdn.net/j903829182/article/details/41150089