Android13_IPC方式之ContentProvider
ContentProvider是安卓提供的专门用于不同应用间进行数据共享的方式;它天生适合进程间通信;
和Messenger一样,ContentProvider的底层实现同样也是Binder;
ContentProvider还是四大组件之一;
其实ContentProvider的使用涉及的细节还是很多的;
一般来讲ContentProvider用于对应用的数据库进行增删改查;
我们可以访问跨应用地其他应用提供的ContentProvider,也可以自定义一个ContentProvider;
自己实现ContentProvider要自定义一个类继承自ContentProvider并实现六个抽象方法:onCreate、query、update、insert、delete、getType;
1、首先在Manifest中注册ContentProvider组件:
android:autorities 是作为标识,一般用完整包名加类名
android:permission 表示权限,其他应用想要用这个ContentProvider时,继续声明该权限才可以,否则外界应用会异常终止;
android:process 这里为了演示方便,直接在同一个应用中开启多进程模式,MainActivity一个进程,ContentProvider一个进程;
至于process如何设置在 Android8_安卓的IPC机制 中 的开启多进程模式有介绍到,这里就不展开讨论;https://www.cnblogs.com/grooovvve/p/12462286.html
2、编写DbOpenHelper类
这个类用于管理数据的创建、升级、降级
package com.example.learncontentprovider; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; //利用SQLiteHelper来管理数据库的创建、升级、降级 public class DbOpenHelper extends SQLiteOpenHelper { private static final String TAG = "DbOpenHelper"; 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; //建表语句 图书和用户信息表 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 DbOpenHelper(Context context){ super(context,DB_NAME,null,DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { Log.d(TAG,"onCreate"); db.execSQL(CREATE_BOOK_TABLE); db.execSQL(CREATE_USER_TABLE); } //用于对数据库进行升级的,是否执行该方法要看DbOpenHelper构造函数传入的version是否比之前更大。 //可以尝试第一次运行该程序version为1,第二次运行该程序version为2 //每次运行后用adb工具查看数据库文件创建情况 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.d(TAG,"onUpgrade"); db.execSQL("drop table if exists Book"); db.execSQL("drop table if exists Category"); onCreate(db); } }
3、编写BookProvider组件
package com.example.learncontentprovider; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.util.Log; //用BookeProvider向外界提供数据库中的信息 //ContentProvider通过Uri来分辨外界要访问的数据集合 //为了让外界知道要访问哪个表,需要单独为本例中的book表、user表定义单独的Uri和Uri_Code //将Uri和Uri_Code进行关联 //外界通过Uri访问BookProvider,我们可以根据Uri和Uri_Code的关联,知道外界想要访问哪个表,然后可以进行相应的数据操作 public class BookProvider extends ContentProvider { private static final String TAG = "BookProvider"; private static final String AUTHORITY = "com.example.learncontentprovider.BookProvider"; private static final Uri BOOK_CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/book"); private static final Uri USER_CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/user"); private static final int BOOK_URI_CODE = 0; private static final int USER_URI_CODE = 1; private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //这个Uri和URICode的关联过程是由下面语句来完成的 static{ sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE); sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE); } private SQLiteDatabase mdb; //匹配函数,根据Uri获取要访问的表名 private String getTableName(Uri uri){ String tableName = null; switch (sUriMatcher.match(uri)){ case BOOK_URI_CODE: tableName = DbOpenHelper.BOOK_TABLE_NAME; break; case USER_URI_CODE: tableName = DbOpenHelper.USER_TABLE_NAME; break; default: break; } return tableName; } //onCreate会运行在主线程中,也就是UI线程,所以不能在onCreate中做耗时操作 @Override public boolean onCreate(){ Log.d(TAG,"onCreate, current thread:"+Thread.currentThread().getName()); mdb = new DbOpenHelper(getContext()).getWritableDatabase(); //实际上不适合在主线程进行耗时的数据库操作 initProviderData(); return true; } private void initProviderData(){ mdb.execSQL("delete from "+DbOpenHelper.BOOK_TABLE_NAME); mdb.execSQL("delete from "+DbOpenHelper.USER_TABLE_NAME); mdb.execSQL("insert into book values(3,'Android');"); mdb.execSQL("insert into book values(2,'IOS');"); mdb.execSQL("insert into book values(5,'HTML5');"); mdb.execSQL("insert into user values(1,'jake',1);"); mdb.execSQL("insert into user values(2,'jasmine',0);"); } @Override public String getType(Uri uri) { Log.d(TAG,"getType"); return null; } //增删改查四大方法支持多线程并发访问,因此方法内部要做好线程同步,但是本例中采用的是SQLite并且只有一个SQLiteDatabase连接; //所以可以正确应对多线程情况;原因是SQLiteDatabase内部对数据库的操作有同步处理; //但是对于多个SQLiteDatabase对象来操作数据库就无法保证数据库是一块内存的话,就必须进行线程同步; @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.d(TAG,"query, current thread:"+Thread.currentThread().getName()); String table = getTableName(uri); if(table == null){ throw new IllegalArgumentException("Unsupported URI:"+uri); } Cursor cursor = null; cursor =mdb.query(table,projection,selection,selectionArgs,null,sortOrder,null); return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { Log.d(TAG,"insert"); String table = getTableName(uri); if(table == null){ throw new IllegalArgumentException("Unsupported URI:"+uri); } mdb.insert(table,null,values); //getContext().getContentResolver().notifyChange(uri,null); return uri; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.d(TAG,"delete"); String table = getTableName(uri); if(table == null){ throw new IllegalArgumentException("Unsupported URI:"+uri); } int count = mdb.delete(table,selection,selectionArgs); if(count>0){ getContext().getContentResolver().notifyChange(uri,null); } return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d(TAG,"update"); String table = getTableName(uri); if(table == null){ throw new IllegalArgumentException("Unsupported URI:"+uri); } int row = mdb.update(table,values,selection,selectionArgs); if(row>0){ getContext().getContentResolver().notifyChange(uri,null); } return row; } }
4、编写MainActivity
package com.example.learncontentprovider; import androidx.appcompat.app.AppCompatActivity; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private static final String TAG = "Activity"; public class Book{ int bookId; String bookName; } public class User{ int userId; String usrName; boolean isMale; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Uri bookuri = Uri.parse("content://com.example.learncontentprovider.BookProvider/book"); //唯一标识了BookProvider //以下的三次调用,会运行在不同的线程中 //getContentResolver().query(uri,null,null,null,null); //getContentResolver().query(uri,null,null,null,null); //getContentResolver().query(uri,null,null,null,null); ContentValues values = new ContentValues(); values.put("_id",6); values.put("name","程序设计的艺术"); getContentResolver().insert(bookuri,values); Cursor bookcursor = getContentResolver().query(bookuri,new String[]{"_id","name"},null,null,null); while(bookcursor.moveToNext()){ Book book = new Book(); book.bookId = bookcursor.getInt(0); book.bookName = bookcursor.getString(1); Log.d(TAG,"query book: "+ book.toString()); } bookcursor.close(); Uri useruri = Uri.parse("content://com.example.learncontentprovider.BookProvider/user"); Cursor userCursor = getContentResolver().query(useruri,new String[]{"_id","name","sex"},null,null,null); while(userCursor.moveToNext()){ User user = new User(); user.userId = userCursor.getInt(0); user.usrName = userCursor.getString(1); user.isMale = userCursor.getInt(2)==1; Log.d(TAG,"query book: "+ user.toString()); } userCursor.close(); } }