第一行代码读书笔记 7 -- 内容提供器

本篇文章主要介绍以下几个知识点:

  • Android 运行时权限;
  • 内容提供器
图片来源于网络

7.1 运行时权限

在介绍内容提供器之前先来了解了解 Android 运行时权限。

7.1.1 Android 权限机制

Android 把所有的权限归成两类:普通权限和危险权限。

  • 普通权限
      不会直接威胁到用户的安全和隐私的权限,对于这部分的权限申请,系统会自动帮我们进行授权。

  • 危险权限
      可能触及用户隐私,或对设备安全性造成影响的权限,如获取联系人信息、地理位置等,对于这部分的权限申请,必须由用户手动点击授权才可以,否则程序无法使用相应的功能。

下面列出了 Android 中所有的危险权限:

Android中的9组24个危险权限

上表中每个危险权限都属于一个权限组,若用户同意授权某个权限名,那么该权限所对应的权限组中的其他权限也会同时跟着被授权。

注:访问 http://developer.android.com/reference/android/Manifest.permission.html 可以查看完整的权限列表。

7.1.2 在运行程序时申请权限

接下来通过个例子来介绍如何在运行程序时申请权限。

此次的例子是通过点击界面上的一个按钮来直接拨打电话。拨打电话时需要声明上节表中的 CALL_PHONE 这个危险权限。

首先,在 AndroidManifest.xml 文件中声明权限:

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

然后在布局 activity_call_phone.xml 中添加个按钮:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <Button
        android:id="@+id/btn_call_phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="点击拨打电话"/>

</RelativeLayout>

接着修改 activity 中代码如下:

public class CallPhoneActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call_phone);

        Button btn_call_phone = (Button) findViewById(R.id.btn_call_phone);
        btn_call_phone.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    // Intent.ACTION_CALL 是系统内置的打电话动作(而Intent.ACTION_DIAL指打开拨号界面,不需要声明权限)
                    Intent intent = new Intent(Intent.ACTION_CALL);
                    intent.setData(Uri.parse("tel:10086"));
                    startActivity(intent);
                }catch (SecurityException e){
                    e.printStackTrace();
                }
            }
        });
    }
}

在 Android 6.0 前,拨打电话功能的实现很简单,上面代码就把拨打电话功能实现了,在低于 Android 6.0 系统的手机上可以正常运行,但在 Android 6.0 或更高版本系统的手机上运行则无效,会抛出“Permission Denial”的异常。

接下来,修改 activity 中的代码,修复上面的问题,如下:

public class CallPhoneActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call_phone);

        Button btn_call_phone = (Button) findViewById(R.id.btn_call_phone);
        btn_call_phone.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 1. 判断用户是否授权,借助 ContextCompat.checkSelfPermission() 方法
                // ContextCompat.checkSelfPermission() 方法接收两参数:context 和权限名
                if (ActivityCompat.checkSelfPermission(CallPhoneActivity.this,
                        Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                    // 3. 若没授权,则调用 ActivityCompat.requestPermissions()方法来向用户授权
                    // 其三个参数:Activity实例、String数组(放权限名)、请求码(只要唯一就行了,这里传1)
                    ActivityCompat.requestPermissions(CallPhoneActivity.this,
                            new String[]{Manifest.permission.CALL_PHONE}, 1);
                    // 4. 调用完 requestPermissions()方法后,会弹出一个权限申请对话框,供用户选择,
                    // 最后回调 onRequestPermissionsResult()方法
                }else {
                    // 2. 若已授权,则直接执行拨打电话的逻辑
                    call();
                }
            }
        });
    }

    /**
     * 拨打电话方法
     */
    private void call() {
        try {
            // Intent.ACTION_CALL 是系统内置的打电话动作
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        }catch (SecurityException e){
            e.printStackTrace();
        }
    }

    /**
     * 无论用户是否同意权限申请,都会回调此方法
     * @param requestCode
     * @param permissions
     * @param grantResults 授权的结果
     */
    @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 {
                    ToastUtils.showShort("你拒绝了权限请求");
                }
                break;

            default:
                break;
        }
    }
}

这样就完成了权限申请的代码编写了,具体过程看代码注释,现在运行一下程序,效果如下:

拨打电话权限申请效果

好了,关于运行时权限的内容先介绍到这,下面进入主题——内容提供器。

7.2 内容提供器

内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问到另一个程序中的数据,同时还能保证被访问数据的安全性。目前,使用内容提供器是 Android 实现跨程序共享数据的标准方式。

不同于文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄露的风险。

7.2.1 访问其他程序中的数据

内容提供器的用法有两种:

  • 使用现有的内容提供器来读取和操作相应程序中的数据
  • 创建自己的内容提供器给我们程序的数据提供外部访问接口

7.2.1.1 ContentResolver 的基本用法

每一个应用程序,借助 ContenResolver 类才能访问内容提供器中共享的数据,该类可通过 Context 中的 getContentResolver() 方法获取实例,该类提供的一系列对数据的增删查改方法不同于 SQLiteDataBase,是不接收表名参数的,而是用参数Uri(称为内容 URI) 代替。

内容 URI 给内容提供器中的数据建立了唯一标识符,它由 authority(区分不同的应用程序)和 path(区分不同的表)组成。如某个程序包名为 com.example.app 且存在两张表 table1 和 table2 ,其标准格式写法如下:

内容 URI 的标准写法

在得到了 内容 URI 字符串后,需要调用 Uri.parse() 方法把它解析成 Uri 对象才可作为参数传入,代码如下:

Uri uri = Uri.parse("content://com.example.app.provider/table1")
  • 查询操作

现在就可以用这个 Uri 对象来查询 table1 表中的数据了,代码如下:

Curson curson = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);

下表对上面的参数进行了详细的解释:

参数详解

查询完后返回一个 Curson 对象,读取代码如下:

// 通过移动游标的位置来遍历Cursor的所有行
if (cursor != null){
            while (cursor.moveToNext()){
                String column1 = cursor.getString(cursor.getColumnIndex("column1"));
                int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
            }
            cursor.close();
        }
  • 添加操作

向 table1 表中添加一条数据:

// 将待添加的数据组装到 ContentValues 中
ContentValues values = new ContentValues();
values.put("column1","text");
values.put("column2",1);
// 调用 insert() 方法添加数据
getContentResolver().insert(uri, values);
  • 更新操作

更新数据,把 column1 的值清空:

ContentValues values = new ContentValues();
// 清空
values.put("column1","");
// 调用 update() 方法更新数据
getContentResolver().update(uri, values, "column1 = ? and column2 = ?",new String[]{"text","1"});
  • 删除操作

调用 ContentResolver 的 delete() 删除数据:

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

以上就是 ContentResolver 中的增删查改方法。

7.2.1.2 读取系统联系人

接下来举个例子来加深学习:利用内容提供器来读取系统联系人。

由于模拟器上木有联系人,先向模拟器中创建几个联系人。联系人准备好后,首先在项目的 AndroidManifest.xml 中声明读取联系人的权限:

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

简单起见,在布局 activity_read_contact.xml 中放一个 ListView 来显示读取的联系人信息:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView
        android:id="@+id/contacts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>

</RelativeLayout>

接着修改 activity 中的代码如下:

public class ReadContactActivity extends AppCompatActivity {
    
    ArrayAdapter<String> adapter;
    List<String> contactsList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_read_contact);

        // 获取 listView 的实例,设置适配器
        ListView contacts_view = (ListView) findViewById(R.id.contacts_view);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,contactsList);
        contacts_view.setAdapter(adapter);
        
        // 判断是否授权
        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 name = cursor.getString(cursor.getColumnIndex
                            (ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    // 获取联系人号码
                    String number = cursor.getString(cursor.getColumnIndex
                            (ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add(name + "\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 {
                    ToastUtils.showShort("你拒绝了权限请求");
                }
                break;
            
            default:
                break;
        }
    }
}

运行程序,效果如下:

访问联系人并展示

7.2.2 创建自己的内容提供器

上一小节,介绍了如何访问其他应用程序得数据,其思路是获取应用程序的内容 URI 后借助 ContentResolver 进行 CRUD 操作就行了。接下来介绍创建自己的内容提供器。

7.2.2.1 创建步骤

首先,新建一个类去继承 ContentProvider,重写它的6个抽象方法,如下:

/**
 * 自己的内容提供器
 * Created by KXwon on 2016/12/18.
 */

public class MyProvider extends ContentProvider{
    
    // 初始化内容提供器的时候调用,返回true表示成功,false失败
    @Override
    public boolean onCreate() {
        return false;
    }

    // 从内容提供器中查询数据
    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        return null;
    }
    
   // 向内容提供器中添加一条数据
    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        return null;
    }

    // 从内容提供器中删除数据
    @Override
    public int delete(Uri uri, String s, String[] strings) {
        return 0;
    }

    // 更新内容提供器中已有的数据
    @Override
    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
        return 0;
    }
    
    // 根据传入的内容 URI 来返回 MIME 类型
    @Override
    public String getType(Uri uri) {
        return null;
    }
}

之前提到一个标准的内容 URI 写法是这样的:

// 表示调用方期望访问的是 com.example.app 这个应用的 table1 表中的数据
content://com.example.app.provider/table1

除此之外,还可以在内容 URI 后面加一个id,如下:

// 表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据
content://com.example.app.provider/table1/1

内容 URI 的格式主要就只有以上两种,可以通过用通配符的方式来匹配这两种内容 URI,规则如下:
 (1) *:表示匹配任意长度的任意字符。
 (2)# :表示匹配任意长度的数字

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

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

一个能够匹配 table1 表中任意一行数据的内容 URI 格式可写成:

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

然后,再借助 UriMatcher 这个类实现内容 URI 功能,修改 MyProvider 类如下:

public class MyProvider extends ContentProvider{
    
    public static final int TABLE1_DIR = 0; //访问 table1 表中的所有数据
    public static final int TABLE1_ITEM = 1;//访问 table1 表中的单条数据
    public static final int TABLE2_DIR = 3; //访问 table2 表中的所有数据
    public static final int TABLE2_ITEM = 4;//访问 table2 表中的单条数据
    
    private static UriMatcher uriMatcher;
    
    static {
        // 创建 UriMatcher 实例
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 调用 addURI() 方法,此方法接收3个参数:authority、path、自定义代码
        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 Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        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;
        }
        . . .
    }

   . . .
}

上述代码只是以 query() 方法做了个示范,其他3个增删改的方法也差不多。

而 getType() 方法,是所有的内容提供器都必须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型。MIME 字符串主要由 3 部分组成,并有如下格式规定:
 (1) 必须由 vnd 开头
 (2)若内容 URI 以路径结尾,则后接 android.cursor.dir/,若内容 URI 以 id 结尾,则后接 android.cursor.item/
 (3)最后接上 vnd.<authority>.<path>

最后,实现 getType() 方法中的逻辑,完善 MyProvide r类如下:

public class MyProvider extends ContentProvider{

    . . . 

    // 根据传入的内容 URI 来返回 MIME 类型
    @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";
            default:
                break;
        }
        return null;
    }
}

到这里,一个完整的内容提供器就创建完成了。

7.2.2.1 实现跨程序数据共享

好了,例子来了。上一章项目中建立了 BookStore.db 数据库,里面有 book 表和 category 表这两张表,为简单起见,在上一章的基础上继续开发。

首先,为项目创建个内容提供器,在 Android Studio 中右击 com.wonderful.myfirstcode.chapter7.provider包(你项目所在的包名)→New→Other→Content Provider,会弹出如下窗口:

创建内容提供器的窗口

可以看到,这里把内容提供器命名为 DatabaseProvider,authority 指定为项目包名,Exported 表示是否允许外部程序访问我们的内容提供器,Enabled 表示是否启用这个内容提供器。两个勾选点击 Finish 完成创建。

接着修改 DatebaseProvider 中的代码如下:

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

另外,内容提供器一定要在 AndroidManifest.xml 中注册才可使用。不过刚用 AS 创建的内容提供器已经帮我们自动注册完成了,如下:

 <provider
     android:name=".chapter7.provider.DataBaseProvider"
     android:authorities="com.wonderful.myfirstcode.chapter7.provider"
     android:enabled="true"
     android:exported="true">
</provider>

现在,内容提供器已经创建好了,接下来新建一个项目 ProviderTest 来访问上面程序中的数据,记得清空上一章项目里的数据,以防造成干涉。

先来编写下布局文件 activity_main.xml,添加4个按钮来增删查改,如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加数据"/>

    <Button
        android:id="@+id/btn_query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查询数据"/>

    <Button
        android:id="@+id/btn_update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="更新数据"/>

    <Button
        android:id="@+id/btn_delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除数据"/>

</LinearLayout>

然后修改 MainActivity 中的代码如下:

public class MainActivity extends AppCompatActivity {

    private String newId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 添加数据
        Button btn_add_data = (Button) findViewById(R.id.btn_add_data);
        btn_add_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.provider/book");
                ContentValues values = new ContentValues();
                values.put("name", "第一行代码");
                values.put("author", "郭霖");
                values.put("pages", 1000);
                values.put("price", 66.66);
                Uri newUri = getContentResolver().insert(uri,values);
                newId = newUri.getPathSegments().get(1);
            }
        });

        // 查询数据
        Button btn_query_data = (Button) findViewById(R.id.btn_query_data);
        btn_query_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.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 auhtor = cursor.getString(cursor.getColumnIndex("auhtor"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        double price = cursor.getDouble(cursor.getColumnIndex("price"));

                        Log.d("MainActivity", "书名: " + name);
                        Log.d("MainActivity", "作者: " + auhtor);
                        Log.d("MainActivity", "页数: " + pages);
                        Log.d("MainActivity", "价格: " + price);
                    }
                }
                cursor.close();
            }
        });

        // 更新数据
        Button btn_update_data = (Button) findViewById(R.id.btn_update_data);
        btn_update_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.provider/book/"
                        + newId);
                ContentValues values = new ContentValues();
                values.put("name", "第二行代码");
                values.put("pages", 2000);
                values.put("price", 88.88);
                getContentResolver().update(uri,values,null,null);
            }
        });

        // 删除数据
        Button btn_delete_data = (Button) findViewById(R.id.btn_delete_data);
        btn_delete_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.wonderful.myfirstcode.chapter7.provider/book/" 
                        + newId);
                getContentResolver().delete(uri,null,null);
            }
        });
    }
}

代码很简单,不做解释了,现在运行一下项目,如下界面:

ProviderTest 主界面

点击 添加数据 按钮,此时数据就添加到上一章项目的数据库中了,点击 查询数据 按钮,打印日志如下:

查询添加的数据

然后点击 更新数据 按钮,再次点击 查询数据 按钮查看打印日志如下:

查询更新后的数据

最后点击 删除数据 按钮,此时数据就没了,点击 查询数据 按钮也就查询不到数据了。

以上,跨程序共享数据功能成功实现了。

与内容提供器相关内容就介绍到这。下篇文章将进入手机多媒体的学习。

推荐阅读更多精彩内容