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 访问权限声明 --><permissionandroid:name="com.android.peter.provider.READ_PERMISSION"android:label="Student provider read permission"android:protectionLevel="normal"/><permissionandroid:name="com.android.peter.provider.WRITE_PERMISSION"android:label="Student provider read permission"android:protectionLevel="normal"/><!-- 声明ContentProvider --><application...<providerandroid: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);}@Overridepublic 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')");}@Overridepublic 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()");}@Overridepublic 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查询 */@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {Log.e("TAG", "PersonProvider query()");//得到连接对象SQLiteDatabase database = dbHelper.getReadableDatabase();//1.匹配uri, 返回codeint 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查询 //得到idlong 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插入(没有)*/@Overridepublic Uri insert(Uri uri, ContentValues values) {Log.e("TAG", "PersonProvider insert()");//得到连接对象SQLiteDatabase database = dbHelper.getReadableDatabase();//匹配uri, 返回codeint 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删除*/@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {Log.e("TAG", "PersonProvider delete()");//得到连接对象SQLiteDatabase database = dbHelper.getReadableDatabase();//匹配uri, 返回codeint 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更新*/@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {Log.e("TAG", "PersonProvider update()");//得到连接对象SQLiteDatabase database = dbHelper.getReadableDatabase();//匹配uri, 返回codeint 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;}@Overridepublic String getType(Uri uri) {// TODO Auto-generated method stubreturn 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 {@Overrideprotected 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. 调用其insertUri 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. 执行updateUri 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. 执行deleteUri 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, 得到cursorUri 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;@Overrideprotected 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);}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if(requestCode==1 && resultCode==RESULT_OK) {//得到返回的numberString 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"><EditTextandroid:id="@+id/et_main_number"android:layout_width="fill_parent"android:layout_height="wrap_content"android:hint="请输入或选择一个联系人号码" /><Buttonandroid: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>>();@Overrideprotected 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.}}}@Overridepublic 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.//查询得到联系人表数据//ContentResolverContentResolver resolver = getContentResolver();//执行查询得到cursorString[] projection = {Phone.DISPLAY_NAME, Phone.NUMBER};Cursor cursor = resolver.query(Phone.CONTENT_URI, projection, null, null, null);//取出其中的数据保存到datawhile (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 {@Overridepublic int getCount() {return data.size();}@Overridepublic Object getItem(int position) {return data.get(position);}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic 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;}}@Overridepublic 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请求永久读取访问权限。然后,您可以获取联系人的生日信息或他们的电子邮件地址,然后发送电子贺卡。

android开发笔记(五)ContentProvider相关推荐

  1. Android开发笔记(五十四)数据共享接口ContentProvider

    ContentProvider 前面几节介绍了进程间通信的几种方式,包括消息包级别的Messenger.接口调用级别的AIDL.启动页面/服务级别的Notification,还有就是本节这个数据库级别 ...

  2. Android开发笔记(一百七十五)利用Room简化数据库操作

    虽然Android提供了数据库帮助器,但是开发者在进行数据库编程时仍有诸多不便,比如每次增加一张新表,开发者都得手工实现以下代码逻辑: 1.重写数据库帮助器的onCreate方法,添加该表的建表语句: ...

  3. Android开发笔记(一百六十五)利用红外发射遥控电器

    红外遥控是一种无线控制技术,它具有功耗小.成本低.易实现等诸多优点,因而被各种电子设备特别是家用电器广泛采用,像日常生活中的电视遥控器.空调遥控器等等基本都采用红外遥控技术. 不过遥控器并不都是红外遥 ...

  4. Android开发笔记(一百五十四)OpenGL的画笔工具GL10

    上一篇文章介绍了OpenGL绘制三维图形的流程,其实没有传说中的那么玄乎,只要放平常心把它当作一个普通控件就好了,接下来继续介绍OpenGL具体的绘图操作,这项工作得靠三维图形的画笔GL10来完成了. ...

  5. Android开发笔记(一百三十五)应用栏布局AppBarLayout

    应用栏布局AppBarLayout Android5.0推出工具栏Toolbar用来替代ActionBar,灵活性和易用性大大增强,有关Toolbar的详细介绍参见< Android开发笔记(一 ...

  6. Android开发笔记(一百二十五)自定义视频播放器

    视频播放方式 在Android中播放视频的方式有两种: 1.使用MediaPlayer结合SurfaceView进行播放.其中通过SurfaceView显示视频的画面,通过MediaPlayer来设置 ...

  7. Android开发笔记(一百零五)社会化分享SDK

    社会化分享 社会化分享,指的是用户通过互联网这个媒介,把文本/图片/多媒体信息分享到该用户的交际圈,从而加快信息传播的行为.对于app来说,网络社区虽多,但用户量足够大的就那几个,所以app的社会化分 ...

  8. Android开发笔记(九十五)自定义Drawable

    Drawable Bitmap是Android对图像的定义描述,而Drawable则是对图像的展现描述,在View视图中显示图像都是通过Drawable来实现的.其中有关Bitmap的介绍参见< ...

  9. Android开发笔记(七十五)内存泄漏的处理

    内存泄漏的原因 一直以来以为只有C/C++才存在内存泄漏的问题,没想到拥有内存回收机制的Java也可能出现内存泄漏.C/C++存在指针的概念,程序中需要使用指针变量时,就从内存中开辟一块区域,并把该区 ...

  10. Android开发笔记(五十七)录像录音与播放

    媒体录制MediaRecorder MediaRecorder是Android自带的录制工具,通过操纵摄像头和麦克风完成媒体录制,既可录制视频,也可单独录制音频.其中对摄像头Camera的介绍参见&l ...

最新文章

  1. 计算机病毒的防治 教案,计算机病毒及防治教案
  2. 浅析cookie以及一些小案例
  3. 小书童——密码(洛谷-P1914)
  4. 使用 ES6 的浏览器兼容性问题
  5. PHP操作图片简单案例
  6. Ubuntu18.04安装python3.7.7和tensorflow-gpu 2.2.0
  7. (十五)洞悉linux下的Netfilteriptables:开发自己的hook函数【实战】
  8. 改变MyEclipse默认编码方式
  9. Ubuntu设置Root用户开机启动
  10. emcc生成wasm,wast,bc文件的方法
  11. JS-Global对象
  12. 编辑器下运行exe或bat run exe or bat in editor
  13. LeetCode/LintCode 题解丨一周爆刷字符串:旋转字符数组
  14. 操作无法完成 计算机名不正确,win7系统连接共享打印机时提示“操作无法完成,键入的打印机名不正确”的解决方法...
  15. linux系统下的编程软件,四款​linux​操作系统总有一款适合你
  16. 同服务器文件同步,同步盘如何实时文件同步?
  17. 企业转型做互联网广告怎么样?
  18. 最新的CSS教程,可以参考下哦!
  19. 牛听听显示服务器正在升级,升级了牛听听读书牛3.0版本,陪着孩子玩的停不下来...
  20. 网络日志采集_企业网络日志对网络安全有哪些帮助作用?

热门文章

  1. 运维工程师必须了解的IPV6技术,一文带你了解!
  2. 互联网行业领域细分 | 你可以准确的说出你混哪个圈的吗?
  3. 辛星跟您玩转vim第一节之vim的下载与三种模式
  4. 导出GMS计算结果,并进行分类汇总
  5. loss问题——工作中对出现的loss问题描述与解决参考
  6. Java面试题之基础篇
  7. 5、python—元组
  8. 用php照片艺术化,美术生都要膜拜的AI,照片迅速被画成艺术画
  9. CNN实现图像风格迁移 ---Image Style Transfer Using Convolutional Neural Networks
  10. 关于Mysql中的不等于