大家好,今天我们来讲解ContentProvider和数据库的区别是他们之间的联系.
四大组件之一
1.ContentProvider是如何实现数据共享的?
- 1.在Android中,为了把自己程序的数据(一般是数据库)提供给其他应用程序,就通过ContentProvider提供的方法.
- 2.内容提供者可认为是程序间共享数据的接口,新建一个类继承ContentProvider.
- 3.按要求重写insert,delete,update,query方法(用于数据库的操作).
4.要记得进行清单文件注册:
注册要加上作者标记authorities(自定义的):
<provider android:authorities="this.bank.authority" android:name=".MyContentProvider"/>
- 5.其他程序通过内容解析者ContentResoler的对象进行增删改查
2.为什么要使用ContentProvider?它和sql在实现上有什么区别?
- 1.ContentProvider 屏蔽了数据存储的细节,内部实现透明化,用户只需关心uri即可(是否匹配)
- 2.ContentProvider能实现不同app的数据共享,sql 只能是自己程序才能访问
- 3.Contentprovider还能增删本地的文件,xml等信息
3.说说ContentProvider,ContentResolver,ContentObserver之间的关系?
- ContentProvider:内容提供者,定义增删改查(方法)和数据库关联;
- ContentResolver:内容解析者,一个app里边用于获取另一个app的数据(进行增删查改的具体数据操作)
ContentObserver:内容观察者,另外的一个app(可以是不同于上述两个app)可以监听数据改变的消息
- 1.getContentResolver.notifyChange(uri):在内容提供者里面的各个方法添加,这样就能发出消息
- 2.getContentResolver.registerContentOberver():进行监听注册,一个想观察内容变化的app,在观察者创建时就注册
4.如何访问assets资源目录下的文件?
针对AndroidStudio,和Eclipse的不同,AS没有资产文件夹,要自己创建,点击一下连接进行访问.
写了一篇案例,让你完全了解Assets和数据库的操作(点我访问)
5.高并发情况下,如何进行数据库查询?
(广泛回答)
- 1.不要关联过多的表查询
- 2.减少链接时间
- 3.采用索引
- 4.将查询到的数据进行缓存策略处理.
内容提供者的案例代码
分成3个程序
- 银行应用程序(内容提供者(数据库)所在程序)
- 行长应用程序(相当于另一个app,通过内容解析者对银行进行操作)
- 观察者应用程序(当银行内容改变就会得到信息)
一. 银行(内容提供者)程序代码如下
- 数据库打开帮助类代码
/**
* 提示创建两分法+构造(写个上下文的就可以)
*/
public class DBhelper extends SQLiteOpenHelper {
public DBhelper(Context context) {
//定义数据库 名字 和 版本,选择
super(context, "mycount.db", null, 1);
}
/**
* 定义表-通过sql语句
* 1.表名+主键+各个列名+类型
* 2.数据库执行语句
* 3.然后就可以单元测试一下是否创建成功了.即创建DBhelper对象,然后对象.获取读/写即可创建并获取数据库
*/
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table count (_id integer primary key autoincrement , name verchar , money integer )";
db.execSQL(sql);
}
//此方法调动的时机: 上面的1变成更大数的时候
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
- 继承内容观察者类(定义的是方法,照着存入方法提供的参数类型即可)的代码
/**
* 新建类继承内容提供者,注册并写作者标记,然后要求重写所有增删改查方法,写完先进行单元测试
*/
public class MyContentProvider extends ContentProvider {
//必须先创建URI匹配器,传入内置的码 ,设置成静态的为了初始化
static UriMatcher mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
//增加标记,和外在标记匹配
//参数一: 作者名,注册的时候对应也要写这个标记;参数二:一般写表名,为了其他程序的直接访问;参数3:匹配上之后返回的状态码
mMatcher.addURI("this.bank.authority", "count", 101);
//等下解析者,匹配的uri是:content//作者名+/+表名
}
//每个方法都要创建数据库帮助对象,然后实现进行增删改查功能,这些都是提供给-其他程序的"解析者"的,所以"不进行""具体参数操作",只是进行"方法操作"
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
//如果匹配就给操作,就是状态码相同
if (mMatcher.match(uri) == 101) {
DBhelper dh = new DBhelper(getContext());
SQLiteDatabase db = dh.getWritableDatabase();
db.insert("count", null, values);//只是做好-提供给解析者的方法即可
} else {
//不匹配就抛异常,不合法参数异常
throw new IllegalArgumentException("口令错误");
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//同样需要匹配才能操作
if (mMatcher.match(uri) == 101) {
DBhelper dh = new DBhelper(getContext());
SQLiteDatabase db = dh.getWritableDatabase();
db.delete("count", selection, selectionArgs);//对应删除
//提供给内容观察者的
//获取解析.通知改变,因为外部app通过解析者操作内容提供者,所以在这里得到解析,并当解析的时候,发出消息
getContext().getContentResolver().notifyChange(uri,null);//第二个参数指谁能收到,不指定的话那只要匹配uri的都能收到.
} else {
//不匹配就抛异常,不合法参数异常
throw new IllegalArgumentException("口令错误");
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
//同样需要匹配才能操作
if (mMatcher.match(uri) == 101) {
DBhelper dh = new DBhelper(getContext());
SQLiteDatabase db = dh.getWritableDatabase();
db.update("count", values, selection, selectionArgs);//对应即可
} else {
//不匹配就抛异常,不合法参数异常
throw new IllegalArgumentException("口令错误");
}
return 0;
}
/**
* 返回一个游标
*/
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
//同样需要匹配才能操作
if (mMatcher.match(uri) == 101) {
DBhelper dh = new DBhelper(getContext());
SQLiteDatabase db = dh.getReadableDatabase();//查询只需要read即可
Cursor cursor = db.query("count", projection, selection, selectionArgs, null, null, sortOrder);//有的就对应填上即可.
//返回游标
return cursor;
} else {
//不匹配就抛异常,不合法参数异常
throw new IllegalArgumentException("口令错误");
}
}
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
}
- MainActivity.java代码(就是拿来进行数据库创建而已,过程测试已经通过)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initDatabase();
}
private void initDatabase() {
DBhelper dBhelper = new DBhelper(this);
dBhelper.getWritableDatabase();
}
}
二. 行长(获取内容提供者里边的内容)程序代码如下**
- (PS,这个程序就不用创建了,直接在刚才的银行App里面进行单元测试,就等同于执行行长app了)
代码如下:
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() { super(Application.class); }
//测试创建帮助类,定义数据库+版本+表名+列名
//获取可读/写,完成创建.这一步可以写在MainActivity的onCreate里
public void testDB() {
DBhelper dBhelper = new DBhelper(getContext());
dBhelper.getWritableDatabase();
}
public void testInsert() {
//获取解析对象,和内容提供者匹配,从而达到操作数据库的目的
//如果是在Activity直接get,这里就要通过上下文get
ContentResolver resolver = getContext().getContentResolver();
//注意Uri的字符串构成是:content+:+//+作者名+/+表名,格式不能忘有content:
Uri uri = Uri.parse("content://this.bank.authority/count");
ContentValues values = new ContentValues();
values.put("name", "小明");
values.put("money", 12300);
//注意:这样连续添加会小明被覆盖掉,只插入了小强,因为不是个集合,解决方法是分开
values.put("name", "小强");
values.put("money", 14000);
resolver.insert(uri, values);
values.put("name", "小王");
values.put("money", 12300);
resolver.insert(uri, values);
values.put("name", "小张");
values.put("money", 12300);
resolver.insert(uri, values);
}
//注意条件参数的对应01
public void testQuery() {
Uri uri = Uri.parse("content://this.bank.authority/count");
ContentResolver contentResolver = getContext().getContentResolver();
//第五个;null:默认升序;DESC:降序,根据后续需要
Cursor cursor = contentResolver.query(uri, new String[]{"name","money"}, null, null,null);
while (cursor.moveToNext()) {
//0,1对应上面的第一第二个参数
String name = cursor.getString(0);
String money = cursor.getString(1);
System.out.println("name : "+name+" money : "+money);
}
//关闭游标
cursor.close();
}
//删除,重点是 列名要加问号,不然报异常
public void testDelete() {
ContentResolver contentResolver = getContext().getContentResolver();
Uri uri = Uri.parse("content://this.bank.authority/count");
//加问号不然提示异常
contentResolver.delete(uri, "name=?", new String[]{"小强"});
contentResolver.delete(uri, "name=?", new String[]{"小张"});
// contentResolver.delete(uri, null,null);//写成这样意味着全部删除
}
public void testUpdate() {
Uri uri = Uri.parse("content://this.bank.authority/count");
ContentResolver contentResolver = getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "小明");
contentResolver.update(uri, values, "name=?", new String[]{"小张"});
// contentResolver.update(uri, values, null, null);//写成这样意味着全部替换
}
}
三. 观察者(监听内容提供者里边的内容的改变)程序代码如下**
通过内容解析者注册即可
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取解析
//注册内容观察者
//此uri变化就会做出通知
Uri uri = Uri.parse("content://this.bank.authority/count");
//参数二,满足前半段的uri也获取通知
getContentResolver().registerContentObserver(uri,true,new myContentObserver(new Handler()));
}
class myContentObserver extends ContentObserver {
public myContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
System.out.println("银行的客户和钱少了");
super.onChange(selfChange);
}
}
}
运行过程:
- 运行内容观察者app
- 运行银行(内容提供+数据库)app
- 运行行长app(银行测试类)里面的删除方法
打印效果:
07-06 09:14:13.203 20859-20859/? I/System.out: 银行的客户和钱少了
07-06 09:14:13.203 20859-20859/? I/System.out: 银行的客户和钱少了
为了方便大家的更直观的理解我画出一个图
总结,大家了解真个交互过程就能完全理解这个版块了
- uri是他们识别的信号
- 内容解析者是他们沟通的桥梁;