Android进程间通信(五):进程间通信的方式之ContentProvider

ContentProvider进阶

转载请以链接形式标明出处:
本文出自:103style的博客

《Android开发艺术探索》 学习记录

base on AndroidStudio 3.5.1


目录

  • 简介
  • 自定义ContentProvider
  • 小结

简介

前面我们介绍了:
进程间通信基础介绍
通过AIDL介绍Binder的工作机制
通过 Bundle、文件共享、Messenger实现进程间通信
进程间通信的方式之AIDL

本文主要介绍进程间通信的方式之 ContentProvider。

ContentProvider 是 Android 中提供的专门用于不同应用间进行数据共享的方式,从这一点来看,他天生就适合进程间通信。
ContentProvider 的底层实现同样也是 Binder,不过使用 比AIDL简单。由于系统已经封装好了,我们可以很轻松实现IPC。
不过还是有很多需要注意的细节,比如 CRUD操作放SQL注入权限控制 等。

系统预置了很多 ContentProvider,像通讯录信息、日程表信息等,访问这些信息只需要用 ContentResolverqueryupdateinsertdelete 方法即可。

接下来我们实现一个自定义的 ContentProvider。


自定义ContentProvider

自定义 ContentProvider 很简单,我们只要继承 ContentProvider,然后实现六个抽象方法就好了。
onCreate() 代表ContentProvider的创建,getType(...) 用来返回一个Uri请求所对应的 MIME类型,如果我们不关心这个可以直接返回 null 或者 */*query(...)insert(...)delete(...)update(...) 即为增删改查的实现。

根据Binder的原理,我们知道这些方法都运行在 ContentProvider 进程中,onCreate 方法由系统回调并运行在 主线程 里,其他五个方法则运行在 Binder线程池 中。

我们先看如下示例,虽然啥也没干,但是它也是可以工作的:

//TestProvider.java
public class TestProvider extends ContentProvider {
    private static final String TAG = "TestProvider";
    @Override
    public boolean onCreate() {
        Log.e(TAG, "onCreate, thread = " + Thread.currentThread().getName());
        return false;
    }
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG, "getType");
        return null;
    }
    @Override
    public Cursor query(...) {
        Log.e(TAG, "query, thread = " + Thread.currentThread().getName());
        return null;
    }
    @Override
    public Uri insert(...) {
        Log.e(TAG, "insert");
        return null;
    }
    @Override
    public int delete(...) {
        Log.e(TAG, "delete");
        return 0;
    }
    @Override
    public int update(...) {
        Log.d(TAG, "update");
        return 0;
    }
}

AndroidManifest.xml 中注册 并 声明权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <permission
        android:name="com.test.cp.PROVIDER"
        android:protectionLevel="normal" />
    <uses-permission android:name="com.test.cp.PROVIDER" />
    <application ...>
        ....
        <provider
            android:name="cp.TestProvider"
            android:authorities="com.test.cp.provider"
            android:permission="com.test.cp.PROVIDER"
            android:process=":provider" />
    </application>
</manifest>

然后在应用默认进程是访问,进行三次查询操作:

//MainActivity.java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //参数为 “content://authorities”  authorities为manifest中注册的
        Uri uri = Uri.parse("content://com.test.cp.provider");
        getContentResolver().query(uri, null, null, null, null);
        getContentResolver().query(uri, null, null, null, null);
        getContentResolver().query(uri, null, null, null, null);
    }
}

运行程序,切换到 :provider进程,日志信息如下:

TestProvider: onCreate, thread = main
TestProvider: query, thread = Binder:2588_3
TestProvider: query, thread = Binder:2588_2
TestProvider: query, thread = Binder:2588_2

我们可以看到 onCreate 是运行在 UI线程,所以我们不能进行耗时操作。
三次查询操作则运行在不同的非UI线程中。

接下来我们来完善 TestProvider 来实现访问 日程安排 的功能。
首先我们来创建保存数据用的数据库。

创建数据库
//DbHelper.java
public class DbHelper extends SQLiteOpenHelper {
    public static final String TABLE_NAME = "todo";
    private static final String DB_NAME = "todo.db";
    private static final int VERSION = 1;
    private String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME
            + "(_id INTEGER PRIMARY KEY, title TEXT, priority INT)";
    public DbHelper(Context context) {
        super(context, DB_NAME, null, VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_SQL);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

完善 TestProvider 如下:

//TestProvider.java
public class TestProvider extends ContentProvider {
    public static final String AUTH = "com.test.cp.provider";
    public static final String TODO_URI = "content://" + AUTH + "/todo";
    public static final int TODO_CODE = 1;
    private static final String TAG = "TestProvider";
    private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        URI_MATCHER.addURI(AUTH, "todo", TODO_CODE);
    }
    private SQLiteDatabase sqLiteDatabase;
    private Context mContext
    @Override
    public boolean onCreate() {
        Log.e(TAG, "onCreate, thread = " + Thread.currentThread().getName());
        mContext = getContext();
        DbHelper dbHelper = new DbHelper(mContext);
        sqLiteDatabase = dbHelper.getWritableDatabase();
        initData();
        return false;
    }
    private void initData() {
        sqLiteDatabase.execSQL("insert into todo values(1,'buy computer',10);");
        sqLiteDatabase.execSQL("insert into todo values(2,'install androidstudio',5);");
    }
    public String getTableName(Uri uri) {
        if (URI_MATCHER.match(uri) == TODO_CODE) {
            return DbHelper.TABLE_NAME;
        } else {
            throw new IllegalArgumentException("illegal uri = " + uri);
        }
    }
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG, "getType");
        return null;
    }
    @Override
    public Cursor query(...) {
        Log.e(TAG, "query, thread = " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        return sqLiteDatabase.query(tableName, projection, selection, selectionArgs,
                null, null, sortOrder, null);
    }
    @Override
    public Uri insert(...) {
        Log.e(TAG, "insert");
        String tableName = getTableName(uri);
        sqLiteDatabase.insert(tableName, null, values);
        notify(uri);
        return null;
    }
    @Override
    public int delete(...) {
        Log.e(TAG, "delete");
        String tableName = getTableName(uri);
        int count = sqLiteDatabase.delete(tableName, selection, selectionArgs);
        if (count > 0) {
            notify(uri);
        }
        return count;
    }
    @Override
    public int update(...) {
        Log.e(TAG, "update");
        String tableName = getTableName(uri);
        int row = sqLiteDatabase.update(tableName, values, selection, selectionArgs);
        if (row > 0) {
            notify(uri);
        }
        return row;
    }
    private void notify(Uri uri) {
        mContext.getContentResolver().notifyChange(uri, null);
    }
}

需要注意的是 增删改查四个方法是并发访问的,所以我们正确处理多线程的问题。
示例只有一个数据库连接,所以是没有问题的。
不过如果 ContentProvider 的底层数据是一块内存的话,例如 List,对其进行数据操作就得进行线程同步了。

在 MainActivity 中进行数据操作:

//MainActivity.java
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Uri uri = Uri.parse(TestProvider.TODO_URI);
        ContentValues values = new ContentValues();
        values.put("title", "read Android艺术开发探索");
        values.put("priority", 5);
        getContentResolver().insert(uri, values);
        query(uri);
        getContentResolver().delete(uri, "priority=10", null);
        query(uri);
    }
    private void query(Uri uri) {
        Cursor cursor = getContentResolver().query(uri, new String[]{"_id", "title", "priority"}, null, null, null);
        if (cursor == null) {
            return;
        }
        while (cursor.moveToNext()) {
            Todo todo = new Todo();
            todo._id = cursor.getInt(0);
            todo.title = cursor.getString(1);
            todo.priority = cursor.getInt(2);
            Log.e(TAG, "query todo: " + todo.toString());
        }
        cursor.close();
    }
}
//Todo.java
public class Todo {
    public int _id;
    public String title;
    public int priority;
    @Override
    public String toString() {
        return "Todo{" +
                "_id=" + _id +
                ", title='" + title + '\'' +
                ", priority=" + priority +
                '}';
    }
}

运行程序,得到日志信息如下:

TestProvider: onCreate, thread = main
TestProvider: insert
TestProvider: query, thread = Binder:6004_3
MainActivity: query todo: Todo{_id=1, title='buy computer', priority=10}
MainActivity: query todo: Todo{_id=2, title='install androidstudio', priority=5}
MainActivity: query todo: Todo{_id=3, title='read Android艺术开发探索', priority=5}
TestProvider: delete
TestProvider: query, thread = Binder:6004_3
MainActivity: query todo: Todo{_id=2, title='install androidstudio', priority=5}
MainActivity: query todo: Todo{_id=3, title='read Android艺术开发探索', priority=5}

从以上日志我们可以看到进行了插入、查询、删除、查询操作,说明 TestProvider 已经能正确的处理外部请求了。


小结

这里我们通过自定义一个 ContentProvider 来介绍使用 ContentProvider 进行 IPC,
可以看到使用ContentProvider进行IPC非常简单,只需要继承 ContentProvider ,然后实现对应的方法 进行对应的数据操作就好。


如果觉得不错的话,请帮忙点个赞呗。

以上


扫描下面的二维码,关注我的公众号 Android1024, 点关注,不迷路。
Android1024

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值