前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android进程间通信(五):进程间通信的方式之ContentProvider

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

作者头像
103style
发布2022-12-19 13:47:41
5990
发布2022-12-19 13:47:41
举报
文章被收录于专栏:Android开发经验分享

转载请以链接形式标明出处: 本文出自: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线程池 中。

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

代码语言:javascript
复制
//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 中注册 并 声明权限:

代码语言:javascript
复制
<?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>

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

代码语言:javascript
复制
//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进程,日志信息如下:

代码语言:javascript
复制
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 来实现访问 日程安排 的功能。 首先我们来创建保存数据用的数据库。

创建数据库
代码语言:javascript
复制
//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 如下:

代码语言:javascript
复制
//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 中进行数据操作:

代码语言:javascript
复制
//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 +
                '}';
    }
}

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

代码语言:javascript
复制
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 ,然后实现对应的方法 进行对应的数据操作就好。


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

以上


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 简介
  • 自定义ContentProvider
    • 创建数据库
    • 小结
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档