十三、 Android 中的 IPC 方式(5) --- 使用 Android 四大组件之一 ContentProvider(内容提供器)

    ContentProvider 是 Android 中的四大组件之一,同时也是 Android 系统中的 IPC 方式之一。ContentProvider 的使用一般有两种情况:

    1. 使用现有的内容提供器来读取和操作相应程序中的数据。比如我们的应用通过 ContentProvider 访问 Android 系统自带的通讯录应用,并获取通讯录中的联系人信息。

    2. 我们在应用中自定义一个内容提供器并提供外部访问接口,供别的应用获取我们提供的数据。

    ContentProvider 的使用:

    1. 访问内容提供器中共享的数据,关键就是使用 ContentResolver 类。所以首先就要获取该类的实例:

// 通过 Context 中的 getContentResolver()
ContentResolver contentResolver = getContentResolver();

    2. 使用 ContentResolver 类中提供的 insert()、update()、delete()、query() 方法对数据进行 CRUD 操作。

// 添加数据
public final Uri insert(Uri url, ContentValues values) {
   ...
}

// 更新数据
public final int update(Uri uri, ContentValues values, String where, String[] selectionArgs) {
    ...
}

// 删除数据
public final int delete(Uri url, String where, String[] selectionArgs) {
    ...
}

// 查询数据
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, tring sortOrder) {
    ...
}

    Uri 参数解释:

    我们可以看到上面的增删查改方法都需要接收一个 Uri 参数。这个参数被称为内容 URI。它为内容提供器中的数据建立了唯一标识符。它主要由两部分组成: authority 和 path。authority 是用于对不同的应用程序做区分的,一般都采用该应用程序的包名作为前缀。path 则用于对同一应用程序中不同的表做区分的,通常都会添加到 authority 的后面。由于是内容 URI,所以通常我们还会在它们的最前面加上 "content://" 这样的协议声明,所以内容 URI 标准的格式写法如下:

// 通过命名我们就可以看出我们要访问的是包名为 com.cfm.contentprovider 的应用,内容提供器名为 provider 中的表 table1 和 表 table2 中的数据
content://com.cfm.contentprovider.provider/table1
content://com.cfm.contentprovider.provider/table2

    除此之外,我们还可以在内容 URI 后面加上一个 id:

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

// 通配符 "*" 表示匹配任意长度的任意字符,所以下面这个就表示匹配指定应用中的任意表的内容 URI
content://com.cfm.contentprovider.provider/*

// 通配符 "#" 表示匹配任意长度的数字,所以下面这个就表示匹配指定应用中的 table1 表中的任意一行数据的内容 URI
content://com.cfm.contentprovider.provider/table1/#

    上面说的是内容 URI,接着我们还需要将它解析成 Uri 对象才可以作为参数传输,解析的方法也很简单,使用 Uri.parse() 即可:

Uri uri = Uri.parse("content://com.cfm.contentprovider.provider/table1");

    ContentValues 参数解释:

public final class ContentValues implements Parcelable { ... }

    可以看到该类实现了 Parcelable 接口,所以可以跨进程传输。它的作用就是封装我们待操作的数据,然后跨进程对内容提供器中的数据进行相应的操作。使用如下:

ContentValues values = new ContentValues();
values.put("key1", "value1");
values.put("key2", "value2");
contentResolver.insert(uri, values);  // 向目标内容提供器中插入数据

    query() 方法参数解释:

    uri:指定查询某个应用程序下的某一张表。

    projection:指定查询的列名。null 表示查询所有列。

    selection:指定 where 的约束条件。

    selectionArgs:为 where 中的占位符提供具体的值。

    sortOrder:指定查询结果的排序方式。

    该方法返回的是一个 Cursor 对象,然后我们通过该对象可以逐个将数据读取出来。

eg:

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

eg1: 使用现成的内容提供器,并访问它里面的数据:

下面看一个实例,读取系统通讯录中联系人:

MainActivity.java:

package com.cfm.contentprovidertest;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "cfmtest";
    private ArrayAdapter<String> mAdapter;
    // 存储读取到的联系人信息
    private List<String> mContactsList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ListView contactView = findViewById(R.id.contacts_view);
        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, mContactsList);
        contactView.setAdapter(mAdapter);

        // 动态申请读取联系人权限
        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 phoneNum = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

                    // 添加到 list 容器中,然后显示出来
                    mContactsList.add(name + "\n" + phoneNum);
                    Log.d(TAG, "Name: " + name + " ,Numer: " + phoneNum);
                }
                mAdapter.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, "权限不允许!", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}

activity_main.xml:

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

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

</LinearLayout>

AndroidManifest.xml:

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

Log 打印信息:

2019-05-23 21:21:07.056 28987-28987/com.cfm.contentprovidertest D/cfmtest: Name: 匪警 ,Numer: 11011 0
2019-05-23 21:21:07.057 28987-28987/com.cfm.contentprovidertest D/cfmtest: Name: 火警 ,Numer: 11911 9
2019-05-23 21:21:07.058 28987-28987/com.cfm.contentprovidertest D/cfmtest: Name: 急救中心 ,Numer: 12012 0

如何自己创建一个内容提供器:

1. 新建一个继承自 ContentProvider 的类,并实现 ContentProvider 类中 6 个抽象方法:

package com.cfm.customcontentprovidertest;

public class MyProvider extends ContentProvider {
    /**
     * 初始化内容提供器,通常在这里完成对数据库的创建升级等操作,
     * 返回 true 表示内容提供器初始化成功,返回 false 则表示失败。
     * 这个方法运行在主线程中,所以这里不能执行耗时操作,其余五个
     * 方法运行在 Binder 线程池中。
     * */
    @Override
    public boolean onCreate() {
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}

    getType() 方法解释:

    它的作用是根据传入的内容 URI 来返回相应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由 3 部分组成,Android 对这 3 个部分做了如下格式规定:

    1. 必须以 vnd 开头。

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

    3. 最后接上: vnd.<authority>.<path>。

    eg:

// 结合上面 getType() 方法的解释
内容 URI 为: content://com.cfm.contentprovider.provider/table1,对应的 MIME 类型为:
vnd.android.cursor.dir/vnd.com.cfm.contentprovider.provider/table1

内容 URI 为: content://com.cfm.contentprovider.provider/table1/1,对应的 MIME 类型为:
vnd.android.cursor.item/vnd.com.cfm.contentprovider.provider/table1/1

2. 借助 UriMatcher 这个类完成匹配请求端传过来的内容 URI,然后返回相应的数据,主要有两个方法 addURI() 和 match():

// 这个方法接收 3 个参数,可以分别把 authority、path 和 一个自定义参数传进去。
// 当调用 UriMatcher.match() 方法时,就可以将一个 对象传入,然后返回值是
// 某个能够匹配这个 Uri 对象所定义的那个自定义参数的值。利用这个参数的值我们
// 就可以判断出调用方期望访问的是哪张表中的数据了。
UriMatcher.addURI(); 

3. 实现完整自定义的 ContentProvider:

package com.cfm.customcontentprovidertest;

public class MyProvider extends ContentProvider {

    // UriMatcher.addURI() 方法中自定义的参数
    public static final int TABLE1_DIR = 0;  // 返回 TABLE1 的整张表
    public static final int TABLE1_ITEM = 1; // 返回 TABLE1 的表中某个 id 的数据(也就是某一项)
    public static final int TABLE2_DIR = 2;  // 返回 TABLE2 的整张表
    public static final int TABLE2_ITEM = 3; // 返回 TABLE2 的表中某个 id 的数据(也就是某一项)
    private static UriMatcher sUriMatcher;

    static {
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sUriMatcher.addURI("com.cfm.contentprovider", "table1", TABLE1_DIR);
        sUriMatcher.addURI("com.cfm.contentprovider", "table1/#", TABLE1_ITEM);
        sUriMatcher.addURI("com.cfm.contentprovider", "table2", TABLE2_DIR);
        sUriMatcher.addURI("com.cfm.contentprovider", "table2/#", TABLE2_ITEM);
    }

    ...

    @Override
    public String getType(Uri uri) {
        Log.d(TAG, "getType, current Thread: " + Thread.currentThread().getName());
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.cfm.customcontentprovidertest.bookprovider.book";

            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.cfm.customcontentprovidertest.bookprovider.book";

            case USER_DIR:
                return "vnd.android.cursor.dir/vnd.com.cfm.customcontentprovidertest.bookprovider.user";

            case USER_ITEM:
                return "vnd.android.cursor.item/vnd.com.cfm.customcontentprovidertest.bookprovider.user";
        }
        return null;
    }
}

    最后注意,当请求端请求的是 TABLE1_ITEM 和 TABLE2_ITEM 类型的单条数据时,我们可以使用 Uri.getPathSegments() 方法,它会将内容 URI 权限之后的部分以 "/" 符号进行分割,并把分割后的结果放入到一个字符串列表中,所以我们要想获得它请求访问哪一个 id,可以这样使用:

String id = uri.getPathSegments().get(1);

 

自定义内容提供器实例:

DbOpenHelper.java:(要操作的数据库)

package com.cfm.customcontentprovidertest;

public class DbOpenHelper extends SQLiteOpenHelper {

    // 数据库名称为 "book_provider.db" ; 里面包含两个表: "book"、"user"
    private static final String DB_NAME = "book_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

    private static final int DB_VERSION = 1;

    // 创建表 "book"
    private String CREATE_BOOK_TABLE = "create table " + BOOK_TABLE_NAME + " (" +
            "id integer primary key autoincrement, "+ "name text)";

    // 创建表 "user"
    private String CREATE_USER_TABLE = "create table " + USER_TABLE_NAME + " (" +
            "id integer primary key autoincrement, "+ "name text, dream text)";

    public DbOpenHelper(Context context){
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

BookProvider.java:(自定义的内容提供器)

package com.cfm.customcontentprovidertest;

public class BookProvider extends ContentProvider {
    private static final String TAG = "cfmtest";

    // UriMatcher.addURI() 方法中自定义的参数
    public static final int BOOK_DIR = 0;  // 返回 BOOK 的整张表
    public static final int BOOK_ITEM = 1; // 返回 BOOK 的表中某个 id 的数据(也就是某一项)
    public static final int USER_DIR = 2;  // 返回 USER 的整张表
    public static final int USER_ITEM = 3; // 返回 USER 的表中某个 id 的数据(也就是某一项)
    public static final String AUTHORITY = "com.cfm.customcontentprovidertest.bookprovider";
    private static UriMatcher sUriMatcher;
    private DbOpenHelper dbHelper;

    static {
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        sUriMatcher.addURI(AUTHORITY, "user", USER_DIR);
        sUriMatcher.addURI(AUTHORITY, "user/#", USER_ITEM);
    }

    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate, current Thread: " + Thread.currentThread().getName());
        dbHelper = new DbOpenHelper(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.d(TAG, "query, current Thread: " + Thread.currentThread().getName());
        // 查询数据
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (sUriMatcher.match(uri)) {
            // 这里是使用了 SQLite 数据库的查询
            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 USER_DIR:
                cursor = db.query("user", projection, selection, selectionArgs, null, null, sortOrder);
                break;

            case USER_ITEM:
                String userId = uri.getPathSegments().get(1);
                cursor = db.query("user", projection, "id = ?", new String[]{ userId }, null, null, sortOrder);
                break;

            default:
                break;
        }
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.d(TAG, "insert, current Thread: " + Thread.currentThread().getName());

        // 添加数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (sUriMatcher.match(uri)) {
            // 这里是使用了 SQLite 数据库的插入
            case BOOK_DIR:
            case BOOK_ITEM:
                long newBookId = db.insert("book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;

            case USER_DIR:
            case USER_ITEM:
                long newUserId = db.insert("user", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/user/" + newUserId);
                break;

            default:
                break;
        }
        return uriReturn;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.d(TAG, "delete, current Thread: " + Thread.currentThread().getName());

        // 删除数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deletedRows = 0;
        switch (sUriMatcher.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 USER_DIR:
                deletedRows = db.delete("user", selection, selectionArgs);
                break;

            case USER_ITEM:
                String userId = uri.getPathSegments().get(1);
                deletedRows = db.delete("user", "id = ?", new String[]{ userId });
                break;

            default:
                break;
        }
        return deletedRows;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.d(TAG, "update, current Thread: " + Thread.currentThread().getName());
        // 更新数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (sUriMatcher.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 USER_DIR:
                updatedRows = db.update("user", values, selection, selectionArgs);
                break;

            case USER_ITEM:
                String userId = uri.getPathSegments().get(1);
                updatedRows = db.update("user", values, "id = ?", new String[]{userId});
                break;

            default:
                break;
        }
        return updatedRows;
    }

    @Override
    public String getType(Uri uri) {
        Log.d(TAG, "getType, current Thread: " + Thread.currentThread().getName());
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIR:
                return "vnd.android.cursor.dir/vnd.com.cfm.customcontentprovidertest.bookprovider.book";

            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.cfm.customcontentprovidertest.bookprovider.book";

            case USER_DIR:
                return "vnd.android.cursor.dir/vnd.com.cfm.customcontentprovidertest.bookprovider.user";

            case USER_ITEM:
                return "vnd.android.cursor.item/vnd.com.cfm.customcontentprovidertest.bookprovider.user";
        }
        return null;
    }
}

ProviderActivity.java:(远程客户端)

package com.cfm.customcontentprovidertest;

public class ProviderActivity extends AppCompatActivity {
    private static final String TAG = "cfmtest";
    private String newBookId;
    private String newUserId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        Button addData = findViewById(R.id.add_data);
        Button queryData = findViewById(R.id.query_data);
        Button updateData = findViewById(R.id.update_data);
        Button deleteData = findViewById(R.id.delete_data);

        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 添加数据
                Uri uri1 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/book");
                Uri uri2 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/user");

                ContentValues values1 = new ContentValues();
                values1.put("name", "Android");

                ContentValues values2 = new ContentValues();
                values2.put("name", "cfm");
                values2.put("dream", "world peace!");

                Uri newBookUri = getContentResolver().insert(uri1, values1);
                Uri newUserUri = getContentResolver().insert(uri2, values2);

                if (newBookUri != null) {
                    newBookId = newBookUri.getPathSegments().get(1);
                }

                if (newUserUri != null) {
                    newUserId = newUserUri.getPathSegments().get(1);
                }
            }
        });

        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 查询数据
                Uri uri1 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/book");
                Uri uri2 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/user");
                Cursor cursor1 = getContentResolver().query(uri1, null, null, null, null);
                Cursor cursor2 = getContentResolver().query(uri2, null, null, null, null);

                if(cursor1 != null){
                    while (cursor1.moveToNext()){
                        String bookName = cursor1.getString(cursor1.getColumnIndex("name"));
                        Log.d(TAG, "书名: " + bookName);
                    }
                    cursor1.close();
                }

                if(cursor2 != null){
                    while (cursor2.moveToNext()){
                        String userName = cursor2.getString(cursor2.getColumnIndex("name"));
                        String userDream = cursor2.getString(cursor2.getColumnIndex("dream"));
                        Log.d(TAG, "姓名: " + userName + " ,梦想: " + userDream);
                    }
                    cursor2.close();
                }
            }
        });

        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 更新数据
                Uri uri1 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/book/" + newBookId);
                Uri uri2 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/user/" + newUserId);

                ContentValues values1 = new ContentValues();
                values1.put("name", "New Android");
                getContentResolver().update(uri1, values1, null, null);

                ContentValues values2 = new ContentValues();
                values2.put("name", "ym");
                values2.put("dream", "New World Peace!");
                getContentResolver().update(uri2, values2, null, null);
            }
        });

        deleteData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 删除数据
                Uri uri1 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/book/" + newBookId);
                Uri uri2 = Uri.parse("content://com.cfm.customcontentprovidertest.bookprovider/user/" + newUserId);
                getContentResolver().delete(uri1, null, null);
                getContentResolver().delete(uri2, null, null);
            }
        });
    }
}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.cfm.customcontentprovidertest">

    <uses-permission android:name="com.cfm.PROVIDER"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ProviderActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <provider
            android:authorities="com.cfm.customcontentprovidertest.bookprovider"
            android:name=".BookProvider"
            android:permission="com.cfm.PROVIDER"
            android:process=":remote"/>
    </application>

</manifest>

Log 打印信息:

// 按 addButton
2019-05-24 22:33:05.419 31980-31980/com.cfm.customcontentprovidertest:remote D/cfmtest: onCreate, current Thread: main
2019-05-24 22:33:05.428 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: insert, current Thread: Binder:31980_3
2019-05-24 22:33:05.478 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: insert, current Thread: Binder:31980_3

// 按 queryButton
2019-05-24 22:33:20.937 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: query, current Thread: Binder:31980_3
2019-05-24 22:33:20.949 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: query, current Thread: Binder:31980_3
2019-05-24 22:33:20.956 31909-31909/com.cfm.customcontentprovidertest D/cfmtest: 书名: Android
2019-05-24 22:33:20.959 31909-31909/com.cfm.customcontentprovidertest D/cfmtest: 姓名: cfm ,梦想: world peace!

// 按 updateButton
2019-05-24 22:33:50.663 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: update, current Thread: Binder:31980_3
2019-05-24 22:33:50.675 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: update, current Thread: Binder:31980_3

// 按 queryButton
2019-05-24 22:34:05.207 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: query, current Thread: Binder:31980_3
2019-05-24 22:34:05.223 31980-31994/com.cfm.customcontentprovidertest:remote D/cfmtest: query, current Thread: Binder:31980_3
2019-05-24 22:34:05.230 31909-31909/com.cfm.customcontentprovidertest D/cfmtest: 书名: New Android
2019-05-24 22:34:05.233 31909-31909/com.cfm.customcontentprovidertest D/cfmtest: 姓名: ym ,梦想: New World Peace!

    这里我们需要注意,query、update、insert、delete 四大方法是存在多线程并发访问的,所以我们在该方法内部要做好线程同步。我们这里使用的是 SQLite 并且只有一个 SQLiteDatabase 的连接,所以可以正确应对多线程的情况。这是因为在 SQLiteDatabase 内部对数据库的操作有同步处理,但是如果通过多个 SQLiteDatabase 对象来操作数据库就无法保证线程同步,因为 SQLiteDatabase 对象之间无法进行线程同步。如果 ContentProvider 的底层数据集是一块内存的话,比如 List,在这种情况下操作 List 的遍历、插入、删除操作就需要进行线程同步,否则就会引发并发错误。

    ContentProvider 除了支持对数据源的增删改查四个操作,还支持自定义调用,这个过程通过 ContentResolver 的 Call 方法和 ContentProvider 的 Call 方法来完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值