最不知疲倦的事情就是我们最喜欢的事情
内容提供者ContentProvider,UriMatcher
说到,内容提供者,我们立马想到的应该是数据库,让我们先去建立数据库吧
> public class MySqliteOpenHelper extends SQLiteOpenHelper {
public MySqliteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
继承SQLiteOpenHelper要我们实现这三个方法,先看下构造函数,第一个参数是上下文,第二个参数是数据库名,第三个参数是..我也没用过,一般填null,第四个参数是数据库的版本号,版本号是什么意思呢,是这样的,当我们new MySqliteOpenHelper对象的时候,如果同一数据库传入版本号1即会执行onCreat方法,你再继续接着new还传版本号1的话,就不执行onCreat了,因为版本1的数据库已经决定好了,但是同一数据库如果你传2的话,这是表明数据库升级了,这时候执行onUpgrade这个方法.好了,我们这里不需要对数据库进行升级,也只创建一个数据库,所以我们把这个类改造一下
> public MySqliteOpenHelper(Context context) {
super(context, "db1", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE myTableName(_id integer primary key autoincrement, name TEXT)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
在onCreat中我们创建了一个表,名字叫做,没错,就叫做我的表名myTableName,它有两个字段_id主键,这android系统的数据库也喜欢把这个当主键,我们跟随潮流,还有一个字段就是name了,好了SqliteOpenHelper搞好了,我们开始我们的内容提供者吧
> public class MyContentProvider extends ContentProvider{
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
ContentProvider是一个抽象类,继承它要我们实现上面那些方法.看到onCreat方法,我们当然看它了,点进去看看
> *
* @return true if the provider was successfully loaded, false otherwise
*/
public abstract boolean onCreate();
前面还有一大堆介绍的,有点长.意思就是说如果我们的SQLiteOpenHelper准备好了后就返回true否则返回false,所以我们这样改
> private MySqliteOpenHelper mMySqliteOpenHelper;
@Override
public boolean onCreate() {
mMySqliteOpenHelper = new MySqliteOpenHelper(getContext());
return mMySqliteOpenHelper != null;
}
接下来我们做什么呢,等等,内容提供者是四大组件之一啊,所以我们应该去清单文件中配置下它啊
> <provider
android:name=".db.MyContentProvider"
android:authorities="com.it.contentprovidernote.myauthorities"/>
看到这个authorities没有,我们俗称主机名,怎么翻译来的我也不知道….如果不配置它是会提示有错误!当然配置成什么样根据自己决定.配置好主机名就轮到我们的UriMatcher登场了
> public static final String MYAUTHORITIES = "com.it.contentprovidernote.myauthorities";
public static final String MYTABLENAME = "myTableName";
public static final int MYTABLENAME_CODE = 1;
private static UriMatcher mUriMatcher ;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(MYAUTHORITIES,MYTABLENAME,MYTABLENAME_CODE);
}
Uri匹配器UriMatcher 看不懂是干嘛的是吧,干说不好说 ,等下在query方法中有注释结合代码一看就明白了,看下匹配器的这个方法addURI(String authority, String path, int code),第一个参数即我们在清单文件配置的主机名,第二个是自己定义的一个子路径,第三个是匹配绑定值,下面我们会根据这个值找到我们的uri.注意这个值必须是个正数,不然会抛一个异常,它的源码有提示,而且一个uri只能绑定一个值,千万不要让它绑定多个值,那就没用了.好了,这些都弄好了可以开始我们的增删改查了,那个getType就算了吧,具体我也没用过.
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
switch (mUriMatcher.match(uri)){ //这就是匹配器的作用所在了,通过匹配得到的绑定值,一个内容提供者可以操作多种自定义功能
case MYTABLENAME_CODE:
SQLiteDatabase db = mMySqliteOpenHelper.getReadableDatabase();
cursor = db.query(MYTABLENAME, projection, selection, selectionArgs, null, null, sortOrder);
//这里我们不能把db关了,如果关了的话,返回的cursor就没了,由此可以得出在内容提供者里是不用我们关db的,它自己应该处理了
default:
break;
}
return cursor;
}
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
long insert = -1;
switch (mUriMatcher.match(uri)){
case MYTABLENAME_CODE:
SQLiteDatabase db = mMySqliteOpenHelper.getWritableDatabase();
insert = db.insert(MYTABLENAME, null, values); //看源码插入失败返回-1,即如果插入失败不提醒内容观察者数据改变了
if (insert != -1) getContext().getContentResolver().notifyChange(uri,null);
default:
break;
}
uri.withAppendedPath(uri,String.valueOf(insert));
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int delete = 0;
switch (mUriMatcher.match(uri)){
case MYTABLENAME_CODE:
Log.e("MyContentProvider", "进来了");
SQLiteDatabase db = mMySqliteOpenHelper.getWritableDatabase();
delete = db.delete(MYTABLENAME,selection,selectionArgs);
if (delete != 0) getContext().getContentResolver().notifyChange(uri,null);
default:
break;
}
return delete;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int update = 0;
switch (mUriMatcher.match(uri)){
case MYTABLENAME_CODE:
SQLiteDatabase db = mMySqliteOpenHelper.getWritableDatabase();
update = db.update(MYTABLENAME,values,selection,selectionArgs);
if(update != 0) getContext().getContentResolver().notifyChange(uri,null);
default:
break;
}
return update;
}
看到代码,这下应该明白了我们匹配器UriMatcher的作用了吧,让我们的类变的这么牛,这是什么设计模式来着…..有匹配模式么O(∩_∩)O~,要让内容观察者知道我们的数据发生变化了,要调用notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer)这个方法,第一个参数就是我们匹配的uri,第二个参数是要提醒哪个内容观察者数据发生改变了,填null即是提醒所有注册了这个uri的内容提供者的.所以我们一般都填null.好了,我们内容提供者到这就大功高成了.一般写好内容提供者因为有数据库,基本都是去测试测试的,所有我们也去测试一下吧,在这用下android studio的测试,看图
android studio帮我们去创建了测试包和类了,不用我们像eclipse那样去清单文件配置之类的了,好了开始写测试方法吧
> Uri url = Uri.parse("content://com.it.contentprovidernote.myauthorities/myTableName");
public void testInsert()
{
ContentValues values = new ContentValues();
for (int i = 1; i < 51; i++) {
values.put("name","走吧走吧去吧去吧 " + i);
getContext().getContentResolver().insert(url,values);
}
}
这里内容提供者的uri.parse(string) string都是content://后面再跟我们自己要操作的数据库定义好的uri.如何运行呢,我们把鼠标移动方法上,然后这样点击运行测试方法就行了,对了,运行的时候要开启模拟器,因为测试也是需要安装APK的
运行结果
这就是测试成功的样子了,让我们去把数据库导出来,看看到底是不是真的成功了
那测试失败是什么样子呢,我们来看看,我们把uri路径改错来.这样就提示我们错了
其它功能测试方法
>
public void testDeletee()
{
getContext().getContentResolver().delete(url, "_id = ?", new String[]{"1"});
}
public void testUpdate()
{
Uri url = Uri.parse("content://com.it.contentprovidernote.myauthorities/myTableName");
ContentValues values = new ContentValues();
values.put("name","999999999999");
getContext().getContentResolver().update(url, values,"_id = ?", new String[]{"2"});
}
public void testQuery(){
Cursor cursor = getContext().getContentResolver().query(url, new String[]{"_id", "name"}, "_id < ?", new
String[]{10 + ""}, null);
while (cursor.moveToNext()){
String id = cursor.getString(cursor.getColumnIndex("_id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
Log.e("ApplicationTest", "========================="+id+" : " + name);
}
}
我都测试过了,都是成功的,说明我们的内容提供者是没有错误的,那我们开始把它用起来吧.我们用一个listview来显示数据,至于怎么加载数据我们用AsyncTask吧,顺便在这里介绍一下它的基本用法,它是一个抽象类,我们继承一下它
> class MyAsynctask extends AsyncTask<Void, Integer, ArrayList<String>> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected ArrayList<String> doInBackground(Void... params) {
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(ArrayList<String> strings) {
super.onPostExecute(strings);
}
这里可以看出 AsyncTask <\Params, Progress, Result>第一个参数是doInBackground的参数类型,它的参数由执行函数execute提供.它运行在子线程,第二个参数是onProgressUpdate的参数类型,从它的解释我们可以看出,当它运行了publishProgress(Progress… values)即也是这个函数的参数类型,它也会在UI线程调用
第三个参数是doInBackground的返回类型也是onPostExecute的参数类型,它运行在UI线程,即表示在子线程加载的数据返回到UI线程在onPostExecute处理数据,至于onPreExecute这个方法看名字就知道是执行子线程前的准备工作,在UI线程执行,这里我们不用它,来看看实现吧.
> @Override
protected ArrayList<String> doInBackground(Void... params) {
ArrayList<String> list = new ArrayList<String>();
Cursor cursor = getContentResolver().query(url, new String[]{"name"}, null, null, null);
mDialog.setMax(cursor.getCount());
int i= 0;
while (cursor.moveToNext()) {
SystemClock.sleep(50); //太快了,睡50毫秒
publishProgress(++i);
String name = cursor.getString(cursor.getColumnIndex("name"));
list.add(name);
}
return list;
}
@Override
protected void onProgressUpdate(Integer... values) {
mDialog.setProgress(values[0]);
}
@Override
protected void onPostExecute(ArrayList<String> lsit) {
mListView.setVisibility(View.VISIBLE);
mAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item_text, lsit);
mListView.setAdapter(mAdapter);
mDialog.dismiss();//加载完了就消失
}
这里我们把加载的进度也实现了,在onCreat中初始化
> @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.listview);
mListView.setCacheColorHint(0x00000000);
mListView.setVisibility(View.GONE);
new MyAsynctask().execute();
mDialog = new ProgressDialog(this);
mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mDialog.setTitle("加载中");
mDialog.show();
好了写了这么久,让我们来看看效果吧.
好了,最后来加上我们的内容观察者吧.ContentObserver它是一个抽象类,我们需要继承它
> class MyContentObserver extends ContentObserver {
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public MyContentObserver(Handler handler) {
super(handler);
}
@Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
@Override
public void onChange(boolean selfChange) {
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
}
}
看,构造方法是必须实现的,至于后面三个方法是我重写的,第一个方法deliverSelfNotifications它默认是返回false的看它的解释是说如果它自己改变要通知别的观察者的话,返回true,我没用过///还有看下面两个双炮胎onChange,其实下面那个多一个参数即那个两个参数的内部调用的也是一个参数的,我们这里不判断哪个uri变化了,所以为了简单点,我们只要重写那个一个参数的onChange就好了,至于那个参数selfchange顾名思义就是判断自己改变了没有,跟讲的第一个方法那样,我没用过,不理它…望知道的可以推荐个好的博客看看
> Toast.makeText(getApplicationContext(), "数据库改变了,我收到通知了,接下来怎么办你看着办吧", Toast.LENGTH_SHORT)
.show();
然后在onCreat中注册它,registerContentObserver中第一个第三个参数都好理解,第二个参数看解释说如果为false的话你注册的uri对应的数据改变了,会提醒你,这个uri的祖先数据改变了也会提醒,额,好像我们这个就是祖先,应该是后面跟个/string就叫它的孩子了.如果为true的话不但祖先变了会提醒还有孩子也会提醒你,所以我们一般是填false的
> mObserver = new MyContentObserver(mHandler);
getContentResolver().registerContentObserver(url, false, mObserver);
再在onDestory注销它
> @Override
protected void onDestroy() {
if (mObserver != null) {
getContentResolver().unregisterContentObserver(mObserver);
mObserver = null;
}
super.onDestroy();
}
然后在我们的MyAsnyTask的onPostExecute方法,加载完数据后,我们3秒后去删除_id为2的数据 ,然后看看内容能不能收到数据改变的提醒
>
@Override
protected void onPostExecute(ArrayList<String> lsit) {
mListView.setVisibility(View.VISIBLE);
mAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item_text, lsit);
mListView.setAdapter(mAdapter);
mDialog.dismiss();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
getContentResolver().delete(url, "_id = ?", new String[]{"2"});
}
}, 3000);
}
加了内容观察者后
完整的MainActivity代码
> public class MainActivity extends Activity {
private ListView mListView;
private ArrayAdapter<String> mAdapter;
Uri url = Uri.parse("content://com.it.contentprovidernote.myauthorities/myTableName");
private MyContentObserver mObserver;
private Handler mHandler = new Handler();
private ProgressDialog mDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.listview);
mListView.setCacheColorHint(0x00000000);
mListView.setVisibility(View.GONE);
new MyAsynctask().execute();
mDialog = new ProgressDialog(this);
//默认是圈的,这里设置为水平条
mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mDialog.setTitle("加载中");
mDialog.show();
mObserver = new MyContentObserver(mHandler);
getContentResolver().registerContentObserver(url, false, mObserver);
}
class MyContentObserver extends ContentObserver {
public MyContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
Toast.makeText(getApplicationContext(), "数据库改变了,我收到通知了,接下来怎么办你看着办吧", Toast.LENGTH_SHORT)
.show();
}
}
class MyAsynctask extends AsyncTask<Void, Integer, ArrayList<String>> {
@Override
protected ArrayList<String> doInBackground(Void... params) {
ArrayList<String> list = new ArrayList<String>();
Cursor cursor = getContentResolver().query(url, new String[]{"name"}, null, null, null);
mDialog.setMax(cursor.getCount());
int i= 0;
while (cursor.moveToNext()) {
SystemClock.sleep(50);
publishProgress(++i);
String name = cursor.getString(cursor.getColumnIndex("name"));
list.add(name);
}
return list;
}
@Override
protected void onProgressUpdate(Integer... values) {
mDialog.setProgress(values[0]);
}
@Override
protected void onPostExecute(ArrayList<String> lsit) {
mListView.setVisibility(View.VISIBLE);
mAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.item_text, lsit);
mListView.setAdapter(mAdapter);
mDialog.dismiss();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
getContentResolver().delete(url, "_id = ?", new String[]{"2"});
}
}, 3000);
}
}
@Override
protected void onDestroy() {
if (mObserver != null) {
getContentResolver().unregisterContentObserver(mObserver);
mObserver = null;
}
super.onDestroy();
}
}
R.layout.item_text这个是我抄的android.R.layout.simple_list_item_1因为我不知道怎么改它的原始的字体颜色
> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="15dp"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:textColor="#000000"
android:textSize="25sp"/>
啊,好长.说实话,真有点啰嗦/也许看到这么长,大家都不看了吧