【IPC实战③】ContentProvider实战

本文介绍了ContentProvider作为Android进程间通信的重要方式,通过提供标准的数据操作接口实现数据共享。详细讲解了如何创建Book实体类、数据库、ContentProvider以及客户端的编写,同时也提到了ContentObserver的使用,最后给出了项目链接和相关文章推荐。

前言

之前只知道ContentProvider是Android进程间通信的一种方式,最近才做了一个具体的学习,特此记录

ContentProvider也是AndroidIPC的一种很重要的方式,和Messenger一样,天生就适合进程间通信。同样的,它使用起来也比AIDL要简单很多,因为都是对Binder做了一层封装。

1.概念理解

使用ContentProvider,其实是提供了一套标准的数据操作接口给外界。外界可以通过这一整套接口(具体就是实现CRUD的功能的接口),实现数据之间的共享,从而实现进程间通信。至于具体操作的数据源是什么,比如是本地数据库,还是文件,还是网络获取,外界并不需要关心,这是ContentProvider内部的事情。

2.具体使用

①创建Book实体类
/**
 * Created by didiwei on 2022/5/15
 * desc: Book实体类
 */
public class Book {
    int bookId;
    String bookName;

    public Book(){

    }

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                '}';
    }
}

②创建数据库,作为ContentProvider操作的数据来源
/**
 * Created by didiwei on 2022/5/15
 * desc:  数据库
 */
public class DatabaseHelper extends SQLiteOpenHelper {
    private static final int DB_VERSION = 1;//数据库版本号
    private static final String DB_NAME = "book_provider.db";//数据库名称
    public static final String BOOK_TABLE_NAME = "book";//book表
    public static final String USER_TABLE_NAME = "user";//user表

    //创建book表和user表
    private String CREATE_BOOK_TABLE = "create table if not exists "+ BOOK_TABLE_NAME +
            "(_id integer primary key," + "name text)";
    private String CREATE_USER_TABLE = "create table if not exists " + USER_TABLE_NAME +
            "(_id integer primary key," + "name text," + "sex int)";

    //创建默认构造方法
    public DatabaseHelper(Context context){
        super(context,DB_NAME,null,DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建book表和user表
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

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

    }
}
③创建ContentProvider,即BookProvider

同时让它运行在一个独立的进程中
在这里插入图片描述
其中,
(1)authorities是ContentProvider的唯一标识。
(2)外界应用如果想访问BookProvider,就必须声明android:permission="com.example.PROVIDER"这个权限

/**
 * todo query 参数相对应的 sql语句
 *
 * String[] projection = {
 *     ContactsContract.Contacts._ID,
 *     ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
 *     ContactsContract.CommonDataKinds.Phone.NUMBER
 * };
 *
 * String selectionClause = ContactsContract.CommonDataKinds.Phone.NUMBER + " = ?";
 *
 * String[] selectionArgs = {"123456"};
 *
 * getContentResolver().query(uri, projection, selectionClause, selectionArgs, "sort_key COLLATE LOCALIZED asc");
 *
 * todo -----------------------------------------
 *
 * 上面的代码 类似于下面的sql语句
 * SELECT _ID, displayName, number FROM uri WHERE number = "123456" ORDER BY sort_key COLLATE LOCALIZED asc
 */

/**
 * Created by didiwei on 2022/5/15
 * desc: ContentProvider实体类
 *
 * todo 其实ContentProvider不一定操作数据库,还可以是文件,网络等。它只暴露一套CRUD的接口给外界,至于数据源具体从哪来,外界不需要管
 * 只是在这里为了方便演示,就以ContentProvider操作数据库为例。
 */
public class BookProvider extends ContentProvider {
    SQLiteDatabase mDb;//数据库

    public static final String AUTHORITY = "com.example.provider";//ContentProvider唯一标识

    //分别操作Book表和User表的uri
    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
    public static final int BOOK_URI_CODE = 0;
    public static final int USER_URI_CODE = 1;

    //UriMatcher
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        uriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
        uriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
    }

    @Override
    public boolean onCreate() {
        Log.v("ljh","onCreate,current thread:" + Thread.currentThread().getName());
        //实际项目中,不建议在onCreate中进行耗时的数据库操作
        initProviderData();
        return false;
    }

    //进行数据库的初始化操作
    public void initProviderData(){
        mDb = new DatabaseHelper(getContext()).getWritableDatabase();
        mDb.execSQL("delete from " + DatabaseHelper.BOOK_TABLE_NAME);
        mDb.execSQL("delete from " + DatabaseHelper.USER_TABLE_NAME);
        mDb.execSQL("insert into book values(3,'Android');");
        mDb.execSQL("insert into book values(4,'Ios');");
        mDb.execSQL("insert into book values(5,'Html');");
        mDb.execSQL("insert into user values(1,'jake',1);");
        mDb.execSQL("insert into user values(2,'Android',0);");
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.v("ljh","query,current thread:" + Thread.currentThread().getName());

        //首先根据uri得到表的名称
        String tableName = getTableName(uri);
        if(tableName.equals("")){
           throw new IllegalArgumentException("Unsupported URI:" + uri);
        }

        return mDb.query(tableName,projection,selection,selectionArgs,null,null,sortOrder,null);
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.v("ljh","getType,current thread:" + Thread.currentThread().getName());
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.v("ljh","insert,current thread:" + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if(tableName.equals("")){
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }

        mDb.insert(tableName,null,values);

        getContext().getContentResolver().notifyChange(uri,null);//通知外界当前ContentProvider已经改变
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.v("ljh","delete,current thread:" + Thread.currentThread().getName());

        String tableName = getTableName(uri);
        if(tableName.equals("")){
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }

        int count = mDb.delete(tableName,selection,selectionArgs);
        if(count > 0){
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return count;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.v("ljh","update,current thread:" + Thread.currentThread().getName());

        String tableName = getTableName(uri);
        if(tableName.equals("")){
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }

        int row = mDb.update(tableName,values,selection,selectionArgs);
        if(row > 0){
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return row;
    }

    //得到要操作的表的名字
    private String getTableName(Uri uri){
        String tableName = "";
        switch (uriMatcher.match(uri)){
            case BOOK_URI_CODE:
                //操作的是Book表
                tableName = DatabaseHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                //操作的是User表
                tableName = DatabaseHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }

        return tableName;
    }

}

④(可选)创建ContentObserver

看名字也能看出来,ContentObserver是Content的观察者。当有人对相应uri的数据进行更改的时候,就会触发ContentObserver

/**
 * Created by didiwei on 2022/5/15
 * desc: 监听ContentProvider中指定Uri标识数据的变化
 *
 * 参考文章:https://www.cnblogs.com/longjunhao/p/8926858.html
 */
public class MyContentObserver {
    public static final int CONTENTPROVIDER_ONCHANGE = 1;
    Handler handler;
    Context context;

    MyContentObserver(Context context,Handler handler){
        this.handler = handler;
        this.context = context;
    }

    public final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange, @Nullable Uri uri) {
            //selfChange一般为false
            Log.v("ljh","MyContentObserver");

            handler.obtainMessage(CONTENTPROVIDER_ONCHANGE,"这里是来自onChange方法的Message").sendToTarget();
        }
    };
}

⑤写客户端,即操作ContentProvider的角色
public class MainActivity extends AppCompatActivity {
    //ContentObserver
    MyContentObserver myContentObserver;

    //Handler
    Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case MyContentObserver.CONTENTPROVIDER_ONCHANGE:
                    Log.v("ljh","这里是MainActivity里面的Handler,传来的Msg的obj为" + (String)msg.obj);
                    break;
                default:
                    break;
            }
        }
    };

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

        //得到book表
        Uri bookUri = BookProvider.BOOK_CONTENT_URI;
        //创建监听
        myContentObserver = new MyContentObserver(this,handler);
        //为book表注册监听
        //notifyForDescendants 为false 表示精确匹配,即只匹配该Uri ,为true 表示可以同时匹配其派生的Uri
        getContentResolver().registerContentObserver(bookUri,false,myContentObserver.mContentObserver);

        //todo -----------------对book表的insert-----------------
        //为book表insert一条数据
        ContentValues values = new ContentValues();
        values.put("_id",6);
        values.put("name","在MainActivity新增的书");

        getContentResolver().insert(bookUri,values);

        //todo -----------------对book表的query-----------------
        Cursor cursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
        while(cursor.moveToNext()){
            Book book = new Book();
            book.setBookId(cursor.getInt(0));
            book.setBookName(cursor.getString(1));
            Log.v("ljh","query book:" + book);
        }
        cursor.close();


        //为book表解除监听
        getContentResolver().unregisterContentObserver(myContentObserver.mContentObserver);
    }
}
效果

在这里插入图片描述
在这里插入图片描述
注意进程区别

3.项目链接与文章推荐

项目地址:https://github.com/LJHnb666666/ContentProviderDemo
好文章推荐

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值