第七章 内容提供器

7.1 内容提供器(Content Provider)

主要应用于在不同的应用程序之间实现数据共享功能。允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。

7.2 运行时权限

7.2.1 Android权限机制

普通权限:不会直接威胁到用户的安全和隐私的权限,系统会自动进行授权

危险权限:可能会触及用户隐私或者对设备安全性造成影响的权限,对于这部分权限,必须要手动点击授权才可以,否则程序就无法使用相应的功能。

7.2.2 在程序运行时申请权限

新建RuntimePermissionTest项目,使用CALL_PHONE权限,编写拨打电话功能的时候需要声明。

1.Android 6.0之前

a.activity_main.xml中加入Button

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <Button
android:id="@+id/make_call"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="make call"/>
</LinearLayout>

b.MainActivity.java中注册事件

 Button makeCall = (Button)findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}catch(SecurityException e){
e.printStackTrace();
});

构建了一个隐式Intent

c.增加AndroidManifest.xml中的权限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sog.runtimepermissiontest"> <uses-permission android:name="android.permission.CALL_PHONE"/> <application

低于6.0的系统都是可以正常运行的,当在6.0或更高版本系统的手机上运行,打印日志会发现需要进行权限处理。

2. 6.0系统以上的处理

public class MainActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makeCall = (Button)findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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();
}

}
});
}
public void call(){
try{
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}catch(SecurityException e){
e.printStackTrace();
}
} @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{
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
}

onCreate()方法中,借助ContextCompat.checkSelfPermission()方法判断是否用户已经授权了,接收两个参数,第一个为Context,第二个参数为具体的权限名,然后将使用方法的返回值和PackageManager.PERMISSION_GRANTED作比较,相等就表示已经授权,否则就为没授权。

授权了就call(),没有授权就调用requestPermissions()方法,接收三个参数,第一个参数为Activity的实例,第二个参数为String数组,要把申请的权限名传入,第三个参数为请求码,传入1

Button makeCall = (Button)findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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();
}
}
});

调用完了requestPermissions()方法之后,系统会弹出一个权限申请的对话框,用户选择同意或者拒绝,不论什么结果,都会回调到onReqPermissionsResult()方法中

    @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{
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}

7.3 访问其他程序中的数据

内容提供器的用法:

1.使用现有的内容提供器来读取和操作相应程序中的数据

2.创建自己的内容提供器给程序的数据提供外部访问的接口

7.3.1 ContentResolver的基本用法

访问内容提供器中共享的数据,就一定要借助ContentResolve类,可以通过Context中的getContentResolver()方法获取该实例。

ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,insert()用于添加数据,update()用于更新数据,delete()用于删除数据,query()用于查询数据。

CRUD操作的参数为Uri参数,内容URI给内容提供器中的数据建立了唯一的标识符,由两部分组成:authority和path。

authority一般为程序包名,path则为用于对一应用程序中不同的表做区分的,最后要在字符串头部加上协议声明,如:

content://com.example.app.provider/table1

content://com.example.app.provider/table2

用Uri.parse()方法进行解析为Uri对象

Uri uri =  Uri.parse("content://com.example.app.provider/table1");

然后就可以利用该Uri对象来查询table1表中的数据了

Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);

通过Cursor对象来进行增删改查操作

1.查

遍历Cursor所有行,通过移动游标的方式,再取出每一行中相应列的数据。

if(cursor != null){
while(cursor.moveToNext()){
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}

2.增

向table1中添加数据

ContentValues values = new ContentValues();
values.put("column1","text");
values.put("column2","1");
getContentResolver().insert(uri, values);

3.清空column1值,update

ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ?and column2 = ?", new
String[]{"text", "1"});

4.删除

getContentResolver().delete(uri, "column2 = ?“, new String[]{ "1" });

7.3.2 读取系统联系人

新建项目ContactsTest,在activity_main中添加ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.song.contactstest.MainActivity"> <ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

修改MainActivity中的代码

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 java.util.ArrayList;
import java.util.List;
import java.util.jar.Manifest; public class MainActivity extends AppCompatActivity {
ArrayAdapter<String> adapter;
List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//加载ListView
ListView contactsView = (ListView)findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
contactsView.setAdapter(adapter);
//权限设置
if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[]{ android.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) {
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;
default:
}
}
}

重点在于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();
}
}
}

使用ContentResolver的query()方法来查询系统的联系人数据,没有调用Uri.parse()方法来解析Uri字符串,ContactsContract.CommomDataKinds.Phone类做好了封装

  String displayName =cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
  String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));  

将拼接好的数据添加到ListView的数据源里,并通知刷新ListView。

最后记得要关闭Cursor对象

在AndroidManifest.xml中加入权限声明

<uses-permission android:name="android.permission.READ_CONTACTS"/>

运行程序

7.4 创建自己的内容提供器

7.4.1 创建内容提供器的步骤

新建一个类来继承ContentProvider的方式来创建一个自己的内容提供器

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.support.annotation.Nullable; public class MyProvider extends ContentProvider{
@Override
public boolean onCreate() {
return false;
} @Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
} @Nullable
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
} @Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
} @Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
} @Nullable
@Override
public String getType(Uri uri) {
return null;
}
}

重写了6个方法

1.onCreate()

初始化内容提供器的时候调用,通常会完成对数据库的创建和升级等操作,返回true表示初始化成功,返回false则表示失败。

2.query()

从内容提供器中查询数据。使用Uri参数来确定查询哪张表,projection参数用于确定查询哪些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回。

3.insert()

向内容提供器中添加一条数据。使用uri参数来确定要添加的表,待添加的数据保存在values参数中。添加完成后,返回一个用于表示这条新纪录的Uri。

4.update()

更新内容提供器中以有的数据。使用uri参数来确定更新哪一张表中的数据,新数据保存在values中,selections和selectionArgs参数用于约束更新那些行,受影响的行数将作为返回值返回。

5.delete()

从内容提供器中删除数据。使用uri参数来确定删除哪一张表中的数据,selections和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。

6.getType()

根据传入的内容URI来返回相应的MIME类型。

同时,可以在URI内容后面添加通配符

数字:表示id    content://com.example.app.provider/table1/1

*:表示匹配任意长度的任意字符    content://com.example.app.provider/*

#:表示匹配任意长度的数字    content://com.example.app.provide/table1/#

借助UriMatch类可以实现匹配内容Uri的功能,UriMatch类提供了一个addURI()方法,接受3个参数,可以分别传入authority、path和一个自定义代码。当调用match()方法时,可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用该代码,就可以判断出调用方期望访问的是表中哪个数据了。

eg:

更改类MyProvider中的代码

public class MyProvider extends ContentProvider{
public static final int TABLE1_DIR = 0; public static final int TABLE1_ITEM = 1; public static final int TABLE2_DIR = 2; public static final int TABLE2_ITEM = 3; public static UriMatcher uriMatcher; static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
}
  
  ......
  
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
break;
case TABLE1_ITEM:
break;
case TABLE2_DIR:
break;
case TABLE2_ITEM:
break;
default:
break;
}
return null;
}
  .....
}
 

TABLE1_DIR表示访问table1表中的所有数据,TABLE1_ITEM表示访问table1表中的单条数据;TABLE2_DIR表示访问table2表中的所有数据,TABLE2_ITEM表示访问Table2中的单条数据。

除此之外,还有一种getType()方法,是所有内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型。

一个内容URI对应的MIME字符串由3部分组成:

1)必须以vnd开头

2)如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/。

3)最后接上vnd.<authority>.<path>

所以,对于content://com.example.app.provider/table1,对应的MIME类型为:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

对于content://com.example/app.provider/table1/1,对应的MIME类型为:

vnd.android.cursor.item/vnd.com.example.app.provider.table1

所以对MyProvider中的方法还可以继续完善为:

  @Override
public String getType(Uri uri) {
switch(uriMatcher.match(uri)){
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
}
return null;
}

7.4.2 实现跨程序数据共享

<Android基础> (七)内容提供器的更多相关文章

  1. Android入门(十三)内容提供器

    原文链接:http://www.orlion.ga/612/ 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一 ...

  2. 入职小白随笔之Android四大组件——内容提供器详解(Content Provider)

    Content Provider 内容提供器简介 内容提供器(Content Provider)主要用于在不同的应用程序之间 实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的 ...

  3. Android中的内容提供器

    用途 不同于File, SharedPreferences和DataBase,Content Provider主要用于不同的应用程序间共享数据,允许一个程序安全的访问另一个程序中的数据. 用法 通过C ...

  4. Android学习之基础知识十—内容提供器(Content Provider)

    一.跨程序共享数据——内容提供器简介 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能 ...

  5. Android基础总结(6)——内容提供器

    前面学习的数据持久化技术包括文件存储.SharedPreferences存储以及数据库存储技术保存的数据都只能被当前应用程序所访问.虽然文件存储和SharedPreferences存储中提供了MODE ...

  6. android学习十二(android的Content Provider(内容提供器)的使用)

    文件存储和SharePreference存储以及数据存储一般为了安全,最好用于当前应用程序中訪问和存储数据.内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能 ...

  7. Android入门(十四)内容提供器-实现跨程序共享实例

    原文链接:http://www.orlion.ga/661/ 打开SQLite博文中创建的 DatabaseDemo项目,首先将 MyDatabaseHelper中使用 Toast弹出创建数据库成功的 ...

  8. Android学习笔记(二十)——自定义内容提供器

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 如果我们想要实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以通过新建一个类去继承 Conten ...

  9. Android学习笔记(十九)——内容提供器

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整 ...

随机推荐

  1. idea git提交时候提示 --author 'java_suisui' is not 'Name ' and matches no existing author

    今天使用idea修改git项目的作者信息,提交时遇到错误: 0 files committed, 1 file failed to commit: test --author 'java_suisui ...

  2. 单机Qps上限是多少?

    现在这个年代,你要是不懂高并发,你都不好意思说自己是搞互联网的! 一.什么是并发,什么是高并发 并发,两个及以上的行为一起发生,比如你一边吃饭一边看电视 高并发,多个行为(至于是多少,这个没有定数,你 ...

  3. JS之BOMBOM!

    什么是BOM? bom即browser object model 也就是浏览器对象模型,BOM由多个对象组成,其中代表浏览器窗口的window对象是BOM的顶层对象,其他对象都是该对象的子对象. 顶层 ...

  4. Windows下建立ArcGIS Server集群

    原创文章,转载须标明出处自: http://www.cnblogs.com/gisspace/p/8269525.html -------------------------------------- ...

  5. Android Material Design控件使用(一)——ConstraintLayout 约束布局

    参考文章: 约束布局ConstraintLayout看这一篇就够了 ConstraintLayout - 属性篇 介绍 Android ConstraintLayout是谷歌推出替代PrecentLa ...

  6. python不能调试的原因

    最近有一个python项目,打开项目不能登录,想调试一下看看为什么,发现不能调试了,郁闷了,搞了半天,发现是进程里有多个python.exe,结束掉就好了.

  7. nginx 常见正则匹配符号表示

    1.^: 匹配字符串的开始位置: 2. $:匹配字符串的结束位置: 3..*: .匹配任意字符,*匹配数量0到正无穷: 4.\. 斜杠用来转义,\.匹配 . 特殊使用方法,记住记性了: 5.(值1|值 ...

  8. 随心测试_职场面试_001<SX的面试观点>

    快速理解_求职面试:必不可少的嘴 +  双向沟通交流 = 人与人之间的心理游戏 ps:以下为_面试题回答套路_案例,仅供参考,不挖坑 常见的面试题: 你是如何看待软件测试这个行业的? 说说你对软件测试 ...

  9. lvds接口介绍

    1.项目简介 用索尼的imx264 sensor采集图像,在内部模数转换之后,由lvds接收,然后解码,最后送给后端显示 2.框图 imx264配置成从模式,由spi总线配置,需要由FPGA提供 行. ...

  10. gradle配置国内镜像

    对单个项目生效,在项目中的build.gradle修改内容 buildscript { repositories { maven { url 'http://maven.aliyun.com/nexu ...