Content Provider内容提供者:android中进程间通信的标准接口。即让当前进程中数据库中数据,可以让其它应用访问到。
作用 : 程序中的数据库中某一张表想要给其他程序访问时,为了安全的访问,就可以用到内容提供者;
举个栗子:A应用相当于一家银行,B应用相当于你自己,你要向银行借钱时,那么就要和银行建立联系,银行的客户经理就相当于provide负责将数据暴露出来,自己就相当于resolve,负责和银行的客户经理接洽,provide与resolve怎么建立通信呢?我们可以找不同的银行借钱,每家银行都有自己的网点,我们根据provide的地址建立连接,如果客户经理同意你的请求,那么你们的通道打通了,客户经理从银行拿到钱,然后通过通道将钱借给你;provide接收客户端resolve的数据请求-->获得数据库的数据-->返回数据;
奉上简单的代码;
创建一个工程:
在这个工程中创建数据库:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// createBankDB(getBaseContext());
}
private void createBankDB(Context baseContext) {
BankDbHelper helper = new BankDbHelper(baseContext);
helper.getWritableDatabase();
}
}
数据库帮助类
public class BankDbHelper extends SQLiteOpenHelper { public BankDbHelper(Context context) { super(context, "bank.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { //创建account表和info表; db.execSQL("create table account(id integer primary key autoincrement, name varchar(20),money folat)");
} @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }} 创建对外提供数据的provide,继承ContentProvider实现增删改查方法;在每个方法中打印日志查看是否连接成功;db.execSQL("create table info(id ,phone)");
在清单文件中注册provide,public class AccountManage extends ContentProvider { private static final String TAG = "AccountManage"; @Override public boolean onCreate() { return false; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { Log.e(TAG, "query: 查询调用" ); return null; } @Nullable @Override public String getType(@NonNull Uri uri) { return null; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { Log.e(TAG, "insert: 增加方法调用"); return null; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { Log.e(TAG, "delete: 删除方法调用"); return 0; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { Log.e(TAG, "update: 修改方法调用" ); return 0; }
authorities相当于一个provide的地址,不同的程序有可能提供不同的 provide;
<provider android:authorities="com.contentprovidersdemo.db.bank" android:name=".provide.AccountManage" android:exported="true"/>
创建新的工程,用来访问数据;界面上提供增删改查四个按钮,对应银行的增删改查;
增:public class MainActivity extends AppCompatActivity { @BindView(R.id.insert) Button mInsert; @BindView(R.id.delete) Button mDelete; @BindView(R.id.updata) Button mUpdata; @BindView(R.id.query) Button mQuery; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); } @OnClick({R.id.insert, R.id.delete, R.id.updata, R.id.query}) public void onClick(View view) { switch (view.getId()) { case R.id.insert: break; case R.id.delete: break; case R.id.updata: break; case R.id.query: break; } } }
利用上下文获取到resolve对象:
通过resolve调用内容提供者的增方法ContentResolver resolver =getContentResolver();
insert方法中需要两个参数,一个uri,一个ContentValues对象,resolver.insert(uri,values);
uri:统一资源标识符;ContentValues values = new ContentValues(); //指定内容提供者的地址,就是注册时的
Uri uri = Uri.parse("content://com.contentprovidersdemo.db.bank");authorities来区分要连接的那个provide,一定要注意格式:"connect://+authorities"
将两个程序运行起来,点击insert按钮,可以看到输出日志:@OnClick({R.id.insert, R.id.delete, R.id.updata, R.id.query}) public void onClick(View view) { switch (view.getId()) { case R.id.insert: ContentResolver resolver =getContentResolver(); ContentValues values = new ContentValues(); Uri uri = Uri.parse("content://com.contentprovidersdemo.db.bank"); resolver.insert(uri,values); Toast.makeText(this,"insert点击",Toast.LENGTH_LONG).show(); break; case R.id.delete: break; case R.id.updata: break; case R.id.query: break; }
11-12 16:31:30.200 32433-32444/? E/AccountManage: insert: 增加方法调用 11-12 16:31:30.250 1612-1717/? E/Cataloger: syncFile:/storage/emulated/0/Mob/comm/locks/.dhlock 11-12 16:31:30.250 1612-1717/? E/Cataloger: need not sync path:/storage/emulated/0/Mob/comm/locks/.dhlock 11-12 16:31:36.300 32433-32443/? E/AccountManage: insert: 增加方法调用 11-12 16:31:40.260 1612-1717/? E/Cataloger: syncFile:/storage/emulated/0/Mob/comm/locks/.dhlock 11-12 16:31:40.260 1612-1717/? E/Cataloger: need not sync path:/storage/emulated/0/Mob/comm/locks/.dhlock
如果报
java.lang.SecurityException: Permission Denial
异常,那就在provide的清单文件中添加<!--authorities 授权许可,在resolve中的uri--> <provider android:authorities="com.contentprovidersdemo.db.bank" android:name=".provide.AccountManage" android:exported="true"//报异常的时候添加 />
表名的区分:UriMatcher
resolver发起请求,也是通过uri来区分要操作的哪张表;就像访问网站时我们可以通过/来指定访问的目录,操作表也一样
将表名添加到uri的authorities后面以/分开;通过log打印看看:Uri uri = Uri.parse("content://com.contentprovidersdemo.db.bank/account");
点击按钮输出:public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { Log.e(TAG, "insert: uri = " +uri.toString()); return null; }
谷歌提供一个用来匹配URI的工具: UriMatherE/AccountManage: insert: uri = content://com.contentprovidersdemo.db.bank/account
可以参考http://blog.csdn.net/feng88724/article/details/6331396这篇文章了解UriMather;在provide中创建uri匹配器对象并且以静态代码块的方式预设匹配规则
第一个参数就是provide中的authorities, path 就是要操作的表名,code是自己预设的一个整数常量;//创建匹配器;参数默认是一个-1的常量匹配不上的时候返回-1 static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //预设匹配规则,将要操作的表都可以添加,定义不同的code\ static { uriMatcher.addURI(authoritity,path,code); }
添加匹配规则后,我们只要拿着这个匹配器插入一个uri对象,它会根据前面的authorities和path转换出一个code整形数,通过比较code就知道是操作的那个表;
//创建匹配器; static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); private static final int ACCOUNT = 1; private static final int INFO = 2; //预设匹配规则\ static { uriMatcher.addURI("com.contentprovidersdemo.db.bank","account",ACCOUNT); uriMatcher.addURI("com.contentprovidersdemo.db.bank","info",INFO); } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { Log.e(TAG, "insert: uri = " +uri.toString());
//通过uriMatcher.match(uri)返回的值来判断是哪张表,if (uriMatcher.match(uri) == INFO){ Log.d(TAG, "操作的是info表"); }else if (uriMatcher.match(uri) == ACCOUNT){ Log.d(TAG, "insert: 操作的是 account 表"); }else { Log.d(TAG, "insert: 没有匹配到"); } return null; } 日志输出:
通过这种方式就可以区分是那张表了!11-12 22:05:34.978 21992-22002/? E/AccountManage: insert: uri = content://com.contentprovidersdemo.db.bank/account 11-12 23:03:02.484 32743-32754/? E/AccountManage: insert: uri = content://com.contentprovidersdemo.db.bank/account 11-12 23:03:02.484 32743-32754/? D/AccountManage: insert: 操作的是 account 表 11-12 23:03:50.844 1377-1389/? E/AccountManage: insert: uri = content://com.contentprovidersdemo.db.bank/info 11-12 23:03:50.844 1377-1389/? D/AccountManage: 操作的是info表
现在建立连接了,也可以操作指定的数据表了,那么我们开始写数据库的增删改查;在provide中:
private BankDbHelper mHelper; @Override public boolean onCreate() { //在创建provide的时候创建数据库帮助类对象; mHelper = new BankDbHelper(getContext()); return false; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { Log.e(TAG, "insert: uri = " +uri.toString()); SQLiteDatabase db = mHelper.getWritableDatabase(); if (uriMatcher.match(uri) == INFO){ //插入info表 ,插入是数据有resolve传过来 db.insert("info",null,values); Log.d(TAG, "插入的是info表"); }else if (uriMatcher.match(uri) == ACCOUNT){ db.insert("account",null,values); Log.d(TAG, "insert: 插入的是 account 表"); }else if (uriMatcher.match(uri)== UriMatcher.NO_MATCH){ //匹配不到表名的时候抛出一个异常; throw new IllegalArgumentException("未知表名,请检查表名"); } return null; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { SQLiteDatabase db = mHelper.getWritableDatabase(); if (uriMatcher.match(uri)==INFO){ //要删除的条件和数据都是resolve传递过来的 db.delete("info",selection,selectionArgs); Log.d(TAG, "delete: 删除是info表的内容"); }else if (uriMatcher.match(uri) == ACCOUNT){ db.delete("account",selection,selectionArgs); Log.d(TAG, "delete: 删除是account表的内容"); }else if (uriMatcher.match(uri) == UriMatcher.NO_MATCH){ throw new IllegalArgumentException("未知的表名"); } Log.e(TAG, "delete: 删除方法调用"); return 0; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { Log.e(TAG, "update: 修改方法调用" ); SQLiteDatabase db = mHelper.getWritableDatabase(); if (uriMatcher.match(uri)==INFO){ //要修改的条件和数据都是resolve传递过来的 db.update("info",values,selection,selectionArgs); Log.d(TAG, "update: 修改是info表的内容"); }else if (uriMatcher.match(uri) == ACCOUNT){ db.update("account",values,selection,selectionArgs); Log.d(TAG, "update: 修改是account表的内容"); }else if (uriMatcher.match(uri) == UriMatcher.NO_MATCH){ throw new IllegalArgumentException("未知的表名"); } return 0; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { Log.e(TAG, "query: 查询调用" ); SQLiteDatabase db = mHelper.getReadableDatabase(); if (uriMatcher.match(uri)==INFO){ Cursor cursor = db.query("info", projection, selection, selectionArgs, null, null, sortOrder); //注意,这里查询的cursor对象是不能关闭的,要关也得由resolve来关闭, return cursor; }else if (uriMatcher.match(uri) == ACCOUNT){ Cursor cursor = db.query("account", projection, selection, selectionArgs, null, null, sortOrder); return cursor; }else if (uriMatcher.match(uri) == UriMatcher.NO_MATCH){ throw new IllegalArgumentException("未知的表名"); } return null; }
resolve端:
@OnClick({R.id.insert, R.id.delete, R.id.updata, R.id.query}) public void onClick(View view) { switch (view.getId()) { case R.id.insert: { ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues(); values.put("name", "和珅"); values.put("money", 20000000); Uri uri = Uri.parse("content://com.contentprovidersdemo.db.bank/account"); resolver.insert(uri, values); Toast.makeText(this, "insert点击", Toast.LENGTH_LONG).show(); break; } case R.id.delete:{ ContentResolver resolver =getContentResolver(); Uri uri = Uri.parse("content://com.contentprovidersdemo.db.bank/account"); resolver.delete(uri,"name=?",new String[]{"和珅"}); Toast.makeText(this, "删除成功", Toast.LENGTH_LONG).show(); } break; case R.id.updata:{ Uri uri = Uri.parse("content://com.contentprovidersdemo.db.bank/account"); ContentResolver resolver =getContentResolver(); ContentValues values = new ContentValues(); values.put("money",999999); resolver.update(uri,values,"name=?",new String[]{"和珅"}); Toast.makeText(this, "修改成功", Toast.LENGTH_LONG).show(); } break; case R.id.query:{ Uri uri = Uri.parse("content://com.contentprovidersdemo.db.bank/account"); ContentResolver resolver =getContentResolver(); /** * resolver.query(uri, * projection,查询的列 ,null为所有列 * selection, 查询条件, null 为查询所有 * selectionArgs, 查询条件 的值 * sortOrde); 排序 null 为默认排序 */ Cursor cursor = resolver.query(uri, null, "name=?", new String[]{"和珅"}, null); if (cursor.moveToFirst()){ float money = cursor.getFloat(cursor.getColumnIndex("money")); Toast.makeText(this, "和珅的资产 = "+money, Toast.LENGTH_SHORT).show(); }else { Toast.makeText(this, "查无此人", Toast.LENGTH_SHORT).show(); } cursor.close(); } break; }
使用步骤总结:provide :
1.创建ContentProvide 子类;
2.添加UriMatcher 匹配规则;
3.实现子类的曾删改查方法
Resolver :
1.上下文获取resolver对象
2.通过Uri区分访问的provide和具体的表
3.实现增删改查方法