我的编程空间,编程开发者的网络收藏夹
学习永远不晚

[《第一行Android代码》读书笔记] 第7章 跨程序共享数据——探究内容提供器

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

[《第一行Android代码》读书笔记] 第7章 跨程序共享数据——探究内容提供器

第七章 跨程序共享数据——探究内容提供器

​ 数据持久化技术,包括文件存储、SharedPreferences存储以及数据库存储,都只能在当前应用程序中访问。跨程序数据共享需要用到另一种技术——内容提供器。

7.1 内容提供器简介

​ 内容提供器(Content Provider)主要用于不同的应用程序之间实现数据共享的功能,同时保证被访数据的安全性,是实现跨程序共享数据的标准方式。

7.2 运行时权限

​ Android权限机制作用比较有限,容易出现“店大欺客”现象。因此在Android 6.0引入运行时权限。

7.2.1 Android权限机制详解

​ 为了访问系统的网络状态以及监听开机广播,于是在

AndroidManifest.xml
文件中添加了两句权限声明:


因为涉及用户设备安全性,因此必须在该文件加入权限声明,否则程序会崩溃。

使用该机制,用户主要在两方面得到了保护:

在低于6.0系统的设备安装程序,在安装界面会给出提醒,让用户知晓程序申请了哪些权限,从而决定是否安装该程序。 用户可以随时在应用程序管理界面查看任意一个程序的权限申请情况,保证应用程序不会滥用权限。

该权限设计思路:用户如果认可你所申请的权限,那就安装,否则就拒绝安装。

运行时权限:用户不需要在安装应用程序时一次性赋予所有申请权限,而是在运行的时候再对某一权限的申请进行授权。这样使得用户可以拒绝某一权限,但仍然可以继续使用该应用。

Android权限分为三类:

普通权限(不会直接威胁到用户的安全和隐私的权限,如设备网络状态和开机自启动等,由系统自动授权) 危险权限(可能会触及用户隐私和对设备的安全性造成影响,如设备联系人信息和地理位置等,由用户手动点击授权) 特殊权限(用的很少)

除了危险权限之外,剩余的都是普通权限。下表列出了Android中的所有危险权限,一共9组24个权限。

权限组名 权限名
CALENDAR READ_CALENDAR
WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION -
ACCESS_COARSE_LOCATION -
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP -
PROCESS_OUTGOING_CALLS
SENSOR BODY_SENSORS
SMS - SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

附 Android完整权限列表

7.2.2 在程序运行时申请权限

新建项目

RuntimePermissionTest
,修改
activity_main.xml
布局文件,如下所示:


    

接着修改

MainActivity
中的代码,如下所示:

package com.example.runtimepermissiontest;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button makeCall = 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.ACTION_CALL是一个系统内置的打电话动作
                    intent.setData(Uri.parse("tel:10086"));
                    //指定了协议是tel,号码是10086
                    startActivity(intent);
                }catch (SecurityException e){
                    e.printStackTrace();
                }
            }
        });
    }
}

接下来修改

AndroidManifest.xml
文件


	
    <application
       ...

运行程序报错是因为权限被禁止所导致,因为6.0系统及以上系统在使用危险权限时都必须进行运行时权限处理。

修改

MainActivity
的代码,来修复这个问题,如下所示:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button makeCall = 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);
                    //如果没有授权就调用 ActivityCompat.requestPermissions()方法,第二个参数是权限名,第三个是请求码,唯一值就行
                } 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();
        }
    }
    
    @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 th permission", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}
7.3 访问其他程序中的数据

内容提供器的使用方法有两种,一种是使用现在的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们的程序提供外部访问接口。

7.3.1
ContentResolver
的基本用法

首先通过

Context
中的
getContextResolver()
方法获取该类的实例,然后调用该类提供的一系列方法用于对数据进行CRUD操作。其中:

insert() 方法:用于添加数据 update() 方法:用于更新数据 delete() 方法:用于删除数据 query() 方法:用于查询数据 内容URI

这些方法接收一个Uri参数,这个参数被称为内容URI,用于给内容提供器中的数据建立起唯一的标识符。它由两部分组成:

authority:用于区分不同的应用程序,一般用程序包名命名,比如某个程序的包名是
com.example.app
,那么该程序的authority可以命名为
com.example.app.provider
path:用于区分同一个应用程序中的不同表,通常添加到authority的后面

此外,在前面还需要加上协议声明。因此,内容URI的最标准格式写法如下:

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

另外,可以在后面加上一个id,表示访问id为该值的数据,如下所示标准访问id为1的数据:

content://com.example.app.provider/table/1

我们还可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下:

*:表示匹配任意长度的任意字符。

#:表示匹配任意长度的数字。

所以一个能匹配任意表的内容URI格式可以写成:

content://com.example.app.provider
            ActivityCompat.requestPermissions(MainActivity.this, new
                                              String[]{Manifest.permission.READ_CONTACTS}, 1);
            //如果没有授权就调用 ActivityCompat.requestPermissions()方法,第二个参数是权限名,第三个是请求码,唯一值就行
        } else {
            readContacts();
        }
    }
    private void readContacts() {
        Cursor cursor = null;
        try {
            //查询联系人数据,ContactsContract.CommonDataKinds.Phone.CONTENT_URI封装了联系人数据表的URI
            getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
            if (cursor.moveToNext()) {
                //获取联系人姓名,常量ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME对应联系人姓名
                String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                //获取联系人手机号码,常量ContactsContract.CommonDataKinds.Phone.NUMBER对应联系人手机号码
                String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                contactsList.add(displayName + "\n" + number);
            }
            //通知刷新一下ListView
            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 th permission", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}

别忘了,还需要在

AndroidManifest.xml
声明读取系统联系人的权限,如下所示:


    
    <application
      ...
7.4 创建自己的内容提供器 7.4.1 创建内容提供器的步骤

新建一个类去继承

ContentProvider
的方式来创建一个自己的内容提供器,然后重写该类的6个抽象方法,代码如下所示:

class MyProvider extends ContentProvider{
    
    @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) {
        return null;
    }
    
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        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;
    }
}

因为所有的CRUD操作都一定要匹配到相应的内容URI格式才能进行的,而我们当然不可能向

UriMatch
中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问到,因此保证了隐私数据不会泄露出去。

7.4.2 实现跨程序数据共享

打开上一章

DatabaseTest
项目,创建一个内容提供器,代码如下所示:

public class DatabaseProvider extends ContentProvider {
    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;
    public static final String AUTHORITY = "com.example.databasetest.provider";
    private static UriMatcher uriMatcher;
    private MyDatabaseHelper dbHelper;
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }
    public DatabaseProvider() {
    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //删除数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        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;
    }
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
            case BOOK_ITEM:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
        }
        return null;
    }
    @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 boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
        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:
                cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);//将内容URI权限后的部分以“/”进行分割并放入一个字符串列表,第0个位置是路径,第1个位置是id
                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 int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        //更新数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        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);
                db.update("Category", values, "id =?", new String[]{categoryId});
                break;
        }
        return updatedRows;
    }
}

删除并重新安装

DatabaseTest
程序,接着新建一个新项目
ProviderTest
。先修改
activity_main
布局文件,如下所示:


    

然后修改

MainActivity
中的代码,如下所示:

public class MainActivity extends AppCompatActivity {
    private String newId;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button addData = findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //添加数据
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name", "A Clash of Kings");
                values.put("author", "George Martin");
                values.put("pages", 1040);
                values.put("price", 22.85);
                Uri newUri = getContentResolver().insert(uri, values);
                newId = newUri.getPathSegments().get(1);
            }
        });
        Button queryData = findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //查询数据
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                Cursor cursor = getContentResolver().query(uri, null, null, null, null);
                if (cursor != null) {
                    while (cursor.moveToNext()) {
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String author = cursor.getString(cursor.getColumnIndex("author"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        float prices = cursor.getFloat(cursor.getColumnIndex("prices"));
                        Log.d("MainActivity", "book name is " + name);
                        Log.d("MainActivity", "book author is " + author);
                        Log.d("MainActivity", "book pages is " + pages);
                        Log.d("MainActivity", "book prices is " + prices);
                    }
                    cursor.close();
                }
            }
        });
        Button updateData = findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //更新数据
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                ContentValues values = new ContentValues();
                values.put("name", "A Storm of Swords");
                values.put("pages", 1216);
                values.put("price", 24.05);
                getContentResolver().update(uri, values, null, null);
            }
        });
        Button deleteData = findViewById(R.id.delete_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //删除数据
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId);
                getContentResolver().delete(uri, null, null);
            }
        });
    }
}
7.5 Git时间——版本控制进阶

7.6 小结和点评

在本章中,我们一开始了解了Android的权限机制,并且学会了如何在6.0以上的系统使用运行时权限,然后又学习了内容提供器的相关内容,以实现跨程序数据共享的功能。


作者:alanliang1998


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

[《第一行Android代码》读书笔记] 第7章 跨程序共享数据——探究内容提供器

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

[《第一行Android代码》读书笔记] 第7章 跨程序共享数据——探究内容提供器

第七章 跨程序共享数据——探究内容提供器 ​ 数据持久化技术,包括文件存储、SharedPreferences存储以及数据库存储,都只能在当前应用程序中访问。跨程序数据共享需要用到另一种技术——内容提供器。 7.1 内容提供器简介 ​
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录