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请求永久读取访问权限。然后,您可以获取联系人的生日信息或他们的电子邮件地址,然后发送电子贺卡。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章