ContentProvider 内容提供器
内容提供器(ContentProvider)主要用于在不同的应用程序之间实现数据共享的功能。他提供了一套完整的机制, 允许一个程序访问另一个程序中的数据, 同事还能保证被访问的数据的安全性。
Android运行时权限,
如果要访问系统的网络状态以及监听开机广播等, 需要在AndroidManifest文件中进行权限声明,否则程序将会崩溃,这些属于静态注册权限。 在Android6.0系统中加入了运行时权限功能。就是说,用户不需要在安装软件的时候一次性授权所有的申请权限, 而是可以再软件的使用过程中在对某一项权限进行授权,
当然,并不是所有权限都需要在运行时申请, 对于用户来说不停的授权也很范睢, Android现在将多有的权限归成了三类, 分别是普通权限,危险权限和特殊权限,其中普通权限是指那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,危险权限则表示可能会触及用户隐私的权限, 对于这部分权限申请,必须由用户手动点击授权才可以,否则程序就会无法使用相应的功能,
危险权限:
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
在 https://developer.android.google.cn/reference/android/Manifest.permission 中可以查看Android系统中完整的权限列表。
在程序运行时申请权限。
public class MainActivity extends AppCompatActivity { Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // android 6.0 以上系统检查是否经过用户授权如果没授权进行授权提醒。 if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1); } else { // 这里是经过用户授权后才会执行的代码 call(); } } }); } private void call(){ try{ Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); //如果没有授权会异常 }catch (SecurityException e){ e.printStackTrace(); } } //不管授权成功与否最后都会进入授权的结果则会封装在grantResults中 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch(requestCode){ case 1: if(grantResults.length> 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ call(); }else{ Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show(); } break; default: } } }
需要在Manifest.xml进行权限声明:比如此次要使用的是拨打电话的权限:
<uses-permission android:name="android.permission.CALL_PHONE" />
程序的第一步就是先要判断用户是不是已经给我们授权了,借助的是
ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.CALL_PHONE)
第一个参数是一个Context第二个参数是具体权限名 然后我们使用方法的返回值和PackageManager.PERMISSION_GRANTED
比较, 相等表示已经授权,不等表示用户没有授权,
如果已经授权的化就直接执行拨打电话的逻辑操作就可以了。
如果没有授权就需要调用ActivityCompat.requestPermissions()
方法来向用户申请授权,他接受三个参数,第一个参数要求的是Activity的实例,第二个参数是一个String数组,我们把要申请的权限名放在数组就可以了,第三个参数是请求码。只要是唯一值就可以了。
在调用完requestPermissions()方法后系统会弹出一个权限申请的对话框,然后用户可以全职同意或者拒绝我们的申请,不论是那种,最终都会返回到onRequestPermissionsResult()方法中,而授权的结果则会封装在grantResults参数中,这里我们需要判断一个最后的授权结果,如果用户同意就调用call()来拨打电话,如果用户不同意就只能放弃操作。
访问其他程序中的数据
内容提供器的用法一般由两种,一种是使用现有的内容提供器啦读取和操作相应程序中的数据,另一种是创建自己的内容提供器供给我们程序的数据提供外部访问接口,
ContentResolver的基本用法
对于每一个应用程序来说,如果想要访问内容提供器中的共享数据。就一定要借助ContentResolver类 可以通过Context中的getContentResolver()方法获取到该类的实例,ContentResolver中提供了一系列的方法用于对数据进行CRUD.
不同于SQLiteDatabase, ContentResolver中的CRUD方法都是不接受表名参数的,而是使用了一个Uri参数代替, 这个参数被成为内容URI 内容URI给内容提供器中的数据建立了一个唯一标识符, 由两部分组成 authority和path authority用于对不同的应用程序作区分,path对表进行区分
格式如下
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在得到了内容URI字符穿之后我们还需要将这个字符串解析成URI对象才可以作为参数传入,只需要调用Uri.parse()方法就可以将内容URI字符串解析成uri对象了
Uri uri = Uri.parse("content://com.example.app.provider/table1");
这样就可以使用这个Uri对象来查询table1表中的数据了。
Cursor cur = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
query()参数 对应SQL部分 描述
uri from table_name 指定查询某个应用程序下的某一张表
projection select column1,column2 指定查询的列名
selection where column=value 指定where的约束条件
selectionArgs - 为where中的占位符提供具体的值
sortOrder order by column1,column2 指定查询结果的排序方式
查询完成后返回的是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来了。
查询数据:
if(cursor != null){ while(cursor.moveToNext()){ String column1 = cursor.getString(cursor.getColumnIndex("column1")) int column2 = cursor.getInt(cursor.getColumnIndex("column2")) } cursor.close(); }
添加数据
ContentValues values = new ContentValues(); values.put("column1","text"); values.put("column2",1); getContentResolver().insert(uri,values);
更新数据
ContentValues values = new COntentValues(); values.put("column1",""); getContentResolver().update(uri,values,"column1=? and column2 = ?", new String[]{"text","1"})
删除数据
getContentResolver.delete(uri,"column2 = ?", new String{"1"});
读取系统联系人
- 添加几个联系人到通讯录中。
在build.gradle中添加RecyclerView 然后编辑布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/rv_listview" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
创建一个Contact类
public class Contact { private String name; private String number; public Contact(String name, String number) { this.name = name; this.number = number; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } }
创建一个contact_item布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="match_parent" android:orientation="horizontal"> <TextView android:id="@+id/tv_contact_number" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_contact_name" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
创建一个ContactAdapter
public class ContactAdapter extends RecyclerView.Adapter
{ private List<Contact> mContactList; static class ViewHolder extends RecyclerView.ViewHolder{ TextView tv_contact_name; TextView tv_contact_number; public ViewHolder(View itemView) { super(itemView); tv_contact_name = (TextView)itemView.findViewById(R.id.tv_contact_name); tv_contact_number = (TextView)itemView.findViewById(R.id.tv_contact_number); } } public ContactAdapter(List<Contact> contactList){ this.mContactList = contactList; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.contact_item,parent,false); ViewHolder viewHolder = new ViewHolder(view); return viewHolder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { Contact contact = mContactList.get(position); holder.tv_contact_name.setText(contact.getName()); holder.tv_contact_number.setText(contact.getNumber()); } @Override public int getItemCount() { return mContactList.size(); }
}
修改MainActivity中的内容
public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private List<Contact> mContactList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView= (RecyclerView) findViewById(R.id.rv_listview); LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); ContactAdapter adapter = new ContactAdapter(mContactList); recyclerView.setAdapter(adapter); if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS},1); }else{ readContacts(this); } } private void readContacts(Context context){ Cursor cursor = null; cursor = context.getContentResolver(). query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null,null,null); if(cursor != null){ while(cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); mContactList.add(new Contact(name,number)); } } } //不管授权成功与否最后都会进入 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch(requestCode){ case 1: if(grantResults.length> 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ readContacts(this); }else{ Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show(); } break; default: } } }
进入AndroidManifest.xml中声明权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
创建内容提供器的步骤
如果想要实现跨程序共享数据的功能,官方的推荐的方式就是使用内容提供器,可以通过新建一个类去继承ContentProvider的方式来创建一个自己的内容提供器并重写他的6个抽象方法
- onCreate() 初始化提供器的时候调用,通常会在这里完成对数据库的创建和升级操作,返回true表示内容提供器初始化成功否者表示失败。
- query()从内容提供器中查询数据。查询结果存放在 Cursor对象中返回
- insert() 向内容提供其中添加一条数据,添加完成后返回一个用于表示这条新纪录的uri
- update()更新内容提供器中已有的数据,返回受影响的行数
- delete()从内容提供器中删除已有的数据,被删除行将作为返回值返回
- getType()根须传入的内容URI返回相应的MIME类型。
标准URI的写法是这样的
content://com.example.app.provider/table1
这表示调用方期望访问的是com.example.app.provider这个应用的table1表中的数据。除此之外,我们还可以在这个内容URI的后面加上一个id
content://com.example.app.provider/table1/1
这就表示调用方期望访问的是com.example.app.provider这个应用的 table1表中id为1的数据。
也可以使用通配符来分别匹配这两种格式的内容URI
*
表示匹配人一长度的任意字符。
#
表示匹配人一长度的数字。
一个能够匹配人意表的内容URI格式
content://com.example.app.provider/*
而一个能匹配tbale1表中任意一行数据的内容就可以写成
content://com.example.app.provider/table1/#
接着借助UriMatcher这个类就可以实现匹配内容URI的功能。UriMatcher中提供了一个addURI()方法 这个方法需要传入authority、path和一个自定义的代码。这样当调用UriMatcher的mathc()方法时,就可以将一个uri对象传入,返回值是某个能够匹配这个uri对象所对应的自定义代码,利用这个代码就可以判断出调用方期望王文的是那张表的数据了。
getTye()方法是所有的内容提供器都必须提供的一个方法, 用于获取URI对象所对应的MIME类型, 一个内容URI所对应的MIME字符串由3部分组成,
- 必须以vnd开头。
- 如果内容URI以路径结尾,则后接
android.cursor.dir/
如果以id结尾则后接android.cursor.item/
最后接上
vnd.<authority>.<path>
例如:
对于
content://com.example.app.provider/table1
这个uri所对应的MIME类型为:vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于
content://com.example.app.provider/table1/1
这个uri所对应的MIME类型为:vnd.android.cursor.item/vnd.com.example.app.provider.table1