再探Android多应用间数据共享机制,自定义ContentProvider



Android中的ContentProvider是一种多应用数据共享的机制,任何时候同一Provider只会创建一次,是由系统进行初始化和管理的。本文中将通过实现一个简单通讯录的插入、删除、查询操作来让你了解ContentProvider机制极其自定义过程。读过本系列(读取手机中通讯录)(sqlite数据库操作)的读者可以发现,本文中实现的应用以这两篇内容为基础的。虽然,本文的MyContentProvider使用sqlite数据库进行持久化存储操作,包装后以ContentProvider机制供各app调用。但是,为理解ContentProvider,你必须有这样两个概念:一、它是一种可以跨应用共享数据的机制,正如本文开头所说。二、底层的存储在实现你的ContentProvider的时候可以自定义,即可以为数据库、也可以为文件,持久化或非持久化存储的其他形式。

程序截图如下:

 


类AcMain.java,为主Activity,用来实现应用的主界面控件和流程,代码:





1.  //AcMain.java
2.  package jtapp.contentproviders;
3. 4.  import android.app.Activity;
5.  import android.content.ContentValues;
6.  import android.database.Cursor;
7.  import android.os.Bundle;
8.  import android.view.View;
9.  import android.view.View.OnClickListener;
10.  import android.widget.Button;
11.  import android.widget.TextView;
12. 13.  public class AcMain extends Activity {
14.          private Button btInsertData = null;
15.          private Button btViewData = null;
16.          private Button btDelOne = null;
17.          private Button btClearAll = null;
18. 19.          /** Called when the activity is first created. */
20.          @Override
21.          public void onCreate(Bundle savedInstanceState) {
22.                  super.onCreate(savedInstanceState);
23.                  setContentView(R.layout.main);
24. 25.                  btInsertData = (Button) findViewById(R.id.Button02);
26.                  btInsertData.setOnClickListener(new ClickViewHandler());
27.                  btViewData = (Button) findViewById(R.id.Button03);
28.                  btViewData.setOnClickListener(new ClickViewHandler());
29.                  btDelOne = (Button) findViewById(R.id.Button04);
30.                  btDelOne.setOnClickListener(new ClickViewHandler());
31.                  btClearAll = (Button) findViewById(R.id.Button05);
32.                  btClearAll.setOnClickListener(new ClickViewHandler());
33.          }
34. 35.          public class ClickViewHandler implements OnClickListener {
36.                  @Override
37.                  public void onClick(View v) {
38.                          if (v == btInsertData) {
39.                                  InsertSomeRecords();
40.                          } else if (v == btViewData) {
41.                                  ViewRecords();
42.                          } else if (v == btDelOne) {
43.                                  DelOne();
44.                          } else if (v == btClearAll) {
45.                                  DelAllPeople();
46.                          }
47.                  }
48. 49.                  private void DelAllPeople() {
50.                          getContentResolver().delete(MyContacts.CONTENT_URI, null, null);
51.                  }
52. 53.                  private void DelOne() {
54.                          int id;
55.                          Cursor c = getContentResolver().query(
56.                                          MyContacts.CONTENT_URI, null, 
57.                                          null, null, MyContacts.NAME + " ASC");
58.                          if (c.moveToFirst()) {
59.                                  int idxID = c.getColumnIndex(MyContacts._ID);
60.                                  id = c.getInt(idxID);
61.                                  getContentResolver().delete(MyContacts.CONTENT_URI, 
62.                                                  MyContacts._ID + " = " + id, null);
63.                          }
64.                  }
65. 66.                  private void ViewRecords() {
67.                          // Make the query
68.                          Cursor c = managedQuery(MyContacts.CONTENT_URI, null, null, null,
69.                                          MyContacts._ID);
70.                          StringBuilder sbRecords = new StringBuilder("");
71.                          if (c.moveToFirst()) {
72.                                  int idxID = c.getColumnIndex(MyContacts._ID);
73.                                  int idxName = c.getColumnIndex(MyContacts.NAME);
74.                                  int idxNumber = c.getColumnIndex(MyContacts.NUMBER1);
75.                                  int idxEmail = c.getColumnIndex(MyContacts.EMAIL);
76.                                  // Iterator the records
77.                                  do {
78.                                          sbRecords.append(c.getInt(idxID));
79.                                          sbRecords.append(". ");
80.                                          sbRecords.append(c.getString(idxName));
81.                                          sbRecords.append(", ");
82.                                          sbRecords.append(c.getString(idxNumber));
83.                                          sbRecords.append(", ");
84.                                          sbRecords.append(c.getString(idxEmail));
85.                                          sbRecords.append("/n");
86.                                  } while (c.moveToNext());
87.                          }
88.                          c.close();
89.                          // Refresh the content of TextView
90.                          ((TextView)(findViewById(
91.                                          R.id.TextView01))).setText(sbRecords);
92.                  }
93. 94.                  private void InsertSomeRecords() {
95.                          ContentValues values = new ContentValues();
96.                          values.put(MyContacts.NAME, "朱元璋");
97.                          values.put(MyContacts.NUMBER1, "13965625585");
98.                          getContentResolver().insert(MyContacts.CONTENT_URI, values);
99.                          values.clear();
100.                          values.put(MyContacts.NAME, "玄烨");
101.                          values.put(MyContacts.EMAIL, "xueye1772@gmail.com");
102.                          getContentResolver().insert(MyContacts.CONTENT_URI, values);
103.                  }
104.          }
105.  }



复制代码



代码中通过getContentResolver()调用contentprovider的delete、insert等操作,由系统根据uri决定调用哪个provider。

query查询除了用getContentResolver().query以外,还可以用activity.managedQuery调用,后者的Cursor的生命周期是由activity来自动管理,本文中的query就是属于这种情况。

主界面ui定义,main.xml,代码:




1.  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2.          android:orientation="vertical" android:layout_width="fill_parent"
3.          android:layout_height="fill_parent">
4.          <TextView android:layout_width="fill_parent"
5.                  android:layout_height="wrap_content" android:text="@string/hello" />
6.          <TableRow android:id="@+id/TableRow01" android:layout_width="wrap_content"
7.                  android:layout_height="wrap_content">
8.                  <Button android:text="insert some records" android:id="@+id/Button02"
9.                          android:height="30px" android:layout_width="wrap_content"
10.                          android:layout_height="wrap_content" />
11.          </TableRow>
12.          <TableRow android:id="@+id/TableRow01" android:layout_width="wrap_content"
13.                  android:layout_height="wrap_content">
14.                  <Button android:text="delete one" android:id="@+id/Button04"
15.                          android:height="30px" android:layout_width="wrap_content"
16.                          android:layout_height="wrap_content" />
17.                  <Button android:text="clear all" android:id="@+id/Button05"
18.                          android:height="30px" android:layout_width="wrap_content"
19.                          android:layout_height="wrap_content" />
20.          </TableRow>
21.          <Button android:text="view records" android:id="@+id/Button03"
22.                  android:height="30px" android:layout_width="wrap_content"
23.                  android:layout_height="wrap_content" android:layout_gravity="center" />
24.          <TextView android:text="..." android:id="@+id/TextView01"
25.                  android:layout_width="wrap_content" android:layout_height="wrap_content" />
26.  </LinearLayout>



复制代码



MyContentProvider.java,代码:




1.  package jtapp.contentproviders;
2.  import android.content.ContentProvider;
3.  import android.content.ContentUris;
4.  import android.content.ContentValues;
5.  import android.content.Context;
6.  import android.database.Cursor;
7.  import android.database.SQLException;
8.  import android.database.sqlite.SQLiteDatabase;
9.  import android.database.sqlite.SQLiteQueryBuilder;
10.  import android.database.sqlite.SQLiteStatement;
11.  import android.net.Uri;
12.  import android.text.TextUtils;
13.  import android.util.Log;
14. 15.  public class MyContactsProvider extends ContentProvider {
16.          private static final String TAG = "MyContactsProvider"; 
17.          private static SQLiteDatabase mDB;
18. 19.          private static void createTablesIfNotExists() {
20.                  mDB.execSQL("CREATE TABLE IF NOT EXISTS "    
21.                  + MyContacts.TB_NAME + " ("    
22.                  + MyContacts._ID + " INTEGER PRIMARY KEY,"    
23.                  + MyContacts.NAME + " VARCHAR,"  
24.                  + MyContacts.NUMBER1 + " VARCHAR,"
25.                  + MyContacts.EMAIL + " VARCHAR)");
26.          }
27. 28.          @Override
29.          public int delete(Uri uri, String selection, String[] selectionArgs) {
30.                  int count;
31.                  switch (MyContacts.uriMatcher.match(uri)) {
32.                          case MyContacts.CONTACTS:
33.                                  count = mDB.delete(MyContacts.TB_NAME, 
34.                                                  selection, selectionArgs);
35.                                  break;
36.                          case MyContacts.CONTACT_ID:
37.                                  String contactID = uri.getPathSegments().get(1);
38.                                  count = mDB.delete(MyContacts.TB_NAME, 
39.                                                  MyContacts._ID + "=" + contactID, selectionArgs);
40.                                  break;
41.                          default: throw new IllegalArgumentException(
42.                                          "Unsupported URI: " + uri);
43.                  }
44.                  return count;
45.          }
46. 47.          @Override
48.          public String getType(Uri uri) {
49.                  switch (MyContacts.uriMatcher.match(uri)) {
50.                          case MyContacts.CONTACTS:
51.                          return "vnd.android.cursor.dir/vnd.jtapp.contacts";
52.                          case MyContacts.CONTACT_ID:
53.                          return "vnd.android.cursor.item/vnd.ambow.contacts";
54.                          default:
55.                          throw new IllegalArgumentException("Unsupported URI: " + uri);
56.                  }
57.          }
58. 59.          @Override
60.          public Uri insert(Uri uri, ContentValues contentValues) {
61.                  long rowId = mDB.insert(MyContacts.TB_NAME, null, contentValues);
62.                  if (rowId > 0) {
63.                          Uri noteUri = 
64.                                  ContentUris.withAppendedId(MyContacts.CONTENT_URI,rowId);
65.                          getContext().getContentResolver().notifyChange(noteUri, null);
66.                          Log.d(TAG+"insert",noteUri.toString());
67.                          return noteUri;
68.                  } else {
69.                      throw new SQLException("Failed to insert row into " + uri);
70.                  }
71.          }
72. 73.          @Override
74.          public boolean onCreate() {
75.                  if (mDB == null) {
76.                          mDB = this.getContext().openOrCreateDatabase(MyContacts.TB_NAME,
77.                                          Context.MODE_PRIVATE, null);
78.                          createTablesIfNotExists();
79.                  }
80.                  return mDB != null;
81.          }
82. 83.          @Override
84.          public Cursor query(Uri uri, String[] projection, String selection,
85.                          String[] selectionArgs, String sortOrder) {
86.                  SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
87.                  qb.setTables(MyContacts.TB_NAME);
88.                  
89.                  switch (MyContacts.uriMatcher.match(uri)) {
90.                          case MyContacts.CONTACT_ID:
91.                                  qb.appendWhere(
92.                                                  MyContacts._ID + "=" + uri.getPathSegments().get(1));
93.                                  break;
94.                  }
95.                  String orderBy;
96.                  if (TextUtils.isEmpty(sortOrder)) {
97.                          orderBy = MyContacts._ID;
98.                  } else {
99.                          orderBy = sortOrder;
100.                  }
101.                  Cursor c = qb.query(mDB, projection, selection, 
102.                                  selectionArgs,null, null,orderBy);
103.                  return c;        
104.          }
105. 106.          @Override
107.          public int update(Uri uri, ContentValues contentValues, String selection,
108.                          String[] selectionArgs) {
109. 110.                  Log.d(TAG+"update",contentValues.toString());
111.                  Log.d(TAG+"update",uri.toString());
112.                  
113.                  int count;                
114.                  switch (MyContacts.uriMatcher.match(uri)) {
115.                          case MyContacts.CONTACTS:
116.                                  Log.d(TAG+"update",MyContacts.CONTACTS+"");
117.                                  count = mDB.update(
118.                                                  MyContacts.TB_NAME, contentValues, 
119.                                                  selection, selectionArgs);
120.                                  break;
121.                          case MyContacts.CONTACT_ID:
122.                                  String contactID = uri.getPathSegments().get(1);
123.                                  Log.d(TAG+"update",contactID+"");
124.                                  count = mDB.update(MyContacts.TB_NAME,contentValues,
125.                                                  MyContacts._ID + "=" + contactID, 
126.                                                  selectionArgs);
127.                                  break;
128.                          default: throw new IllegalArgumentException(
129.                                          "Unsupported URI: " + uri);
130.                  }
131.                  return count;
132.          }
133.  }



复制代码


MyContentProvider扩展了android.content.ContentProvider类,数据层的操作就在该类中完成。sqlite、文件形式、内存对象,持久化或者非持久化数据存储都可以包装在此类中,本文中使用了sqlite数据库进行持久化存储。

getContext().getContentResolver().notifyChange可以通知观察者数据发生改变,这是处理多应用调用时数据同步的关键,本文并没有完整的展现。自定义的MyContentProvider.java中还实现了update方法,在AcMain中没有调用到,你可尝试一下如何实现。


与MyContentProvider有关的表mycontacts相对应类MyContacts.java,其中定义了唯一资源标示URI,表结构字段名等。任何contentprovider的调用都是通过系统来进行,因此,URI起到了定位资源和特定provider类的作用。


1.  //MyContacts.java
2.  package jtapp.contentproviders;
3. 4.  import android.content.UriMatcher;
5.  import android.net.Uri;
6.  import android.provider.BaseColumns;
7. 8.  public class MyContacts implements BaseColumns {
9.          public MyContacts(){
10.          }
11.          public static final String AUTHORITY = 
12.                  "jtapp.contentproviders.contacts";
13.          public static final String TB_NAME = "mycontacts";
14.          public static final Uri CONTENT_URI = Uri.parse(
15.                  "content://" + AUTHORITY + "/" + TB_NAME);
16.          
17.          public static final int CONTACTS = 1;
18.          public static final int CONTACT_ID = 2;
19.          public static final UriMatcher uriMatcher;
20.          static{
21.                  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
22.                  uriMatcher.addURI(AUTHORITY,"mycontacts",CONTACTS);
23.                  uriMatcher.addURI(AUTHORITY,"mycontacts/#",CONTACT_ID);
24.          }
25.          
26.          public static final String _ID = "id";
27.          public static final String NAME = "name";
28.          public static final String NUMBER1 = "number1";
29.          public static final String EMAIL = "email";
30.  }



复制代码



应用配置文件AndroidManifest.xml 代码:




1.  <?xml version="1.0" encoding="utf-8"?>
2.  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3.          package="jtapp.contentproviders" android:versionCode="1"
4.          android:versionName="1.0">
5.          <application android:icon="@drawable/icon" android:label="@string/app_name">
6.                  <activity android:name=".AcMain" android:label="@string/app_name">
7.                          <intent-filter>
8.                                  <action android:name="android.intent.action.MAIN" />
9.                                  <category android:name="android.intent.category.LAUNCHER" />
10.                          </intent-filter>
11.                  </activity>
12.                  <provider android:authorities="jtapp.contentproviders.contacts"
13.                          android:name="MyContactsProvider"></provider>
14.          </application>
15.  </manifest>



复制代码