《第一行代码》7 ContentProvider内容提供器

个人笔记,仅供参考

《第一行代码》7 ContentProvider内容提供器_第1张图片
目录

1 内容提供器简介

ContentProvider 主要用于应用程序之间实现数据共享
它提供了一套完整的机制,允许一个程序访问另一个程序的数据,同时还能保证被访数据的安全性。

2 访问其他程序的数据

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

如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他应用程序都可以对这部分数据进行访问。Android自带的电话薄、短信、媒体库都提供了类似的外部访问接口,这使得第三方应用程序可以充分利用这些数据来提供更好的功能

2-1 ContentResolver 内容解析器基本用法
  • 想要访问内容提供器的共享数据,则需要要借助ContentResolver
  • 可以通过Context的getContentResolver()方法获取到该类的实例
  • ContentResolver 提供了一系列方法对数据进行CRUD操作:
    添加数据:insert()
    更新数据:update()
    删除数据:delete()
    查询数据:query()
  • 不同于SQLiteDatabase,ContentResolver 中的增改删查都是不接收表名的,而是使用一个Uri代替,这个参数被称为内容URI
  • 内容URI 给内容提供器中的数据建立了唯一标识符
    它由两部分组成:authority 和 path (授权 和 路径)

authority
是用于对不同的应用程序做区分的,为了避免冲突,一般用包名的方式来命名
比如某个程序包名是:com.example.app
那么它的 authority 就是:com.example.app.provider
path
是用于对同一个程序不同表做区分的,通常会添加到 authority 后面
比如某个程序的数据库里存在2张表 table1和table2
那么就可以将 path 分别命名为 /table1 和 /table2

  • 最后把authority 和 path 进行组合,并在头部加上协议,就生成了标准格式的内容URI
content://com.example.app.provider/table1
content://com.example.app.provider/table2

这样可以非常清楚地表示我们想访问哪个程序哪张表里的数据

  • 在得到内容URI字符串之后,还需要把它作为参数来解析得到Uri对象
    拿到Uri对象以后,我们就可以进行增删改查
Uri uri = Uri.parse("content://com.example.app.provider/table1");

1 - 查询数据:

        Cursor cursor = getContentResolver().query(
                uri,
                projection,
                selection,
                selectionArgs,
                sortOrder);
《第一行代码》7 ContentProvider内容提供器_第2张图片
  • uri已经指明了我们正在查询table1表的数据
    查询完成后会返回一个Cursor对象,我们可以通过移动游标的位置来遍历Cursor的所有行,然后再取出每一行中相应列的数据
    如下:
        if(cursor!=null){
            while (cursor.moveToNext()){
                String column1 = cursor.getString(cursor.getColumnIndex("column1"));
                int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
            }
            cursor.close();
        }

可以见是利用游标不断读取每一行的 column1和column2列的数据

2 - 添加数据:

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

3 - 更新数据:

        ContentValues values =  new ContentValues();
        values.put("column1","");
        getContentResolver().update(uri,values,"column1 = ? and column2 = ?",new String[]{"text","1"});
  • 这里我们找到了刚才添加的那条数据,然后把column1列的值进行了清空

4 - 删除数据:

getContentResolver().delete(uri,"column2 = ?",new String[]{"1"});
  • 找到刚才新增的这条数据并删除

这其实在Android数据存储那章SQLite里有详细讲解过,如果对上面这一块不熟悉,可以参考这里

2-2 读取系统联系人

下面来看具体实例:
新建项目GetPhoneNumDemo

  • 先来看布局文件,简单放入一个ListView


    
    

  • 然后再来看MainActivity
package com.example.getphonenumdemo;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    ArrayAdapter adapter;
    List contactsList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView contactsView = findViewById(R.id.contacts_view);
        //ListView加载适配器
        adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList);
        contactsView.setAdapter(adapter);
        //动态检查6.0权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) !=
                PackageManager.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, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    readContacts();
                } else {
                    Toast.makeText(this, "您拒绝了读取权限", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }
}

  • 拿到ListView实例并加载适配器
  • ContextCompat.checkSelfPermission这一段主要是检查6.0权限,因为读取联系人需要较高的权限,没问题以后则调用readContacts()方法开始读取
  • ContactsContract.CommonDataKinds.Phone.CONTENT_URI是系统封装好了的常量,同样也是Uri.parse的结果,即指明了我们要查询系统联系人这张表
  • 而且
    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
    ContactsContract.CommonDataKinds.Phone.NUMBER
    也正好是系统联系人名称、手机号的列名
    因此我们可以很容易拿到这两个数据并拼接起来,并显示在ListView上
  • 最后,我们要记得在配置文件AndroidManifest中加入读取联系人的权限声明

运行效果:即读取到了通讯录的内容(这个4个是我新建的联系人)


《第一行代码》7 ContentProvider内容提供器_第3张图片

总结:可见利用内容提供器读取数据的步骤很简单,只需要获取到该应用程序的内容URI,然后借助ContentResolver进行CRUD操作即可。

3 创建自己的内容提供器

3-1 创建内容提供器的步骤
  • 直接上代码
package com.example.getnumdemo;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;


public class MyContentProvider 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;
    }
}

实现数据共享官方推荐的是使用内容提供器ContentProvider
第一步:
我们新建一个类MyContentProvider 去继承ContentProvider并重写其
6个方法

我们其实已经很容易能看出它们表达的意义

《第一行代码》7 ContentProvider内容提供器_第4张图片

可以看到每个方法都传入了Uri这个参数,这个参数也是调用ContentResolver的增删改查时传过来的
第二步:
我们要对传入的Uri进行解析,从而分析出调用方需要访问哪些表和数据

《第一行代码》7 ContentProvider内容提供器_第5张图片
  • 内容URI的格式只要有以上两种:
    (1)以路径结尾表示访问该表中所有数据
    (2)以id结尾表示访问该表中拥有相应id的数据
  • 此外我们还可以使用通配符的方式来分别匹配这两种格式的内容URI


    《第一行代码》7 ContentProvider内容提供器_第6张图片

第三步:
借助UriMatcher类来实现匹配内容URI的功能

—— UriMatcher提供了addURL()方法,接收3个参数
分别是:authority、path、自定义代码
—— 当UriMatcher调用match()方法时,可以将一个Uri对象传入,返回值是能够匹配这个Uri对象的自定义代码
—— 利用这个代码,我们就可以判断出调用方期望访问的是哪张表的数据

  • 以查询为例
package com.example.databasetest;


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;

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;

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

    @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_ITEM:
                //查询table1表中的单条数据
                break;
            case TABLE2_DIR:
                //查询table2表中的所有数据
                break;
            case TABLE2_ITEM:
                //查询table2表中的单条数据
                break;
            default:
                break;
        }
        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;
    }
}

  • 在MyProvider中新增了4个整型常量
  • TABLE1_DIR:表示访问 table1表中的所有数据
    TABLE1_ITEM :表示访问 table1表中的单条数据
    TABLE2_DIR :表示访问 table2表中的所有数据
    TABLE2_ITEM :表示访问 table2表中的单条数据
  • 然后在静态代码块创建了UriMatcher的实例,并调用addURI()方法,将期望匹配的内容URI传递进去,这里路径参数可以使用头佩服
  • 然后当query()方法被调用的时候,会通过UriMatcher的match()方法对传入的Uri进行匹配,如果发现UriMatcher中某个内容URI格式成功匹配了该Uri对象,则会返回相应的自定义代码,我们就可以判断出调用方期望访问的是什么数据。
  • 这里仅仅只是示范了query()方法,insert()、update()、delete()实现方式也是差不多

第四步:
认识getType()方法

主要用于获取Uri对象所对应的MIME类型
一个内容URI所对应的MIME字符串主要由3部分组成:

  • 若内容URI是 content://com.example.app.provider/table1
    对应的MIME类型是 vnd.android.cursor.dir/vnd.com.example.app.provider.table1

  • 若内容URI是 content://com.example.app.provider/table1/1
    对应的MIME类型是 vnd.android.cursor.item/vnd.com.example.app.provider.table1

  • 修改MyContentProvider 的getType()方法:

《第一行代码》7 ContentProvider内容提供器_第7张图片

3-2 实现跨程序共享

简单起见,我们这里复用另一章 《Android数据存储》中最后介绍SQLite演示的 DatabaseTest项目,并在此基础上进行开发。

  • 首先去掉MyDatabaseHelper中创建数据库成功的相关Toast提示去掉,因为我们跨程序访问的时候不能直接使用Toast

之前的DatabaseTest项目,主要是使用了 SQLite数据库在本地的新建了2个表Book和Category ,并保存了几条数据。

DatabaseTest项目代码如下:

package com.example.databasetest;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper{

    public static final String CREATE_BOOK = "create table Book ( id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)";

    public static final String CREATE_CATEGORY = "create table Category ("
            + "id integer primary key autoincrement,"
            + "category_name text,"
            + "category_code integer)";

    private Context context;

    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        this.context = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //建Book表
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Category");
        onCreate(db);
    }
}
package com.example.databasetest;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
        Button createDatabase = findViewById(R.id.create_database);

        //创建数据库
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dbHelper.getWritableDatabase();
            }
        });

        //添加数据
        Button addData = findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values =  new ContentValues();
                //组装和插入第一条数据
                values.put("name","The Da Vinci Code");
                values.put("author","Dan Brown");
                values.put("pages",454);
                values.put("price",16.96);
                db.insert("Book",null,values);
                values.clear();
                //组装和插入第二条数据
                values.put("name","The Lost Symbol");
                values.put("author","Dan Brown");
                values.put("pages",510);
                values.put("price",19.95);
                db.insert("Book",null,values);
                Toast.makeText(MainActivity.this, "数据添加成功", Toast.LENGTH_SHORT).show();
            }
        });

        //更新数据
        Button updateData = findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values =  new ContentValues();
                values.put("price",10.99);
                db.update("Book",values,"name = ?",new String[]{"The Da Vinci Code"});
                Toast.makeText(MainActivity.this, "数据更新成功", Toast.LENGTH_SHORT).show();
            }
        });

        //删除数据
        Button deleteButton = findViewById(R.id.delete_data);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                db.delete("Book","pages > ?",new String[]{"500"});
                Toast.makeText(MainActivity.this, "数据删除成功", Toast.LENGTH_SHORT).show();
            }
        });

        //查询数据
        Button queryButton = findViewById(R.id.query_data);
        queryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                //查询Book表中所有数据
                Cursor cursor = db.query("Book",null,null,null,null,null,null);
                if(cursor.moveToFirst()){
                    do {
                        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"));
                        Toast.makeText(MainActivity.this, name+" "+author+" "+pages+" "+price, Toast.LENGTH_SHORT).show();
                    }while (cursor.moveToNext());
                }
                cursor.close();
            }
        });
    }
}

接着我们先在DatabaseTest项目新建我们的内容提供器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";
    public 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 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;
        }
        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;
    }

    @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:
                //返回新增数据的id,并解析成uri对象返回
                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);
                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 updateRows = 0;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
                updateRows = db.update("Book",values,selection,selectionArgs);
                break;
            case BOOK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updateRows = db.update("Book",values,"id=?",new String[]{bookId});
                break;
            case CATEGORY_DIR:
                updateRows = db.update("Category",values,selection,selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updateRows = db.update("Category",values,"id=?",new String[]{categoryId});
                break;
        }
        return updateRows;
    }
}

解析一下:

  • 首先定义4个常量
    BOOK_DIR 代表访问 Book表中的所有数据
    BOOK_ITEM 代表访问 Book表中的单条数据
    CATEGORY_DIR 代表访问 Category表中的所有数据
    CATEGORY_ITEM 代表访问 Category表中的单条数据
  • 然后在静态代码块中对 UriMatcher 进行了初始化
    将期望匹配的几种URI添加了进去
  • 接下来是对每个抽象方法的具体实现

—— onCreate()
主要是创建了一个MyDatabaseHelper实例
返回了true代表内容提供器初始化成功
此时数据库已经完成了创建和升级操作

—— query()
先是获取SQLiteDatabase 的实例db
然后根据传入的Uri参数来判断用户想访问哪张表
最后调用SQLiteDatabase 的query()方法进行查询
并将Cursor对象返回
注意这里有个细节:
当访问单条数据时,这里调用了Uri 的getPathSegments()方法
它会将URI权限之后的部分以 " / " 符号进行分割
并返回一个字符串列表
而这个字符串列表第0个位置存放的是路径
第1个位置存放的就是id
得到了id以后,我们就可能根据约束条件进行单条数据的查询

—— insert()
当调用SQLiteDatabase 的 insert() 方法后
这里要求返回一个能够表示这条新增数据的URI
然后我们需要调用Uri.parse来解析它

update() 和 delete() 相信已经无需再赘述
而 getType() 则是完全按照 3-1节的规则来编写,也无需再细讲

  • 最后我们记得去配置文件看一下看是否声明
    (一般在新建ContentProvider的时候AS会帮我们自动创建)


    《第一行代码》7 ContentProvider内容提供器_第8张图片
  • 我们指明了authorities
    enabled :表示是否启动这个内容提供器
    exported :表示是否允许被外部程序访问这个内容提供器


现在DatabaseTest项目就拥有数据共享的功能,可以被其他程序调用;
为了避免上一章遗留数据造成的影响,我们先卸载一次DatabaseTest项目,然后再重新运行一遍

接着创建一个新的项目ProviderTest
开始尝试通过内容提供器DatabaseProvider来访问DatabaseTest项目的数据

  • 先看布局文件,加入四个按钮,点击分别执行
    添加数据、更新数据、查询数据、删除数据


    
  • 然后在MainActivity加入监听事件
package com.example.providertest;

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

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);
                Toast.makeText(MainActivity.this, "添加数据成功", Toast.LENGTH_SHORT).show();
            }
        });

        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"));
                        double price = cursor.getDouble(cursor.getColumnIndex("price"));
                        Toast.makeText(MainActivity.this, "book name is"+name +"\n"
                                + "author is"+author +"\n"
                                + "pages is"+pages +"\n"
                                + "price is"+price, Toast.LENGTH_SHORT).show();

                    }
                    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);
                Toast.makeText(MainActivity.this, "更新数据成功", Toast.LENGTH_SHORT).show();
            }
        });

        Button deleteData = 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);
                Toast.makeText(MainActivity.this, "删除数据成功", Toast.LENGTH_SHORT).show();
            }
        });

    }
}

  • 添加数据:
    首先调用 Uri.parse() 将一个内容URI解析成Uri对象
    然后用ContentValues组装好要添加的数据
    接着调用ContentResolver的insert()方法插入数据即可
    这里insert()返回了一个Uri对象,里面包含了新增数据的id,我们通过getPathSegments()将这个id取出,等下更新数据的时候需要用到

  • 查询数据:
    首先调用 Uri.parse() 将一个内容URI解析成Uri对象
    然后通过ContentResolver的query()方法进行查询
    利用Cursor去一行行读取,并最终把结果用Toast显示出来

  • 更新数据:
    首先调用 Uri.parse() 将一个内容URI解析成Uri对象
    然后把要更新的数据保存在ContentValues里
    最后调用ContentResolver的update()方法
    <注意这里>
    我们为了不影响Book表中的其他数据,在调用 Uri.parse()的时候,给内容URI的尾部增加了一个id,这个id正是添加数据的时候返回的
    这就具体指明了本次更新操作,仅仅只是修改刚才添加的那条数据,Book表的其他行数据不受影响

  • 删除数据:
    首先调用 Uri.parse() 将一个内容URI解析成Uri对象
    然后调用ContentResolver的delete()方法
    并且在URI尾部指明了id,因此本次删除操作只针对刚才添加的那条数据,Book表的其他行数据不受影响


到这里就分析完了
一起看看效果:

我这里暂时用的模拟器,此时无论程序是否运行中
我首先已经先安装好了这2个程序

  • 打开ProviderTest项目,开始跨程序使用DatabaseTest里的数据
  • 点击第一个按钮,添加数据,弹出提示
    然后点击第二个按钮,查询一下


    《第一行代码》7 ContentProvider内容提供器_第9张图片

可以看见分别弹出DatabaseTest里两条旧数据
以及我们利用DatabaseProvider新增的数据



添加成功!

最后,查询、更新、以及删除的效果就不赘述了,可以操作一次后马上点击查询按钮来验证,楼主已经运行过了一遍,说明通过自定义内容提供器,已经可以实现数据共享给其他程序调用。


参考:《第一行代码》

你可能感兴趣的:(《第一行代码》7 ContentProvider内容提供器)