ContentProvider作為安卓的四大組件之一,在看開發(fā)中用到的頻率遠(yuǎn)不如其他三個,以至于我都把這個東西給忘了,最近由于工作原因,不得不重新拾起來總結(jié)一下,那么今天就來說說自定義ContentProvider吧,
自定義ContentProvider
。今天的案例是這樣的,我們有兩個App,一個叫做cpHost,作為內(nèi)容提供者;另外一個叫做cpTest,專門用來操作這個cpHost中的數(shù)據(jù)。我們的cpHost中有一個數(shù)據(jù)庫,該數(shù)據(jù)庫中有一個User表,我們通過內(nèi)容提供者將這個User表共享出去,供其他App調(diào)用。下面我們就來看看怎么實現(xiàn)這樣一個效果。
既然用到數(shù)據(jù)庫存儲用戶表,那么毫無疑問我們需要一個DBHelper,如下:
public class DBHelper extends SQLiteOpenHelper { public static final String USERTABLE = user_table; public DBHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE IF NOT EXISTS + USERTABLE + (_id INTEGER PRIMARY KEY AUTOINCREMENT,USERNAME TEXT,NICKNAME TEXT,GENDER TEXT,AGE TEXT);); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }}我們創(chuàng)建一個用戶表,然后在在MainActivity中將該表中的數(shù)據(jù)顯示出來,這樣方便我們后面看到操作效果,于是我們需要一個listview,看看MainActivity的布局文件:
<relativelayout android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"><li></listview></relativelayout>
MainActivity.java:
public class MainActivity extends Activity { private ListView lv; private DBHelper helper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lv = (ListView) this.findViewById(R.id.lv); } public List<user>getData() { helper = new DBHelper(this, lenve.db, null, 1); SQLiteDatabase db = helper.getWritableDatabase(); List<user>list = new ArrayList<user>(); Cursor c = db.rawQuery(SELECT * FROM + DBHelper.USERTABLE, null); User u = null; while (c.moveToNext()) { String username = c.getString(c.getColumnIndex(USERNAME)); String nickname = c.getString(c.getColumnIndex(NICKNAME)); String gender = c.getString(c.getColumnIndex(GENDER)); String age = c.getString(c.getColumnIndex(AGE)); u = new User(username, nickname, gender, age); list.add(u); } c.close(); return list; } @Override protected void onResume() { super.onResume(); lv.setAdapter(new MyAdapter(this, getData())); }}</user></user></user>這里之所以把給ListView設(shè)置Adapter的方法放在onResume()方法中執(zhí)行,主要是為了測試方便,沒有其他意思,那么我們在來看看UserBean:
public class User { private String username; private String nickname; private String gender; private String age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public User(String username, String nickname, String gender, String age) { this.username = username; this.nickname = nickname; this.gender = gender; this.age = age; } public User() { }}還有一個MyAdapter:
public class MyAdapter extends BaseAdapter { private Context context; private List<user>list; public MyAdapter(Context context, List<user>list) { this.context = context; this.list = list; } @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.lv_item, null); holder = new ViewHolder(); holder.ageTv = (TextView) convertView.findViewById(R.id.age_tv); holder.nickNameTv = (TextView) convertView.findViewById(R.id.nickname_tv); holder.userNameTv = (TextView) convertView.findViewById(R.id.username_tv); holder.genderTv = (TextView) convertView.findViewById(R.id.gender_tv); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } User u = list.get(position); holder.ageTv.setText(u.getAge()); holder.genderTv.setText(u.getGender()); holder.nickNameTv.setText(u.getNickname()); holder.userNameTv.setText(u.getUsername()); return convertView; } class ViewHolder { TextView userNameTv, nickNameTv, genderTv, ageTv; }}</user></user>這些都很簡單的顯示部分的代碼,我就不再詳細(xì)解釋。說完這些我們終于可以介紹今天的核心內(nèi)容了,那就是ContentProvider。我們自定義的ContentProvider首先要繼承ContentProvider,繼承ContentProvider之后,我們會看到有多個方法需要我們實現(xiàn),分別onCreate()、query、getType、insert、delete、update,主要是這幾個方法,通過方法名字我們都能看出這幾個方法的含義,就是執(zhí)行增刪改查操作,其中onCreate在應(yīng)用啟動的時候會調(diào)用,我們可以在里邊做一些初始化的操作,但是不宜做一些耗時過長的操作,否則會導(dǎo)致應(yīng)用啟動時間變長,造成不好的用戶體驗。在其他的方法中我們分別執(zhí)行相應(yīng)的增刪改查操作即可。說到這里,我們不得不介紹ContentProvider中另外一個非常重要的東西,那就是Uri。
Uri是ContentResolver執(zhí)行CRUD方法時的重要參數(shù),我們可以從Uri中提取出我們要操作的數(shù)據(jù)對象,要操作哪一條數(shù)據(jù)等等信息,UriMatcher對象映射Uri的返回碼,我們可以使用UriMatcher來方便的知道ContentResolver想要干什么。下面我們舉例來說明一下:
<strong>content://com.lenve.cphost.mycontentprovider/user/3</strong>一般情況下,我們見到的Uri就是這樣的,我們可以將Uri分為三部分,第一部分是固定內(nèi)容,第二部分是authorities,也就是我們在清單文件中注冊ContentProvider時的authorities參數(shù),最后一部分表示數(shù)據(jù)源路徑,可有可無,這些根據(jù)自己的項目需求隨意定義即可,但是這里有一些約定俗成的規(guī)則,比如:
1.user多數(shù)情況下表示我們要操作的表名,因為一個ContentProvider可能涉及到多個表,通過這里來進(jìn)行區(qū)分。
2.如果user之后沒有參數(shù),默認(rèn)返回當(dāng)前表中所有數(shù)據(jù),或者是操作當(dāng)前表中所有數(shù)據(jù)
3.user之后的3表示操作數(shù)據(jù)庫的條件,可以是id,也可以是其他字段。
那么這么長一個字符串我們要怎么提取我們需要的數(shù)據(jù)呢?難道要用正則?其實不必,這里就用到我們前面說的UriMatcher,使用UriMatcher會自動對我們的Uri進(jìn)行匹配,但是,在匹配之前我們要先定義一下匹配規(guī)則,如下:
private static final String AUTHORITY = com.lenve.cphost.mycontentprovider; static { matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(AUTHORITY, user, 1);// 配置表 matcher.addURI(AUTHORITY, user/#, 2);// 匹配任何數(shù)字 matcher.addURI(AUTHORITY, user/*, 3);// 匹配任何文本 }我們一般習(xí)慣于在靜態(tài)模塊中初始化UriMatcher,我們可以向其中添加匹配規(guī)則,比如第6行,我們添加了匹配規(guī)則,如果如果Uri第三部分只用一個user,那么匹配結(jié)果為1,第7行,#表示任意數(shù)字,這句話表示如果Uri的第三部分是數(shù)字,那么匹配結(jié)果為2,第8行,*表示任意字符,user后還跟了第三個參數(shù),那么匹配結(jié)果為3,我們以delete方法為例:
@Override public int delete(Uri uri, String selection, String[] selectionArgs) { int code = matcher.match(uri); int result = 0; switch (code) { case UriMatcher.NO_MATCH: break; case 1: // 刪除所有 result = db.delete(DBHelper.USERTABLE, null, null); Log.d(qf, 刪除所有數(shù)據(jù)!); break; case 2: // content://com.lenve.cphost.mycontentprovider/user/10 // 按條件刪除,id result = db.delete(DBHelper.USERTABLE, _id=?, new String[] { ContentUris.parseId(uri) + }); Log.d(qf, 根據(jù)刪除一條數(shù)據(jù)); break; case 3: // content://com.lenve.cphost.mycontentprovider/user/zhangsan // uri.getPathSegments()拿到一個List<string>,里邊的值分別是0-->user、1-->zhangsan result = db.delete(DBHelper.USERTABLE, USERNAME=?, new String[] { uri.getPathSegments().get(1) }); break; default: break; } return result; }</string>
調(diào)用Uri中的matcher方法來進(jìn)行匹配,系統(tǒng)會根據(jù)我們在靜態(tài)模塊中的定義來返回相應(yīng)的匹配結(jié)果,根據(jù)不同的結(jié)果,執(zhí)行不同的操作,那么我們有什么方法可以快速提取出Uri中的參數(shù)呢?
這里我們介紹兩個方法,
1.比如我們的Uri是這樣的:
content://com.lenve.cphost.mycontentprovider/user/zhangsan那么我們通過uri.getPathSegments()方法可以拿到一個List集合,該集合中放了兩個字符串,第一個是user,第二個是zhangsan
2.比如我們的Uri是這樣的:
content://com.lenve.cphost.mycontentprovider/user/10那么我們可以通過ContentUris.parseId(uri)方法獲得10這個數(shù)字
以上兩種方式基本已經(jīng)可以解決我們遇到的所有問題了,
電腦資料
《自定義ContentProvider》(http://m.clearvueentertainment.com)。說了這么多,現(xiàn)在給大家看看一個完整的我的自定義ContentProvider:public class MyContentProvider extends ContentProvider { private SQLiteOpenHelper helper; private SQLiteDatabase db; private static UriMatcher matcher; private static final String AUTHORITY = com.lenve.cphost.mycontentprovider; static { matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(AUTHORITY, user, 1);// 配置表 matcher.addURI(AUTHORITY, user/#, 2);// 匹配任何數(shù)字 matcher.addURI(AUTHORITY, user/*, 3);// 匹配任何文本 } @Override public boolean onCreate() { helper = new DBHelper(getContext(), lenve.db, null, 1); db = helper.getWritableDatabase(); Log.d(qf, MyContentProvider--->onCreate()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public String getType(Uri uri) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { db.insert(DBHelper.USERTABLE, null, values); return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int code = matcher.match(uri); int result = 0; switch (code) { case UriMatcher.NO_MATCH: break; case 1: // 刪除所有 result = db.delete(DBHelper.USERTABLE, null, null); Log.d(qf, 刪除所有數(shù)據(jù)!); break; case 2: // content://com.lenve.cphost.mycontentprovider/user/10 // 按條件刪除,id result = db.delete(DBHelper.USERTABLE, _id=?, new String[] { ContentUris.parseId(uri) + }); Log.d(qf, 根據(jù)刪除一條數(shù)據(jù)); break; case 3: // content://com.lenve.cphost.mycontentprovider/user/zhangsan // uri.getPathSegments()拿到一個List<string>,里邊的值分別是0-->user、1-->zhangsan result = db.delete(DBHelper.USERTABLE, USERNAME=?, new String[] { uri.getPathSegments().get(1) }); break; default: break; } return result; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int code = matcher.match(uri); int result = 0; switch (code) { case 1: result = db.update(DBHelper.USERTABLE, values, selection, selectionArgs); break; case 2: result = db.update(DBHelper.USERTABLE, values, _id= + ContentUris.parseId(uri) + AND + selection, selectionArgs); break; // 根據(jù)手動傳參id來更新 case 3: result = db.update(DBHelper.USERTABLE, values, selection, selectionArgs); break; } return result; }</string>由于時間關(guān)系,有幾個方法沒有實現(xiàn),不過原理都是一樣的,不多說。
所有這些都做完之后,別忘了在清單文件中注冊ContentProvider,如下:
<provider android:authorities="com.lenve.cphost.mycontentprovider" android:exported="true" android:name="com.lenve.cphost.MyContentProvider"></provider>這里解釋一下第三個參數(shù),設(shè)置為true表示允許其他App調(diào)用,設(shè)置為false表示不允許其他App調(diào)用。
這里說完,我們再看看怎么在cpTest這個App中操作這些數(shù)據(jù):
核心代碼如下:
public class MainActivity extends Activity { private ContentResolver cr; private ContentValues values; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); cr = getContentResolver(); values = new ContentValues(); } public void onClickBtn(View v) { switch (v.getId()) { case R.id.add_btn: addData(); break; case R.id.delete_btn: deleteData(); break; case R.id.update_btn: updateData(); break; case R.id.search_btn: break; default: break; } } private void updateData() { // values.put(USERNAME, lisi); // cr.update(Uri.parse(content://com.lenve.cphost.mycontentprovider/user), // values, _id=?, // new String[] { 4 + }); values.put(USERNAME, wangwu); cr.update(Uri.parse(content://com.lenve.cphost.mycontentprovider/user/3), values, USERNAME=?, new String[] { lisi }); } private void deleteData() { // 根據(jù)id刪除 // cr.delete(Uri.parse(content://com.lenve.cphost.mycontentprovider/user/1), // , new String[] {}); // 根據(jù)username刪除 cr.delete(Uri.parse(content://com.lenve.cphost.mycontentprovider/user/zhangsan), , new String[] {}); } private void addData() { values.put(USERNAME, zhangsan); values.put(NICKNAME, 張三); values.put(AGE, 18); values.put(GENDER, 男); cr.insert(Uri.parse(content://com.lenve.cphost.mycontentprovider), values); }}
好了,關(guān)于自定義ContentProvider就說這么多。