Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)

本模块共有四篇文章,参考郭神的《第一行代码》,对Content Provider的学习做一个详细的笔记,大家可以一起交流一下:


**简单起见,我们还是在之前的DatabaseTest项目(点击前往码云地址)的基础上继续开发。
需求是:通过内容提供器来给它加人外部访问接口。**

程序设计的步骤:
1.在A程序中注册内容提供器,写好接口处理方法;
      具体的,全局变量:定义自定义代码常量,定义authority常量,声明uriMatcher和DatabaseHelper对象;
      1.1 内容提供器中增删改查的程序设计步骤为:
            1.1.1  调用实例化DatabaseHelper的get方法获得SQLiteDatabase实例化对象;
                      (get方法即getWritableDatabase或者getReadableDatabase)
            1.1.2   接着,query需定义一个Cursor对象(cursor)用于接收返回结果;
                                  insert需定义一个Uri对象(urireturn)用于接收insert新增的数据行的uri
                                  update需定义一个int对象(updatedRows)用于接收受影响的行数;
                                  delete需定义一个int对象(deletedRows)用于接收受影响的行数(被删除的行数);
            1.1.3  使用switch语句进行对uri的判断及判断结果的处理;
2.在需要访问A程序的内容提供器的程序中,构建对应的Uri,通过getContentResolver调用增删改查即可;


下面开始详细解析:
打开DatabaseTest项目(点击前往码云地址),首先将MyDatabaseHelper中使用Toast弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用Toast(!!!!!)。然后创建一个内容提供器,右击com.example.databasetest包—New—Other—ContentProvider,



会弹出如图所示:

**这里我们将内容提供器命名为DatabaseProvider,
authority指定为com.example.databasetest.provider,
Exported属性表示是否允许外部程序访问我们的内容提供器,
Enabled属性表示是否启用这个内容提供器。
将两个属性都勾中,点击Finish完成创建。
接着我们修改DatabaseProvider:**

package com.example.databasetest;

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;

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);
    }

    @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);
                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);          //插入后返回一个id!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);//id构造用于返回的URI!!!!!!!!!!!!!!!!!!!!!!
                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.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);
                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.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.item/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.item/vnd.com.example.databasetest. provider.category";
        }
        return null;
    }

}
  • **代码虽然很长,不过都不难理解,都是上一节学习过的内容。

首先在类的一开始,同样是定义了4个常量,分别用于表示
访问Book表中的所有数据;
访问Book表中的单条数据;
访问Category表中的所有数据;
访问Category表中的单条数据。
然后在静态代码块里对UriMatcher进行了初始化操作,将期望匹配的几种URI格式添加了进去。**

  • **接下来是onCreate()方法:

创建了一个MyDatabaseHelper的实例,然后返回true表示内容提供器初始化成功,这时数据库就已经完成了创建或升级操作。**

  • **接着是query()方法:

首先获取到SQLiteDatabase实例,然后根据传入的Uri参数判断出用户想要访问哪张表,
再调用SQLiteDatabase的query()进行查询,并将Cursor对象返回即可。
注意当访问单条数据的时候有一个细节:
这里调用了Uri对象的getpathSegments()方法,它会将内容URI权限之后的部分以“/“符号进行分割,并把分割后
的结果放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id了。
得到了id之后,再通过selection和selectionArgs参数进行约束,就实现了查询单条数据的功能。**

getPathSegments().get(1)的解释参考:

  • **然后是insert()方法:

同样先获取到SQLiteDatabase实例,
然后根据传入的Uri参数判断出用户想要往哪张表里添加数据,
再调用SQLiteDatabase的insert()方法进行添加即可。
注意insert()方法要求返回一个能够表示这条新增数据的URI,则这里还需调用Uri.parse()方法来将一个内容URI解析成Uri对象,当然这个内容URI是以新增数据的id结尾的。**

  • **接着是update()方法:

先获取SQLiteDatabase实例,
然后根据传入的Uri参数判断出用户想要更新哪张表里的数据,
再调用SQLiteDatabase的update()方法进行更新即可,受影响的行数作为返回值返回。**

  • **然后是delete()方法:

先获取到SQLiteDatabase的实例,
然后根据传入的Uri参数判断出用户想要删除哪张表里的数据,
再调用SQLiteDatabase的delete()方法进行删除即可,被删除的行数作为返回值返回。**

  • 最后是getType()方法,这里按照上一节中介绍的格式规则编写即可。

至此内容提供器中的代码便全部编写完了。

**另外!!内容提供器一定要在AndroidMamfest.xml文件中注册才可以使用,
不过使用Androidstudio的快捷方式创建内容提供器的话,注册会被自动完成。
打开AndroidManifest.xmI文件看一下:**

**可以看到标签内出现了一个新的标签,我们使用它来注册内容提供器DatabaseProvider。
其中android:name指定DatabaseProvider的类名,
android:authorities指定了DatabaseProvider的authority,
enabled和exported属性则是根据我们刚才勾选的状态自动生成的。**

**现在DatabaseTest这个项目便具备跨程序共享数据的功能了。
可以调试一下:
首先将DatabaseTest程序从模拟器中删除掉,防止遗留数据的造成干扰。
然后运行一下项目,将DatabaseTest程序重新安装在模拟器上。
接着关闭掉DatabaseTest这个项目,并创建一个新项目ProviderTest,
接着通过这个程序去访问DatabaseTest中的数据,**

先编写布局文件:

<?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" >

    <Button
        android:id="@+id/add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add To Book" />

    <Button
        android:id="@+id/query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Query From Book" />

    <Button
        android:id="@+id/update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Update Book" />

    <Button
        android:id="@+id/delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete From Book" />

</LinearLayout>

MainActivity.java:

package com.example.providertest;

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button addData = (Button) 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", 55.55);
                Uri newUri = getContentResolver().insert(uri, values);
                newId = newUri.getPathSegments().get(1);
            }
        });
        Button queryData = (Button) 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"));
                        double price = cursor.getDouble(cursor. getColumnIndex("price"));
                        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 price is " + price);
                    }
                    cursor.close();
                }
            }
        });
        Button updateData = (Button) 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 = (Button) findViewById(R.id.delete_data);
        deleteData.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);
            }
        });
    }
}

其中值得注意的是:

从以上代码中,我们可以看到DIR类型常量匹配的,selection, selectionArgs参数位是由调用本内容提供器的时候由调用方程序提供的,
ITEM则不同,uri中已经包含了ID,我们可以使用getPathSegments将之get出来再使用,即ITEM类型常量匹配的,调用方程序无需提供selection, selectionArgs参数位(如下方的代码截图), uri中已经包含了信息,处理方法也在内容提供器中写好了。
(这一点在调用getContentResolver().update()以及getContentResolver().delete()的时候都一样)

下面进行代码的简析:
分别在这4个按钮的点击事件里面处理了增删改查的逻辑:

  • **添加数据的时候:

首先调用Uri.parse()将内容URI解析成Uri对象,
把要添加的数据存放到ContentValues对象中,
调用ContentResolver的insert()方法执行添加操作即可。
注意insert()会返回一个Uri对象,这个对象中包含了新增数据的id,这里用getPathSegments()将这个id取出,稍后会用到它;**

  • **查询数据的时候:

调用Uri.parse()将内容URI解析成Uri对象,
调用ContentResolver的query()方法去查询数据,
查询的结果存放在Cursor对象中,
对Cursor进行遍历,从中取出查询结果,并一一打印出来;**

  • **更新数据的时候:

调用Uri.parse()将内容URI解析成Uri对象,
把想要更新的数据存放到ContentValues对象中,
调用ContentResolver的update()方法执行更新操作即可;
注意这里为了不让Book表中的其他行受到影响,
在调用Uri.parse()方法时,给内容URI的尾部增加了一个id,而这个id正是添加数据时所返回的。
也就是说这里只更新刚刚添加的那条数据,不受影响Book表中的其他行。**

  • **删除数据的时候,

解析一个以id结尾的内容URI,
调用ContentResolver的delete()方法执行删除操作就可以了,
由于我们在内容URI里指定了一个id,因此只会删掉拥有相应id的那行数据,不会影响Book表中的其他数据。**

现在运行一下ProviderTest项目,效果图如下:


**点击一下AddToBook按钮,此时数据便已经添加到DatabaseTest程序的数据库中了,
可以点击QueryFromBook按钮来检查一下,打印日志如图:**

这里可以看到DatabaseTest程序中只有我们刚刚添加的一条数据,
databaseTest的SQLite数据库是我们在点击Add To Book的时候,试图访问DatabaseTest的内容提供器,由此DatabaseTest的内容提供器(DatabaseProvider)会触发DatabaseProvider.java中的onCreate()方法,如下,
由此创建了数据库,并返回True,这一点在 跨程序共享数据——Content Provider 之 创建自己的内容提供器 中曾经有提及;
创建了数据库之后,便添加了添加了一条数据,由此DatabaseTest程序中只有我们刚刚添加的那一条数据而已。

点击一下Update Book按钮来更新数据,再点击一下Query From Book按钮进行检查,结果如图:

最后点击Delete From Book按钮删除数据,此时再点击Query From Book按钮就查询不到数据了。

至此跨程序共享数据功能便成功实现了。

现在不仅是ProviderTest程序,任何一个程序都可以轻松访问DatabaseTest中的数据,同时丝毫不用担心隐私数据泄漏的问题。

相关文章
|
11天前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
48 4
|
4月前
|
开发工具 Android开发
Android项目架构设计问题之外部客户方便地设置回调如何解决
Android项目架构设计问题之外部客户方便地设置回调如何解决
29 0
|
4月前
|
监控 安全 API
Android项目架构设计问题之保证线上用户不会进入到本地配置页面如何解决
Android项目架构设计问题之保证线上用户不会进入到本地配置页面如何解决
32 0
|
5月前
软件研发核心问题之在需求拆解过程中,“数据与UI如何关联”的问题如何解决
软件研发核心问题之在需求拆解过程中,“数据与UI如何关联”的问题如何解决
|
5月前
|
小程序 API
跨端技术问题之哪些形态可以通过getApp()获取全局App实例
跨端技术问题之哪些形态可以通过getApp()获取全局App实例
|
6月前
|
监控 Serverless 开发工具
函数计算产品使用问题之要确保服务能在后台持续运行,而不依赖于WebUI是否打开,该怎么操作
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
7月前
|
Web App开发 Java Serverless
Serverless 应用引擎操作报错合集之阿里函数计算中,使用自定义运行时部署程序时,发现Chrome层已经建立但运行程序仍然缺失如何解决
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
7月前
|
开发者
Flutter状态管理终极方案GetX第二篇——状态管理
Flutter状态管理终极方案GetX第二篇——状态管理 在Flutter应用程序中,状态管理是必不可少的。GetX提供了简单易用的状态管理方案,使得开发者可以更加轻松地管理应用程序状态。下面介绍GetX中的状态管理方案。
276 0
|
计算机视觉
队列的概念及结构(内有成型代码可供CV工程师参考)
队列的概念及结构(内有成型代码可供CV工程师参考)
75 0
|
存储 安全 测试技术
去中心化社交软件ktalk开发步骤详情(源码demo实例分析)
ktalk是一款基于区块链技术的去中心化社交软件,它的设计理念是解锁社交自由,让用户可以畅所欲言,不受言论限制,实现真正的社交自由。