Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android學習十二(android的Content Provider(內容提供器)的使用)

android學習十二(android的Content Provider(內容提供器)的使用)

編輯:關於Android編程

文件存儲和SharePreference存儲以及數據存儲一般為了安全,最好用於當前應用程序中訪問和存儲數據。內容提供器(Content Provider)主要用於在不同的應用程序之間實現數據共享的功能,它提供了一套完整的機制,允許一個程序訪問另一個程序中的數據,同時還能保證被訪問數據的安全性。目前使用內容提供器是android實現跨程序共享數據的標准方式。內容提供器可以選擇只對一部分數據進行共享,從而保證我們的程序中的隱私數據不會有洩漏的風險。

內容提供器的用法一般有兩種,一種是使用現有的內容提供器來讀取和操作相應程序中的數據,另一種是創建自己的內容提供器給我們程序的數據提供外部訪問接口。


訪問其他程序中的數據

當一個應用程序通過內容提供器對其數據了外部訪問接口,任何其他的應用程序就都可以對這部分數據進行訪問了。android系統中自帶的電話薄,短信,媒體等程序都提供了類似的訪問接口,這就使得第三方應用程序可以充分地利用這部分數據來實現更好的功能。

ContentResolver的基本用法

對於一個應用程序來說,如果想要訪問內容提供器中共享的數據,就一定要借助ContentResolver類,可以通過Context中的getContentResolver()方法獲取到該類的實例。ContentResolver中提供了一系列的方法用於對數據進行CRUD操作,其中insert()方法用於添加數據,update()方法用於更新數據,delete()方法用於刪除數據,query()方法用於查詢數據。

不同於SQLiteDatabase,ContentResolver中的增刪改查方法都是不接收表名參數的,而是使用一個Uri參數替代,這個參數被稱為內容URI。內容URI給內容提供器中的數據建立了唯一標識符,它主要由2部分組成,權限(authority)和路徑(path)。權限是用於對不同的應用程序做區分的,一般為了避免沖突,都會采用程序包名的方式來進行命名。比如某個程序的包名是com.example.app,那麼該程序對應的權限就可以命名為com.example.app.provider.路徑則是用於對同一應用程序中不同的表做區分的,通常都會添加到權限的後面。比如某個程序的數據庫裡存在兩張表,table1和table2,這時就可以將路徑分別命名為/table1和table2,然後把權限和路徑進行組合,內容URI就變成了com.example.app.provider/table1和com.example.app.provider/table2.不過目前還很難辨認出這兩個字符串是兩個內容URI,我們還需要在字符串的頭部加上協議聲明,因此,內容提供器最標准的格式寫法如下:

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"});

基本的操作,已經做了一個小結了,下面進行實戰。



讀取系統聯系人

由於要讀取系統的聯系人信息,而模擬器中沒有聯系人信息的存在,所以先手動添加幾個聯系人信息。打開電話簿程序,界面如下所示:

\



<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+xL/HsLXnu7CyvsDvw+bDu9PQyM66zsGqz7XIy6Osz9bU2rXju/djcmVhdGUgYSBuZXcgY29udGFjdLC0xaXAtLbUwarPtcjLvfjQ0LS0vaiho9XiwO/PyLS0vagyuPbBqs+1yMujrLfWsfDK5Mjry/vDx7XE0NXD+7rNyta7+rrFwuujrMjnz8KjujwvcD4KPHA+PGltZyBzcmM9"/uploadfile/Collfiles/20141117/201411170855363.jpg" alt="\">


做好了准備工作了,現在新建一個ContactsTest項目。

修改activity_main.xml布局文件,使讀取出來的聯系人信息能夠在ListView中顯示,因此修改代碼如下:



    
    



接著修改MainActivity中的代碼,如下:

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 adapter;
	private List contactsList=new ArrayList();
	@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(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();//關閉游標
			}
		}
		
	}
	
	
}



讀取聯系人信息需要權限,因此要修改AndroidManifest.xml中的代碼,如下:




    
    
   
    
        
            
                

                
            
        
    



運行程序,效果如下:

\


剛剛在模擬器中添加的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..

所以,對於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文件中注冊才可以,如下所示:




    

    
        
            
                

                
            
        
        
        
        
        
    





android:exported="true"上面三個屬性需要添加的,不然,就不能實現跨程序訪問了。我剛開始沒加
android:exported="true"這個屬性,值加了上面的android:name="com.jack.databasetest.DatabaseProvider"
android:authorities="com.jack.databasetest.provider"屬性,程序訪問出現安全問題了,百度後,說是需要android:exported="true"這個屬性,才能跨程序被其他的程序訪問。我試試了下,當中需要這個屬性,不然後面進行跨程序訪問的時候會出現錯誤。


現在DatabaseTest這個項目就已經擁有了跨程序共享數據的功能了,現在我們來試試。首先需要將DatabaseTest程序從模擬器中刪除掉,以防止以前的遺留數據對我們產生影響。然後運行下項目,將DatabaseTest程序重寫安裝在模擬器上。接著關閉這個項目,並創建一個新項目ProviderTest,我們就通過這個程序去訪問DatabaseTest中的數據。

先修改下ProviderTest的布局文件activity_main.xml中的代碼,如下所示:



    


放置了四個按鈕,分別用來添加數據,查詢,修改和刪除數據。然後在修改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;
	}

}

現在運行下ProviderTest項目,顯示如下:

\


點擊add data to book,此時數據應該已經添加到DatabaseTest程序的數據庫中了 ,我們通過點擊query form book按鈕來檢查下,打印日志如下:

\




然後點擊下update book按鈕來更新數據,在點擊下query from book按鈕進行檢查,結果如下:





最後再點擊delete data frombook按刪除數據,此時再點擊query from book按鈕就查詢不到數據了。

通過上面的測試,我們的跨程序共享數據功能已經成功實現了!!不僅是ProviderTest程序,任何一個程序都可以輕松訪問DatabaseTest中的數據,而且我們還絲毫不用擔心隱私數據洩漏的問題。



轉載請注明來自:http://blog.csdn.net/j903829182/article/details/41150089


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved