内容提供器主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序的数据,同时还能保证被访问数据的安全性。
不同于文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄露的风险。
Android现在将所有的权限归成了两类,一类时普通权限,一类是危险权限。
Android中所有的危险权限
如果是属于危险权限的范畴,那么就需要进行运行时权限处理,如果是普通权限的范畴,那么只需要在AndroidManifest.xml文件中添加一下权限声明即可。
倘若我们要使用的功能属于危险权限范畴时,我们就需要在程序运行时申请权限。
使用步骤:
1.先在AndroidManifest.xml文件中添加权限声明。
2.调用ContextCompat.checkSelfPermission(Context,权限名)方法,判断用户是否给予了该权限。
3.若未拥有该权限则调用 ActivityCompat.requestPermissions(Context,权限名称的字符串数组,请求码)方法,调用完后系统会弹出一个权限申请的对话框,然后用户可以选择同意或拒绝我们的权限申请,无论是那种结果,最终都会回调到onRequestPermissionsResult()方法中。
4.重写onRequestPermissionsResult()方法 ,在其中判断用户的选择,再进行不同的逻辑处理。
示例(拨打电话的权限):
//步骤一
<uses-permission android:name="android.permission.CALL_PHONE" />
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button=(Button) findViewById(R.id.Button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
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();
}
}
});
}
@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){
//用户的选择结果存储在grantResults数组中
call();
}else {
Toast.makeText(MainActivity.this,"rejust",Toast.LENGTH_SHORT).show();
}
default:
}
}
public void call(){
Intent intent=new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
}
内容提供器的用法一般有两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。
访问其他程序中的数据说穿了任然是对数据库的操作,只不过我们之前都是操作自己App的数据库,而这次我们就是要来操作别人App的数据库了。
使用步骤:
1.调用getContentResolver()方法获取ContentResolver实例。
2.对数据库进行CRUD操作。事实上这里的操作与操作自己App的数据库十分相似,不过操作自己App的数据库是用表名来确定要操作的表,而这里是用uri来确定要操作的表。
添加数据:
ContentResolver resolver=getContentResolver();
ContentValues values=new ContentValues();
values.put("column1","text");
values.put("column1",1);
resolver.insert(uri,values);
修改数据:
ContentResolver resolver=getContentResolver();
ContentValues values=new ContentValues();
values.put("column1","");
resolver.update(uri,values,"id = ?",new String[]{
"1"});
删除数据:
ContentResolver resolver=getContentResolver();
resolver.delete(uri,"id = ?",new String[]{
"1"});
查询数据:
ContentResolver resolver=getContentResolver();
Cursor cursor=resolver.query(uri,null,null,null,null);//注意这里总共只有五个参数
if(cursor!=null){
while(cursor.moveToNext()){
String column1=cursor.getString(cursor.getColumnIndex("column1"));
int column2=cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
ContentResolver的query方法详解
需求:读取通话录中的联系人信息,展示在界面上。
代码展示:
//ListView相关代码
//活动布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_View"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
//listView子项布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/name_text"
android:textSize="20sp"
android:text="Tom"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tel_text"
android:textSize="20sp"
android:text="10086"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
//子项布局所需数据的类
public class Contact {
private String name;
private String tel;
public Contact(String name,String tel){
this.name=name;
this.tel=tel;
}
public String getName() {
return name;
}
public String getTel() {
return tel;
}
}
//listView适配器
public class MyAdapter extends ArrayAdapter<Contact> {
private int resourceId;
public MyAdapter(@NonNull Context context, int resource, @NonNull List<Contact> objects) {
super(context, resource, objects);
resourceId=resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View view= LayoutInflater.from(parent.getContext()).inflate(resourceId,parent,false);
Contact contact=getItem(position);
TextView nameText=(TextView) view.findViewById(R.id.name_text);
TextView telText=(TextView) view.findViewById(R.id.tel_text);
nameText.setText(contact.getName());
telText.setText(contact.getTel());
return view;
}
}
//活动中的代码
public class MainActivity extends AppCompatActivity {
private List<Contact> list=new ArrayList<>();
MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView=(ListView) findViewById(R.id.list_View);
adapter=new MyAdapter(MainActivity.this,R.layout.item_layout,list);
listView.setAdapter(adapter);
if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this,new String[]{
Manifest.permission.READ_CONTACTS},1);
}else {
read();
}
}
@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){
read();
}else {
Toast.makeText(MainActivity.this,"rejust",Toast.LENGTH_SHORT).show();
}
default:
}
}
public void read(){
ContentResolver resolver=getContentResolver();
Cursor cursor=resolver.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 tel=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Contact contact=new Contact(name,tel);
list.add(contact);
}
cursor.close();
}
adapter.notifyDataSetChanged();
}
}
在上一节中,我们学习了如何在自己的程序中访问其它应用程序中的数据,而这节我们就要学习如何让其它程序能够访问我们程序中的数据。
可以通过新建一个类去继承ContentProvider的方式来创建一个自己的内容提供器。ContentProvider类中共有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写。
简单示例:
public class MyProvider extends ContentProvider {
private static final int TABLE_DIR=0;
private static final int TABLE_ITEM=1;
private static UriMatcher matcher;
//将其他人的uri语句转化成自己的标识,用于判断他人想要查询的方式
static {
matcher=new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI("com.example.app.provider","table",TABLE_DIR);
matcher.addURI("com.example.qpp.provider","table/#",TABLE_ITEM);
}
// 初始化内容提供器的时候调用,返回true表示成功,false失败
@Override
public boolean onCreate() {
return false;
}
// 从内容提供器中查询数据
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
switch (matcher.match(uri)){
case TABLE_DIR:
//查询table表中的所有数据
break;
case TABLE_ITEM:
//查询table表中的单条数据
break;
default:
}
return null;
}
// 根据传入的内容 URI 来返回 MIME 类型
@Nullable
@Override
public String getType(@NonNull Uri uri) {
//MIME类型 规定:
// 1. 必须以vnd 开头
// 2. 如果内容URI以路径结尾,则后接android.cursor.dir/
// 如果内容URI以id结尾,则后接android.cursor.item/
// 3. 最后接上vnd..
return null;
}
// 向内容提供器中添加一条数据,添加完成后,返回一个用于表示这条新纪录的URI
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
//从内容提供器中删除数据
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
//更新内容提供器中的内容
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}
这个内容提供器类,是先将别人的CRUD操作解析后,再在这个类中替他去完成相应的操作。
在上节中我们只写了一个简单的范例,在这节中我们就来完整的实现跨程序的数据共享。关于创建数据库的操作在这里就不展示了,不清楚的读者可以看一下这篇博客
示例:
public class MyContentProvider extends ContentProvider {
private static final int BOOK_DIR=0;//访问book表中的所有数据
private static final int BOOK_ITEM=1;//访问book表中的某一条数据
private static UriMatcher matcher;
private static MySQLiteOpenHelper helper;
static {
matcher=new UriMatcher(UriMatcher.NO_MATCH);
// 调用 addURI() 方法,此方法接收3个参数:authority、path、自定义代码
matcher.addURI("com.example.temp.provider","book",BOOK_DIR);
matcher.addURI("com.example.temp.provider","book/#",BOOK_ITEM);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase database=helper.getWritableDatabase();
int returnRows=0;
switch (matcher.match(uri)){
case BOOK_DIR:
returnRows=database.delete("Book",selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId=uri.getPathSegments().get(1);//获取Uri中的id项,而get(0)就是路径了
returnRows=database.delete("Book","id = ?",new String[]{
bookId});
break;
default:
break;
}
return returnRows;
}
@Override
public String getType(Uri uri) {
switch (matcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.temp.provider.book";
case BOOK_ITEM:
return "cnd.android.cursor.item/vnd.com.example.temp.provider.book";
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase database=helper.getWritableDatabase();
Uri mUri=null;
switch (matcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId=database.insert("Book",null,values);
mUri=Uri.parse("content://"+"com.example.temp.provider"+"/book/"+newBookId);
break;
default:
break;
}
return mUri;
}
@Override
public boolean onCreate() {
helper=new MySQLiteOpenHelper(getContext(),"BookStore.db",null,1);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase database=helper.getReadableDatabase();
Cursor cursor=null;
switch (matcher.match(uri)){
case BOOK_DIR:
cursor=database.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
break;
case BOOK_ITEM:
String bookId=uri.getPathSegments().get(1);
cursor=database.query("Book",projection,"id = ?",new String[]{
bookId},null,null,sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase database=helper.getWritableDatabase();
int returnRows=0;
switch (matcher.match(uri)){
case BOOK_DIR:
returnRows=database.update("Book",values,selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId=uri.getPathSegments().get(1);
returnRows=database.update("Book",values,"id = ?",new String[]{
bookId});
break;
default:
break;
}
return returnRows;
}
}
另外还有一点需要注意,内容提供器一定要在AndroidManifest.xml文件中注册才可以使用,但是倘若你是用As的快捷方式创建的内容提供器,注册这一步就会被自动完成。
注册代码展示:
<provider
android:name=".MyContentProvider"
android:authorities="com.example.temp.provider"
android:enabled="true"
android:exported="true" />
ContentProvider、ContentResolver、ContentObserver之间的关系
答:
ContentProvider:
四大组件的内容提供者,主要用于对外提供数据
实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者SQLite等
一个应用实现ContentProvider来提供内容给别的应用来操作,通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以
ContentResolver:
内容解析者,用于获取内容提供者提供的数据
ContentResolver.notifyChange(uri)发出消息
ContentObserver:
内容监听器,可以监听数据的改变状态
目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObsever也分为表ContentObserver、行ContentObserver,当然这是与它所监听的Uri MIME Type有关的
ContentResolver.registerContentObserver()监听消息