内容提供器主要用于在不同应用程序之间实现数据共享的功能,他可以选择只对那一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
例子:微信读取手机联系人信息
Android开发团队在6.0系统中加入了运行时权限功能,即用户不需要在安装程序时一次性授权所有权限,可以在软件运行过程中再对某一权限进行授权。
Android吧所有的权限大致分为两类,一类是普通权限,一类是危险权限。
普通权限指不会威胁到用户的安全和隐私的权限,对于这部分权限申请,系统自动帮我们进行授权。
危险权限表示那些可能触及用户隐私或者对设备安全造成影响的权限,对于这部分权限,必须用户动手点击才可授权。
Android危险权限:
危险权限即作用
如果要使用以上危险权限,除了需要在AndroidManifest文件中配置,还需要在代码中对这些权限进行动态申请
CALL_PHONE时拨打电话功能时需要的权限,是个危险权限。
拨打电话的逻辑:
private void call(){
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
接下来添加权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.revise_7_1">
<uses-permission android:name="android.permission.CALL_PHONE"/>
...
</manifest>
如果在低于6.0的版本使用此方法编写打电话功能,是可以正常运行的,但在高于6.0的系统运行,会出错。提示我们"Permission Denial"。
对6.0后作出的修改:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//第一步:判断用户是不是给过我们权限
//ContextCompat.checkSelfPermission接收两个参数,第一个是上下文,第二个是权限名
if(ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
//第二步:没有授权就调用ActivityCompat.requestPermissions()方法来向用户申请授权
//接收三个参数,第一个是上下文,第二个是String[]数组,把要申请的权限名放在数组中,第三个是请求码
ActivityCompat.requestPermissions(MainActivity.this,new String[]{
Manifest.permission.CALL_PHONE},1);
}else
call();
}
//第三步:不管我们是否给予权限,都回调到此方法中,授权结果封装在grantResults参数中
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED)
call();
else
finish();
}
}
这样动态添加权限后,程序就可正常运行。
内容URI为内容提供器中的数据建立了唯一的标识符,就是根据他知道要访问那个数据,由两部分组成:
authority:用于对不同的app做区分,一般写法:包名.provider
path:用于对同一应用程序中不同的表做区分
为了看出这是URI还要加上头部协议,标准格式如下
content://com.example.app.provider/table
还有一种常用格式
content://com.example.app.provider/table/ID
这种表示访问com.example.app程序下table表中id为ID(int)的数据
<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
<dbname value="demo"/>
<version value="1"/>
<list>
<mapping class="com.example.revise_sqlitedatabase.Demo1"></mapping>
</list>
</litepal>
package com.example.revise_sqlitedatabase;
import org.litepal.crud.LitePalSupport;
public class Demo1 extends LitePalSupport {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
MyContentProvider 要重写6个方法
注意事项:
1.使用通配符匹配两种格式的内容URI
符号 | 含义 |
---|---|
* | 匹配任意长度的任意字符 |
# | 匹配任意长度的数字 |
例如:匹配表中的一行数据content://com.example.app.provider/table/#
2.UriMatcher类
UriMatcher类可看做匹配URI的工具类,addURI方法传入authority,path,和一个自定义代码当调用UriMatcher的match()方法时,可根据传入的uri返回相应的自定义代码
用自定义代码去判断到底访问的是那个表
3.Uri对象所对应的MIME类型
以vnd开头
如果URI以路径结尾,后接android.cursor.dir/,如果以id结尾,后接android.cursor.item/
最后接上vnd.authority.path
package com.example.revise_sqlitedatabase;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import org.litepal.LitePal;
public class MyContentProvider extends ContentProvider {
public static final int Demo1_DIR = 0; //访问所有数据
public static final int Demo1_ITEM = 1; //访问单条数据
public static final String AUTHORITY= "com.example.revise_sqlitedatabase.provider";
private static UriMatcher uriMatcher;
private Demo1 demo1;
SQLiteDatabase db;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"Demo1",Demo1_DIR);
uriMatcher.addURI(AUTHORITY,"Demo1/#",Demo1_ITEM);// #匹配数字
}
public MyContentProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//删除数据
int deletedRows = 0;
switch (uriMatcher.match(uri)){
case Demo1_DIR:
deletedRows = db.delete("Demo1",selection,selectionArgs); //返回受影响的行数
break;
case Demo1_ITEM:
String Id = uri.getPathSegments().get(1);
//getPathSegments()方法吧URI权限后的部分以“/”分割,0位置是路径,1位置是id
deletedRows = db.delete("Demo1","id = ?",new String[]{
Id});
break;
default:
break;
}
return deletedRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case Demo1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.revise_sqlitedatabase.Demo1";
case Demo1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.revise_sqlitedatabase.Demo1";
default:
break;
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
//添加数据返回的是新添加的数据的Uri
Uri uriReturn = null;
switch (uriMatcher.match(uri)){
case Demo1_DIR:
case Demo1_ITEM:
long newId = db.insert("Demo1",null,values);
uriReturn = Uri.parse("content://"+AUTHORITY+"/Demo1/"+newId);
break;
default:
break;
}
return uriReturn;
}
@Override
public boolean onCreate() {
//获取SQLiteDatabase因为这些方法全是用SQLiteDatabase访问数据库的
db = LitePal.getDatabase();
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//查询数据返回Cursor
Cursor cursor = null;
switch (uriMatcher.match(uri)){
case Demo1_DIR:
cursor = db.query("Demo1",projection,selection,selectionArgs,null,null,sortOrder);
break;
case Demo1_ITEM:
String Id = uri.getPathSegments().get(1);
cursor = db.query("Demo1",projection,"id = ?",new String[]{
Id},null,null,sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
//更新数据
int updatedRows = 0;
switch (uriMatcher.match(uri)){
case Demo1_DIR:
updatedRows = db.update("Demo1",values,selection,selectionArgs); //返回受影响的行数
break;
case Demo1_ITEM:
String Id = uri.getPathSegments().get(1);
updatedRows = db.update("Demo1",values,"id = ?",new String[]{
Id});
break;
default:
break;
}
return updatedRows;
}
}
访问刚才的程序中的数据
Context中的getContentResolver()中提供了一系列方法用于对数据的CRUD
Uri uri = Uri.parse("content://com.example.revise_sqlitedatabase.provider/Demo1");
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.button1 :
//添加
ContentValues values = new ContentValues();
values.put("name","aaa");
values.put("age",17);
values.put("hight",70);
getContentResolver().insert(uri,values);
values.clear();
values.put("name","bbb");
values.put("age",19);
values.put("hight",100);
getContentResolver().insert(uri,values);
values.clear();
values.put("name","ccc");
values.put("age",21);
values.put("hight",180);
getContentResolver().insert(uri,values);
break;
case R.id.button2 :
//更新
ContentValues values1 = new ContentValues();
values1.put("age",9);
getContentResolver().update(uri,values1,"name = ?",new String[]{
"aaa"});
break;
case R.id.button3 :
//删除
getContentResolver().delete(uri,"name = ?",new String[]{
"bbb"});
break;
case R.id.button4 :
//查询
Cursor cursor = null;
try{
cursor = getContentResolver().query(uri,null,null,null,null);
if(cursor != null){
while (cursor.moveToNext()){
Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("name"))
+cursor.getInt(cursor.getColumnIndex("age")));
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();
}
}
break;
case R.id.button5 :
break;
}
}