android開發筆記(五)ContentProvider

1 ContentProvider簡介

1.1 爲什麼要有ContentProvider?

  • 功能需求: 一個應用需要訪問另一個應用的數據庫表數據
  • 實際情況: 一個應用的數據庫文件是應用私有的, 其它應用不能直接訪問

1.2 ContentProvider是什麼?

  • ContentProvider是四大應用組件之一
  • 當前應用使用ContentProvider將數據庫表數據操作暴露給其他應用訪問
  • 其他應用需要使用ContentResolver來調用ContentProvider

1.3 ContentProvider相關API

  • public abstract boolean onCreate();//provider對象創建後調用(應用安裝成功或手機啓動完成)。
  • Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs);//查詢表數據
  • Uri insert(Uri uri,ContentValues values);//插入表數據
  • int delete(Uri uri,String selection,String selectionArgs);//刪除表數據
  • update(Uri uri,ContentValues values,String selection,String selectionArgs);//更新表數據

1.4 ContentResolver:內容提供者的解析類

  • context.getContentResolver();//得到它的對象

  • 調用provider進行CRUD(增刪改查)操作

  • registerContentObserver(Uri uri,boolean notify,ContentObserver observer);//註冊uri的監聽器

  • unRegisterContentObserver(ContentObserver observer);//解註冊uri的監聽器

  • notifyChange(Uri uri,ContentObserver observer);//通知監聽器

1.5 Uri:包含請求地址數據的類

  • Uri static parse(String uriString);//得到其對象

1.6 UriMatcher:用於匹配Uri的容器

  • void addURI(String authority,String path,int code);//添加一個合法的URI
  • int match(Uri uri);//匹配指定的URI,返回匹配碼

1.7 ContentUris:解析URI的工具類

  • long parseId(Uri contentUri);//解析uri,得到其中的id
  • Uri withAppendedId(Uri contentUri,long id);//添加id到指定的uri中

1.8 MIME類型

MIME(Multipurpose Internet Mail Extensions)即多用途互聯網郵件擴展類型,是指定某種擴展名的文件用什麼應用程序來打開的方式類型。當該擴展名文件被訪問的時候,瀏覽器會自動使用指定應用程序來打開。多用於指定一些客戶端自定義的文件名,以及一些媒體文件打開方式。

常用的一些mime類型如下表格

類型/子類型(Content-Type/subtype ) 擴展名

application/vnd.android.package-archive

.apk
text/plain .txt
image/jpeg .jpeg
text/html .html
audio/x-pn-realaudio .rmvb
audio/mpeg .mp3
video/mp4 .mp4
image/png .png
application/json .json
application/pdf .pdf

自定義MIME類型

對於多條數據:

vnd.android.cursor.dir

示例:

vnd.android.cursor.dir/vnd.example.line1

 

對於單條數據

vnd.android.cursor.item

示例:

vnd.android.cursor.item/vnd.example.line2

1.9 給ContentProvider制定權限

<!-- student provider 訪問權限聲明 -->
    <permission
        android:name="com.android.peter.provider.READ_PERMISSION"
        android:label="Student provider read permission"
        android:protectionLevel="normal"
        />
    <permission
        android:name="com.android.peter.provider.WRITE_PERMISSION"
        android:label="Student provider read permission"
        android:protectionLevel="normal"
        />

    <!-- 聲明ContentProvider -->
    <application
         ...
         <provider
            android:name=".StudentContentProvider"
            android:authorities="com.android.peter.provider"
            android:readPermission="com.android.peter.provider.READ_PERMISSION"
            android:writePermission="com.android.peter.provider.WRITE_PERMISSION"
            android:exported="true"/>
        ...
    </application>

爲了方便起見,權限聲明時protectionLevel設置的是最低風險權限(normal),關於其他等級權限和說明如下:

權限等級 說明
normal 低風險權限,只要申請了就可以使用,安裝時不需要用戶確認。
dangerous 高風險權限,安裝時需要用戶確認授權纔可使用。
signature 只有當申請權限應用與聲明此權限應用的數字簽名相同時才能將權限授給它。
signatureOrSystem 簽名相同或者申請權限的應用爲系統應用才能將權限授給它。

2 簡單示例代碼:

2.1 SQLiteOpenHelper

package com.atguigu.l09_provider;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DBHelper extends SQLiteOpenHelper {

	public DBHelper(Context context) {
		super(context, "atguigu.db", null, 1);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		Log.e("TAG", "onCreate()...");
		//建表
		db.execSQL("create table person(_id integer primary key autoincrement, name varchar)");
		//插入初始化數據
		db.execSQL("insert into person (name) values ('Tom')");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

	}

}

2.2 ContentProvider

package com.atguigu.l09_provider;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

/**
 * 操作person表的provider類
 * @author 張曉飛
 *
 */
public class PersonProvider extends ContentProvider {

	//用來存放所有合法的Uri的容器
	private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
	//保存一些合法的uri
	// content://com.atguigu.l09_provider.personprovider/person 不根據id操作
	// content://com.atguigu.l09_provider.personprovider/person/3 根據id操作
	static {
		matcher.addURI("com.atguigu.l09_provider.personprovider", "/person", 1);
		matcher.addURI("com.atguigu.l09_provider.personprovider", "/person/#", 2);  //#匹配任意數字
	}
	private DBHelper dbHelper;
	public PersonProvider() {
		Log.e("TAG", "PersonProvider()");
	}
	@Override
	public boolean onCreate() {
		Log.e("TAG", "PersonProvider onCreate()");
		dbHelper = new DBHelper(getContext());
		
		return false;
	}

	/**
	 * content://com.atguigu.l09_provider.personprovider/person 不根據id查詢 
	 * content://com.atguigu.l09_provider.personprovider/person/3 根據id查詢 
	 */
	@Override
	public Cursor query(Uri uri, String[] projection, String selection,
			String[] selectionArgs, String sortOrder) {
		Log.e("TAG", "PersonProvider query()");
		
		//得到連接對象
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		
		//1.匹配uri, 返回code
		int code = matcher.match(uri);
		//如果合法, 進行查詢
		if(code==1) {//不根據id查詢
			Cursor cursor = database.query("person", projection, selection, selectionArgs, null, null, null);
			return cursor;
		} else if(code==2) {//根據id查詢 
			//得到id
			long id = ContentUris.parseId(uri);
			//查詢
			Cursor cursor = database.query("person", projection, "_id=?", new String[]{id+""}, null, null, null);
			return cursor;
		} else {//如果不合法, 拋出異常
			throw new RuntimeException("查詢的uri不合法");
		}
	}

	/**
	 * content://com.atguigu.l09_provider.personprovider/person 插入
	 * content://com.atguigu.l09_provider.personprovider/person/3 根據id插入(沒有)
	 */
	@Override
	public Uri insert(Uri uri, ContentValues values) {
		Log.e("TAG", "PersonProvider insert()");
		//得到連接對象
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		//匹配uri, 返回code
		int code = matcher.match(uri);
		//如果合法, 進行插入
		if(code==1) {
			long id = database.insert("person", null, values);
			//將id添加到uri中
			uri = ContentUris.withAppendedId(uri, id);
			database.close();
			return uri;
		} else {
			//如果不合法, 拋出異常
			database.close();
			throw new RuntimeException("插入的uri不合法");
		}
	}

	/**
	 * content://com.atguigu.l09_provider.personprovider/person 不根據id刪除
	 * content://com.atguigu.l09_provider.personprovider/person/3 根據id刪除
	 */
	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		Log.e("TAG", "PersonProvider delete()");
		//得到連接對象
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		//匹配uri, 返回code
		int code = matcher.match(uri);
		int deleteCount = -1;
		//如果合法, 進行刪除
		if(code==1) {
			deleteCount = database.delete("person", selection, selectionArgs);
		} else if(code==2) {
			long id = ContentUris.parseId(uri);
			deleteCount = database.delete("person", "_id="+id, null);
		} else {
			//如果不合法, 拋出異常
			database.close();
			throw new RuntimeException("刪除的uri不合法");
		}
		
		database.close();
		return deleteCount;
	}

	/**
	 * content://com.atguigu.l09_provider.personprovider/person 不根據id更新
	 * content://com.atguigu.l09_provider.personprovider/person/3 根據id更新
	 */
	@Override
	public int update(Uri uri, ContentValues values, String selection,
			String[] selectionArgs) {
		Log.e("TAG", "PersonProvider update()");
		//得到連接對象
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		//匹配uri, 返回code
		int code = matcher.match(uri);
		int updateCount = -1;
		//如果合法, 進行更新
		if(code==1) {
			updateCount = database.update("person", values, selection, selectionArgs);
		} else if(code==2) {
			long id = ContentUris.parseId(uri);
			updateCount = database.update("person", values, "_id="+id, null);
		} else {
			//如果不合法, 拋出異常
			database.close();
			throw new RuntimeException("更新的uri不合法");
		}
		
		database.close();
		return updateCount;
	}
	
	@Override
	public String getType(Uri uri) {
		// TODO Auto-generated method stub
		return null;
	}
}

並將其註冊到manifest中

<!-- exported : 是否可以讓其它應用訪問 -->
<provider android:name="com.atguigu.l09_provider.PersonProvider" 
          android:authorities="com.atguigu.l09_provider.personprovider"
          android:exported="true"/>
        

2.3 其他需要使用數據的應用的Acitivity代碼

package com.atguigu.l09_resolver;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}
	
	/*
	 * 通過ContentResolver調用ContentProvider插入一條記錄
	 */
	public void insert(View v) {
		//1. 得到ContentResolver對象
		ContentResolver resolver = getContentResolver();
		//2. 調用其insert
		Uri uri = Uri.parse("content://com.atguigu.l09_provider.personprovider/person");
		//uri = Uri.parse("content://com.atguigu.l09_provider.personprovider/person/3");
		ContentValues values = new ContentValues();
		values.put("name", "JACK");
		uri = resolver.insert(uri, values);
		
		Toast.makeText(this, uri.toString(), 1).show();
	}

	/*
	 * 通過ContentResolver調用ContentProvider更新一條記錄
	 */
	public void update(View v) {
		//1. 得到ContentResolver對象
		ContentResolver resolver = getContentResolver();
		//2. 執行update
		Uri uri = Uri.parse("content://com.atguigu.l09_provider.personprovider/person/2");
		ContentValues values = new ContentValues();
		values.put("name", "JACK2");
		int updateCount = resolver.update(uri, values, null, null);
		
		Toast.makeText(this, "updateCount="+updateCount, 1).show();
	}

	/*
	 * 通過ContentResolver調用ContentProvider刪除一條記錄
	 */
	public void delete(View v) {
		//1. 得到ContentResolver對象
		ContentResolver resolver = getContentResolver();
		//2. 執行delete
		Uri uri = Uri.parse("content://com.atguigu.l09_provider.personprovider/person/2");
		int deleteCount = resolver.delete(uri, null, null);
		Toast.makeText(this, "deleteCount="+deleteCount, 1).show();
	}

	/*
	 * 通過ContentResolver調用ContentProvider查詢所有記錄
	 */
	public void query(View v) {
		//1. 得到ContentResolver對象
		ContentResolver resolver = getContentResolver();
		//2. 調用其query, 得到cursor
		Uri uri = Uri.parse("content://com.atguigu.l09_provider.personprovider/person/1");
		uri = Uri.parse("content://com.atguigu.l09_provider.personprovider/person");
		Cursor cusor = resolver.query(uri, null, null, null, null);
		//3. 取出cursor中的數據, 並顯示
		while(cusor.moveToNext()) {
			int id = cusor.getInt(0);
			String name = cusor.getString(1);
			Toast.makeText(this, id+" : "+name, 1).show();
		}
		cusor.close();
	}
}

3 應用案例

此案例展示瞭如何讀取聯繫人

3.1 MainActivity及其佈局文件代碼

package com.example.providerpractice;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
    private EditText et_main_number;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et_main_number = (EditText) findViewById(R.id.et_main_number);
    }

    public void toContactList(View v) {
        //啓動聯繫人列表界面
        startActivityForResult(new Intent(this, ContactListActivity.class), 1);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode==1 && resultCode==RESULT_OK) {
            //得到返回的number
            String number = data.getStringExtra("NUMBER");
            //顯示
            et_main_number.setText(number);
        }
    }
}
<RelativeLayout 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">

    <EditText
        android:id="@+id/et_main_number"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="請輸入或選擇一個聯繫人號碼" />

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/et_main_number"
        android:text="選擇聯繫人"
        android:onClick="toContactList"/>

</RelativeLayout>

3.2 ContactListActivity及其佈局文件代碼

package com.example.providerpractice;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.Manifest;
import android.app.ListActivity;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class ContactListActivity extends ListActivity implements OnItemClickListener {
    private static final String TAG = "ContactListActivity";
    private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 5;

    private ListView listView;
    private ContactAdapter adapter;
    private List<Map<String, String>> data = new ArrayList<Map<String, String>>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact_list);

        listView = getListView();
        adapter = new ContactAdapter();
        applyPermission();
    }

    /**
     * 在android6.0及以上,必須動態申請危險權限
     */
    private void applyPermission() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {
            Log.i(TAG, "checkSelfPermission");
			/*ActivityCompat.shouldShowRequestPermissionRationale
            如果應用之前請求過此權限但用戶拒絕了請求,此方法將返回 true。如果用戶在過去拒絕了權限請求,
            並在權限請求系統對話框中選擇了 Don't ask again 選項,此方法將返回 false。
            如果設備規範禁止應用具有該權限,此方法也會返回 false。
            所以如果返回true,最好在申請權限的時候給一個說明,讓用戶放心授權
            */
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.READ_CONTACTS)) {
                Log.i(TAG, "shouldShowRequestPermissionRationale");
                // Show an expanation to the user *asynchronously* -- don't block
                // this thread waiting for the user's response! After the user
                // sees the explanation, try again to request the permission.

                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_CONTACTS},
                        MY_PERMISSIONS_REQUEST_READ_CONTACTS);

            } else {
                Log.i(TAG, "requestPermissions");
                // No explanation needed, we can request the permission.
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_CONTACTS},
                        MY_PERMISSIONS_REQUEST_READ_CONTACTS);
                // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
                // app-defined int constant. The callback method gets the
                // result of the request.
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.i(TAG, "onRequestPermissionsResult granted");
                    // permission was granted, yay! Do the
                    // contacts-related task you need to do.
                    //查詢得到聯繫人表數據
                    //ContentResolver
                    ContentResolver resolver = getContentResolver();
                    //執行查詢得到cursor
                    String[] projection = {Phone.DISPLAY_NAME, Phone.NUMBER};
                    Cursor cursor = resolver.query(Phone.CONTENT_URI, projection, null, null, null);
                    //取出其中的數據保存到data
                    while (cursor.moveToNext()) {
                        String name = cursor.getString(0);
                        String number = cursor.getString(1);
                        Map<String, String> map = new HashMap<String, String>();
                        map.put("name", name);
                        map.put("number", number);
                        data.add(map);
                    }
                    //顯示列表
                    listView.setAdapter(adapter);

                    //給listView添加item點擊監聽
                    listView.setOnItemClickListener(this);
                } else {
                    Log.i(TAG, "onRequestPermissionsResult denied");
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.

                }
                return;
            }

            // other 'case' lines to check for other
            // permissions this app might request
        }
    }

    class ContactAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public Object getItem(int position) {
            return data.get(position);
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = View.inflate(ContactListActivity.this, R.layout.item_contact, null);
            }

            Map<String, String> map = data.get(position);
            TextView nameTV = (TextView) convertView.findViewById(R.id.tv_item_name);
            TextView nubmerTV = (TextView) convertView.findViewById(R.id.tv_item_number);
            nameTV.setText(map.get("name"));
            nubmerTV.setText(map.get("number"));

            return convertView;
        }

    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position,
                            long id) {
        //得到選擇的號碼
        String number = data.get(position).get("number");
        Intent intent = getIntent();
        intent.putExtra("NUMBER", number);
        //設置結果
        setResult(RESULT_OK, intent);
        //返回
        finish();
    }
}
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

</ListView>

注意,讀取聯繫人需要申請對應的權限,並且在android6.0及以上需要動態申請權限

<uses-permission android:name="android.permission.READ_CONTACTS"/>

4 ContentProvider的一些進階操作

4.1 ContentProvider的批量操作

有時候你需要操作多行數據,可以選擇調用多次ContentResolver的對應函數,或者 使用批量操作,當然使用批量操作後者性能會比較好些。爲了使批量更新、插入、刪除數據更加方便,android系統引入了 ContentProviderOperation類。

在官方開發文檔中推薦使用ContentProviderOperations,有一下原因:

  • 所有的操作都在一個事務中執行,這樣可以保證數據完整性
  • 由於批量操作在一個事務中執行,只需要打開和關閉一個事務,比多次打開關閉多個事務性能要好些
  • 使用批量操作和多次單個操作相比,減少了應用和ContentProvider之間的上下文切換,這樣也會提升應用的性能,並且減少佔用CPU的時間,當然也會減少電量的消耗。

要創建ContentProviderOperation對象,則需要使用 ContentProviderOperation.Builder類,通過調用ContentProviderOperation的下面幾個靜態方法來獲取一個Builder 對象:

方法 說明
newInsert () 創建一個用於執行插入操作的Builder
newUpdate () 創建一個用於執行更新操作的Builder
newDelete() 創建一個用於執行刪除操作的Builder
newAssertQuery() 該方法並不是用來查詢數據的,可以理解爲斷點查詢,也就是查詢有沒有符合條件的數據,如果沒有,會拋出一個OperationApplicationException異常。

一段簡單的代碼示例:

Uri uri = Uri.parse("content://com.atguigu.l09_provider.personprovider/person");
        ContentResolver resolver = getContentResolver();
        ArrayList<ContentProviderOperation> list = new ArrayList<>();
        for (int i = 0; i < 5; i ++) {
            ContentValues values = new ContentValues();
            values.put("name", "JACK" + i);
            ContentProviderOperation operation = ContentProviderOperation.newInsert(uri)
                    .withValues(values)
                    .build();
            list.add(operation);
        }
        try {
            resolver.applyBatch("com.atguigu.l09_provider.personprovider",list);
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }

Builder對象核心方法:

  • withSelection (String selection, String[] selectionArgs) 
    指定需要操作的數據條件。只有在更新、刪除操作中有用。

  • withValue (String key, Object value) 
    定義一列的數據值。只在更新、插入數據中有用。

  • withValues (ContentValues values) 
    定義多列的數據值。 只在更新、插入數據中有用

  • withYieldAllowed(boolean) 
    批量操作一大堆數據可能會長期鎖定數據庫,從而阻止其他應用訪問該數據庫並且有可能會引起ANR(應用無響應)對話框出現。 
    爲了避免長期鎖定數據庫,只要在批量操作中添加“yield points”即可。一個yield points告訴Content Provider,在執行下一個操作之前可以先提交當前的數據,然後通知其他應用,如果有其他應用請求數據的話,就先讓其他應用操作,等其他應用操作完成後,再繼續打開一個事務來執行下一個操作。如果沒有其他程序請求數據,則一個yield points不會自動提交事務,而是繼續執行下一個批量操作。通常情況下一個同步Adapter應該在開始操作一行原數據之前添加一個yield points

  • withValueBackReference(String key, int previousResult) 
    在Android中 創建一個聯繫人,需要先創建一個Raw Contact,然後再創建其他附件的數據(電話號碼、email、地址等)。而後面的操作需要Raw Contact的ID值作爲外鍵。 由於用到了 ContentProviderOperation,第一步Raw Contact的id還沒有生成呢。 這個時候就可以使用withValueBackReference 函數來實現該功能了。在withValueBackReference 函數中第一個參數爲 本次操作數據字段的名稱 ;第二個參數爲 需要引用前面某一次操作的序號。參考https://stackoverflow.com/questions/4655291/what-are-the-semantics-of-withvaluebackreference可能會更加清晰

  • withExpectedCount()(int count) 

      如果設置了這個方法,那麼如果受此操作影響的行數與此count不匹配,OperationApplicationException將拋出。

4.2 通過intent來訪問數據

即使沒有適當的訪問權限,也可以通過向具有權限的應用程序發送Intent並接收包含“URI”權限的結果intent來訪問內容提供程序中的數據。 這些是特定內容URI的權限,這些內容URI將持續到接收它們的Activity爲止。 具有永久權限的應用程序通過在結果意圖中設置標誌來授予臨時權限。

對於與URIZ中的authority匹配的ContentProvider來說,這個flag並不會授予未申請權限的應用完整的讀寫這個ContentProvider的權限,這個訪問僅限於這個URI自身。

一個典型的例子是電子郵件應用程序中的附件。 應該通過權限來保護對電子郵件的訪問,因爲這是敏感的用戶數據。 但是,如果向圖像查看程序提供圖像附件的URI,則該圖像查看器不具有打開附件的權限,因爲它沒有理由擁有訪問所有電子郵件的權限。此問題的解決方案是per-URI權限:啓動Activity或將結果返回給Activity時,調用者可以設置Intent.FLAG_GRANT_READ_URI_PERMISSION或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。 這授予接收Actiity針對特定數據URI的訪問權限,而不管這個應用是否具有訪問對應於URI的內容提供者中的數據的任何權限。

ContentProvider使用清單文件中<provider>元素的android:grantUriPermission屬性以及<provider>元素的<grant-uri-permission>子元素,爲其清單中的內容URI定義URI權限。

例如,即使沒有READ_CONTACTS權限,也可以在Contacts Provider中檢索聯繫人的數據。您可能希望在應用程序中執行此操作,該應用程序在其生日時向聯繫人發送電子賀卡。您不希望請求READ_CONTACTS(允許您訪問所有用戶的聯繫人及其所有信息),而是讓用戶去聯繫人應用中選擇要使用的聯繫人。爲此,請使用以下過程:

  • 用startActivityForResult()方法發送包含操作ACTION_PICK和“contacts”MIME類型CONTENT_ITEM_TYPE的intent。
  • 因爲此Intent與聯繫人應用程序的“選擇”Activity的intent filter過濾器匹配,所以對應的Activity將出現在前臺。
  • 在選擇Activity中,用戶選擇要更新的聯繫人。發生這種情況時,選擇Activity會調用setResult(resultcode,intent)來設置回覆應用程序的intent。 intent包含用戶選擇的聯繫人的內容URI,以及“extras”標誌FLAG_GRANT_READ_URI_PERMISSION。這些標誌爲您的應用授予URI權限,以讀取內容URI指向的聯繫人的數據。然後,選擇活動調用finish()將控制權返回給您的應用程序。
  • 您的Activity返回到前臺,系統會調用您的Activity中的onActivityResult()方法。此方法接收聯繫人應用程序中的選擇Activity創建的結果intent。
  • 使用結果intent中的內容URI,您可以從聯繫人應用程序中的ContactContentProvider中讀取聯繫人的數據,即使您沒有向ContactContentProvider請求永久讀取訪問權限。然後,您可以獲取聯繫人的生日信息或他們的電子郵件地址,然後發送電子賀卡。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章