7.1 内容提供器简介
7.2 运行时权限
7.2.1 Android 权限机制详解
7.2.2 在程序运行时中申请权限
7.3 访问其他程序中的数据
7.3.1 ContentResolver的基本用法
7.3.2 读取系统联系人
7.4 创建自己的内容提供器
7.4.1 创建内容提供器的步骤
7.4.2 实现跨程序数据共享
7.5 Git 时间——版本控制工具进阶
7.5.1 忽略文件
7.5.2 查看修改内容
7.5.3 撤销未提交的修改
7.5.4 查看提交记录
7.6 小结与点评
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问到另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使用内容提供器是 Android 实现跨程序共享数据的标准方式。
不同于文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄露的风险。
运行时权限并不是什么新鲜事物,从系统的第一个版本开始就已经存在了。但其实之前Android的权限机制在保护用户安全和隐私等方面起到的作用比较有限,尤其是一些大家都离不开的常用软件,非常容易“店大欺客”。为此,Android开发团队在Android6.0 系统中引用了运行时权限这个功能,从而更好地保护了用户的安全和隐私。
Android 把所有的权限归成两类:普通权限和危险权限。
普通权限
不会直接威胁到用户的安全和隐私的权限,对于这部分的权限申请,系统会自动帮我们进行授权。
危险权限
可能触及用户隐私,或对设备安全性造成影响的权限,如获取联系人信息、地理位置等,对于这部分的权限申请,必须由用户手动点击授权才可以,否则程序无法使用相应的功能。
首先我们需要在AndroidManifest.xml中添加 如下 用于拨打电话
然后新建Activity 在onCreate中 直接申请权限
之后在回调中进行处理
package com.dak.administrator.firstcode.permission;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.ViewConfiguration;
import android.widget.Toast;
import com.dak.administrator.firstcode.R;
public class PermissionActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);
} else {
call();
}
}
@SuppressLint("MissingPermission")
private void call() {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10085"));
startActivity(intent);
}
@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, "YYou denied the permiss", Toast.LENGTH_SHORT).show();
}
break;
}
}
private void setObject(Object object) {
// (() object) object.cast
// 用户滑动的最小距离
// ViewConfiguration.get(this).getScaledTouchSlop();
}
}
内容提供器的用法有两种:
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver方法获取到该类的实例,从而对数据记性CRUD操作。
要点:
内容 URI 给内容提供器中的数据建立了唯一标识符,它由 authority(区分不同的应用程序)和 path(区分不同的表)组成,具体的可以到百度了解一下。
content://com.example.app.provider/table1
对于CRUD操作:
查询:
Curson curson = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);
添加操作:
// 将待添加的数据组装到 ContentValues 中
ContentValues values = new ContentValues();
values.put("column1","text");
values.put("column2",1);
// 调用 insert() 方法添加数据
getContentResolver().insert(uri, values);
更新操作:
ContentValues values = new ContentValues();
// 清空
values.put("column1","");
// 调用 update() 方法更新数据
getContentResolver().update(uri, values, "column1 = ? and column2 = ?",new String[]{"text","1"});
删除操作:
getContentResolver().delete(uri, "column2 = ?", new String[]{"1"});
话不多说,直接上代码:
package com.dak.administrator.firstcode.content_resolver;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.dak.administrator.firstcode.R;
import java.util.ArrayList;
import java.util.List;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class ResolverActivity extends AppCompatActivity {
ArrayAdapter adapter;
List contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_resolver);
ListView contactsView = findViewById(R.id.contacts_view);
adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList);
contactsView.setAdapter(adapter);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
}else{
readContacts();
}
}
private void readContacts() {
Cursor cursor = null;
try {
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
adapter.notifyDataSetChanged();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (cursor != null) {
cursor.close();
}
}
}
@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();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
Xml文件 显示我们查询到的内容
相关方法已经注释
package com.dak.administrator.firstcode.content_resolver;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.BufferedReader;
/**
* Created by Administrator on 2018/10/18.
*/
public class MyProvider extends ContentProvider {
public static final int TABLE1_DIR = 0;
public static final int TABLE1_TIME = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_TIME = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.dak.administrator.firstcode","table1",TABLE1_DIR);
uriMatcher.addURI("com.dak.administrator.firstcode","table1/#",TABLE1_TIME);
uriMatcher.addURI("com.dak.administrator.firstcode","table2",TABLE2_DIR);
uriMatcher.addURI("com.dak.administrator.firstcode","table2/#",TABLE2_TIME);
}
// 初始化内容提供器的时候调用,返回true表示成功,false失败
@Override
public boolean onCreate() {
return false;
}
// 从内容提供器中查询数据
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
//查询table1表中的所有数据
break;
case TABLE1_TIME:
//查询table1表中的单条数据
break;
case TABLE2_DIR:
//查询table2表中的所有数据
break;
case TABLE2_TIME:
//查询table2表中的单条数据
break;
}
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..
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.dak.administrator.firstcode.table1";
case TABLE1_TIME:
return "vnd.android.cursor.item/vnd.com.dak.administrator.firstcode.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.dak.administrator.firstcode.table2";
case TABLE2_TIME:
return "vnd.android.cursor.item/vnd.com.dak.administrator.firstcode.table2";
}
return null;
}
// 向内容提供器中添加一条数据
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
// 从内容提供器中删除数据
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
// 更新内容提供器中已有的数据
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
在这里主要是通过SQLite 的增删改查方式,来操作。
提供一个主要Resolver,关乎数据库方面的这块就不写了。
public class DataBaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0; //访问 book 表中的所有数据
public static final int BOOK_ITEM = 1;//访问 book 表中的单条数据
public static final int CATEGORY_DIR = 3;
public static final int CATEGORY_ITEM = 4;
public static final String AUTHORITY = "com.wonderful.myfirstcode.chapter7.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static {
// 创建 UriMatcher 实例
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 调用 addURI() 方法,此方法接收3个参数:authority、path、自定义代码
uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);
}
/**
* 初始化内容提供器
*/
@Override
public boolean onCreate() {
// 创建 MyDatabaseHelper 实例
dbHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,2);
// 返回true表示完成了创建或升级数据库
return true;
}
/**
* 查询数据
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
// 查询 book 表中的所有数据
cursor = db.query("book",projection,selection,selectionArgs,
null,null,sortOrder);
break;
case BOOK_ITEM:
// 查询 book 表中的单条数据
String bookId = uri.getPathSegments().get(1);
cursor = db.query("book",projection,"id = ?",new String[]{bookId},
null,null,sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("category",projection,selection,selectionArgs,
null,null,sortOrder);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("category",projection,"id = ?",new String[]
{categoryId}, null,null,sortOrder);
break;
default:
break;
}
return cursor;
}
/**
* 添加数据
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("book",null,values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book" + newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("category",null,values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category" +
newCategoryId);
break;
default:
break;
}
return uriReturn;
}
/**
* 更新数据
*/
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
updatedRows = db.update("book",values,selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("book",values,"id = ?",new String[]{bookId});
break;
case CATEGORY_DIR:
updatedRows = db.update("category",values,selection,selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("category",values,"id = ?",new String[]
{categoryId});
break;
default:
break;
}
return updatedRows;
}
/**
* 删除数据
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
deletedRows = db.delete("book",selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deletedRows = db.delete("book","id = ?",new String[]{bookId});
break;
case CATEGORY_DIR:
deletedRows = db.delete("category",selection,selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deletedRows = db.delete("category","id = ?",new String[]
{categoryId});
break;
default:
break;
}
return deletedRows;
}
/**
* 获取 Uri 对象所对应的 MIME 类型
*/
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.wonderful.myfirstcode." +
"chapter7.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.wonderful.myfirstcode." +
"chapter7.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.wonderful.myfirstcode." +
"chapter7.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.wonderful.myfirstcode." +
"chapter7.provider.category";
}
return null;
}
}
关于Git 相关的内容请移驾:
https://blog.csdn.net/lhk147852369/article/details/84307580
郭霖总结:
本章的内容不算多,而且很多时候都是在使用上一章中学习的数据库只是,所以理解这部分内容对你来说是比较轻松地吧。在本章中,我们一开始先了解了Android的权限机制,并且学会了如何在6.0以上的系统中使用运行时权限,然然后又重点学习了内容提供器的相关内容,以实现跨进程数据共享的功能。现在你不仅知道了如何去访问其他程序中的数据,还学会了怎样创建自己的内容提供器来共享数据,收获还是挺大的吧。
不过每次在创建内容提供器的时候,你都需要提醒下自己, 我是不是应该这么做?因为只有真正需要将数据共享出去的时候我们才应该创建内容提供器,仅仅是用于程序内部访问的数据就没有必要这么做,所以千万别对它进行滥用。
在连续学了几章系统机制方面的内容之后是不是感觉有些枯燥?那么下一- 章中我们就来换换口味,学习一下Android多媒体方面的知识吧。
我的总结:
之前在进行工作的过程中,并没有用到这方面的知识,现在学习了一番,收获还是非常大的。