//此系列博文是《第一行Android代码》的学习笔记,如有错漏,欢迎指正!
我们继续在Database项目的基础上继续开发,通过内容提供器来给它加入外部访问接口。首先将 MyDatabaseHelper 中使用 Toast弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用 Toast。
一、添加一个 DatabaseProvider 类:
1 public class DatabaseProvider extends ContentProvider { 2 public static final int BOOK_DIR = 0; 3 public static final int BOOK_ITEM = 1; 4 public static final int CATEGORY_DIR = 2; 5 public static final int CATEGORY_ITEM = 3; 6 public static final String AUTHORITY = "com.mycompany.database.provider"; 7 private static UriMatcher uriMatcher; 8 private MyDatabaseHelper dbHelper; 9 static { 10 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 11 uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR); 12 uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM); 13 uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR); 14 uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM); 15 } 16 @Override 17 public boolean onCreate() { 18 dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2); 19 return true; 20 } 21 @Override 22 public Cursor query(Uri uri, String[] projection, String selection, 23 String[] selectionArgs, String sortOrder) { 24 // 查询数据 25 SQLiteDatabase db = dbHelper.getReadableDatabase(); 26 Cursor cursor = null; 27 switch (uriMatcher.match(uri)) { 28 case BOOK_DIR: 29 cursor = db.query("Book", projection, selection, selectionArgs, 30 null, null, sortOrder); 31 break; 32 case BOOK_ITEM: 33 String bookId = uri.getPathSegments().get(1); 34 cursor = db.query("Book", projection, "id = ?", new String[] 35 { bookId }, null, null, sortOrder); 36 break; 37 case CATEGORY_DIR: 38 cursor = db.query("Category", projection, selection, 39 selectionArgs, null, null, sortOrder); 40 break; 41 case CATEGORY_ITEM: 42 String categoryId = uri.getPathSegments().get(1); 43 cursor = db.query("Category", projection, "id = ?", new String[] 44 { categoryId }, null, null, sortOrder); 45 break; 46 default: 47 break; 48 } 49 return cursor; 50 } 51 @Override 52 public Uri insert(Uri uri, ContentValues values) { 53 // 添加数据 54 SQLiteDatabase db = dbHelper.getWritableDatabase(); 55 Uri uriReturn = null; 56 switch (uriMatcher.match(uri)) { 57 case BOOK_DIR: 58 case BOOK_ITEM: 59 long newBookId = db.insert("Book", null, values); 60 uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId); 61 break; 62 case CATEGORY_DIR: 63 case CATEGORY_ITEM: 64 long newCategoryId = db.insert("Category", null, values); 65 uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + 66 newCategoryId); 67 break; 68 default: 69 break; 70 } 71 return uriReturn; 72 } 73 @Override 74 public int update(Uri uri, ContentValues values, String selection, 75 String[] selectionArgs) { 76 // 更新数据 77 SQLiteDatabase db = dbHelper.getWritableDatabase(); 78 int updatedRows = 0; 79 switch (uriMatcher.match(uri)) { 80 case BOOK_DIR: 81 updatedRows = db.update("Book", values, selection, selectionArgs); 82 break; 83 case BOOK_ITEM: 84 String bookId = uri.getPathSegments().get(1); 85 updatedRows = db.update("Book", values, "id = ?", new String[] 86 { bookId }); 87 break; 88 case CATEGORY_DIR: 89 updatedRows = db.update("Category", values, selection, 90 selectionArgs); 91 break; 92 case CATEGORY_ITEM: 93 String categoryId = uri.getPathSegments().get(1); 94 updatedRows = db.update("Category", values, "id = ?", new String[] 95 { categoryId }); 96 break; 97 default: 98 break; 99 } 100 return updatedRows; 101 } 102 @Override 103 public int delete(Uri uri, String selection, String[] selectionArgs) { 104 // 删除数据 105 SQLiteDatabase db = dbHelper.getWritableDatabase(); 106 int deletedRows = 0; 107 switch (uriMatcher.match(uri)) { 108 case BOOK_DIR: 109 deletedRows = db.delete("Book", selection, selectionArgs); 110 break; 111 case BOOK_ITEM: 112 String bookId = uri.getPathSegments().get(1); 113 deletedRows = db.delete("Book", "id = ?", new String[] { bookId }); 114 break; 115 case CATEGORY_DIR: 116 deletedRows = db.delete("Category", selection, selectionArgs); 117 break; 118 case CATEGORY_ITEM: 119 String categoryId = uri.getPathSegments().get(1); 120 deletedRows = db.delete("Category", "id = ?", new String[] 121 { categoryId }); 122 break; 123 default: 124 break; 125 } 126 return deletedRows; 127 } 128 @Override 129 public String getType(Uri uri) { 130 switch (uriMatcher.match(uri)) { 131 case BOOK_DIR: 132 return "vnd.android.cursor.dir/vnd.com.mycompany.database.provider.book"; 133 case BOOK_ITEM: 134 return "vnd.android.cursor.item/vnd.com.mycompany.database.provider.book"; 135 case CATEGORY_DIR: 136 return "vnd.android.cursor.dir/vnd.com.mycompany.database.provider.category"; 137 case CATEGORY_ITEM: 138 return "vnd.android.cursor.item/vnd.com.mycompany.database.provider.category"; 139 } 140 return null; 141 } 142 }
代码中我们主要重写了CRUD操作:
1)query()方法:在这个方法中先获取到了 SQLiteDatabase 的实例,然后根据传入的 Uri参数判断出用户想要访问哪张表,再调用 SQLiteDatabase 的 query()进行查询,并将 Cursor 对象返回就好了。注意当访问单条数据的时候有一个细节,这里调用了 Uri 对象的getPathSegments()方法,它会将内容 URI权限之后的部分以“/”符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第 0 个位置存放的就是路径,第 1个位置存放的就是 id 了。得到了 id 之后,再通过 selection 和 selectionArgs 参数进行约束,就实现了查询单条数据的功能。
2)insert()方法: 同样先获取到了 SQLiteDatabase 的实例, 然后根据传入的 Uri参数判断出用户想要往哪张表里添加数据, 再调用 SQLiteDatabase 的 insert()方法进行添加。 注意 insert()方法要求返回一个能够表示这条新增数据的 URI, 所以我们还需要调用 Uri.parse()方法来将一个内容 URI解析成 Uri对象,当然这个内容 URI是以新增数据的 id 结尾的。
3)update()方法:先获取SQLiteDatabase 的实例,然后根据传入的 Uri 参数判断出用户想要更新哪张表里的数据,再调用 SQLiteDatabase的 update()方法进行更新,最后将受影响的行数作为返回值返回。
4)delete()方法:仍然是先获取到 SQLiteDatabase 的实例,然后根据传入的 Uri 参数判断出用户想要删除哪张表里的数据,再调用 SQLiteDatabase 的 delete()方法进行删除就好了,被删除的行数将作为返回值返回。
二、在AndroidManifest.xml文件中进行注册:
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.mycompany.database" > 4 5 <application 6 android:allowBackup="true" 7 android:icon="@mipmap/ic_launcher" 8 android:label="@string/app_name" 9 android:supportsRtl="true" 10 android:theme="@style/AppTheme" > 11 <activity android:name=".MainActivity" > 12 <intent-filter> 13 <action android:name="android.intent.action.MAIN" /> 14 15 <category android:name="android.intent.category.LAUNCHER" /> 16 </intent-filter> 17 </activity> 18 <provider 19 android:name="com.mycompany.database.DatabaseProvider" 20 android:authorities="com.mycompany.database.provider" > 21 </provider> 22 </application> 23 24 </manifest>
这里我们使用了<provider>标签来对 DatabaseProvider 这个内容提供器进行注册,在 android:name 属性中指定了该类的全名,又在 android:authorities 属性中指定了该内容提供器的权限。现在 Database这个项目就已经拥有了跨程序共享数据的功能了。接着我们需要将 Database 程序从手机中删除掉,以防止上次中产生的遗留数据对我们造成干扰。然后运行一下项目,将Database程序重新安装在手机了。接着关闭掉Databaset 这个项目,并创建一个新项目 ProviderTest,我们就将通过这个程序去访问Database中的数据。
三、编写ProviderTest的布局文件:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" 3 android:orientation="vertical" 4 android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" 5 android:paddingRight="@dimen/activity_horizontal_margin" 6 android:paddingTop="@dimen/activity_vertical_margin" 7 android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> 8 9 <Button 10 android:id="@+id/add_data" 11 android:layout_width="match_parent" 12 android:layout_height="wrap_content" 13 android:text="Add To Book" /> 14 <Button 15 android:id="@+id/query_data" 16 android:layout_width="match_parent" 17 android:layout_height="wrap_content" 18 android:text="Query From Book" /> 19 <Button 20 android:id="@+id/update_data" 21 android:layout_width="match_parent" 22 android:layout_height="wrap_content" 23 android:text="Update Book" /> 24 <Button 25 android:id="@+id/delete_data" 26 android:layout_width="match_parent" 27 android:layout_height="wrap_content" 28 android:text="Delete From Book" /> 29 </LinearLayout>
布局文件比较简单,里面放置了四个按钮,分别用于添加、查询、修改和删除数据的。
四、修改ProviderTest主代码:
1 public class MainActivity extends AppCompatActivity { 2 3 private String newId; 4 @Override 5 protected void onCreate(Bundle savedInstanceState) { 6 super.onCreate(savedInstanceState); 7 setContentView(R.layout.activity_main); 8 Button addData = (Button) findViewById(R.id.add_data); 9 addData.setOnClickListener(new View.OnClickListener() { 10 @Override 11 public void onClick(View v) { 12 // 添加数据 13 Uri uri = Uri.parse("content://com.mycompany.database.provider/book"); 14 ContentValues values = new ContentValues(); 15 values.put("name", "A Clash of Kings"); 16 values.put("author", "George Martin"); 17 values.put("pages", 1040); 18 values.put("price", 22.85); 19 Uri newUri = getContentResolver().insert(uri, values); 20 newId = newUri.getPathSegments().get(1); 21 } 22 }); 23 Button queryData = (Button) findViewById(R.id.query_data); 24 queryData.setOnClickListener(new View.OnClickListener() { 25 @Override 26 public void onClick(View v) { 27 // 查询数据 28 Uri uri = Uri.parse("content://com.mycompany.database.provider/book"); 29 Cursor cursor = getContentResolver().query(uri, null, null, null, null); 30 if (cursor != null) { 31 while (cursor.moveToNext()) { 32 String name = cursor.getString(cursor. 33 getColumnIndex("name")); 34 String author = cursor.getString(cursor. 35 getColumnIndex("author")); 36 int pages = cursor.getInt(cursor.getColumnIndex 37 ("pages")); 38 double price = cursor.getDouble(cursor. 39 getColumnIndex("price")); 40 Log.d("MainActivity", "book name is " + name); 41 Log.d("MainActivity", "book author is " + author); 42 Log.d("MainActivity", "book pages is " + pages); 43 Log.d("MainActivity", "book price is " + price); 44 } 45 cursor.close(); 46 } 47 } 48 }); 49 Button updateData = (Button) findViewById(R.id.update_data); 50 updateData.setOnClickListener(new View.OnClickListener() { 51 @Override 52 public void onClick(View v) { 53 // 更新数据 54 Uri uri = Uri.parse("content://com.mycompany.database.provider/book/" + newId); 55 ContentValues values = new ContentValues(); 56 values.put("name", "A Storm of Swords"); 57 values.put("pages", 1216); 58 values.put("price", 24.05); 59 getContentResolver().update(uri, values, null, null); 60 } 61 }); 62 Button deleteData = (Button) findViewById(R.id.delete_data); 63 deleteData.setOnClickListener(new View.OnClickListener() { 64 @Override 65 public void onClick(View v) { 66 // 删除数据 67 Uri uri = Uri.parse("content://com.mycompany.database.provider/book/" + newId); 68 getContentResolver().delete(uri, null, null); 69 } 70 }); 71 } 72 }
我们分别在这四个按钮的点击事件里面处理了增删改查的逻辑。
1)Add To Book: 添加数据的时候,首先调用了 Uri.parse()方法将一个内容 URI解析成 Uri对象,然后把要添加的数据都存放到 ContentValues对象中,接着调用 ContentResolver 的 insert()方法执行添加操作就可以了。注意 insert()方法会返回一个 Uri 对象,这个对象中包含了新增数据的 id,我们通过getPathSegments()方法将这个 id 取出,稍后会用到它。
2)Query From Book: 查询数据的时候,同样是调用了 Uri.parse()方法将一个内容 URI解析成 Uri对象,然后调用 ContentResolver 的 query()方法去查询数据,查询的结果当然还是存放在 Cursor对象中的。之后对 Cursor 进行遍历,从中取出查询结果,并一一打印出来。
3)Update Book: 更新数据的时候,也是先将内容 URI 解析成 Uri 对象,然后把想要更新的数据存放到ContentValues对象中,再调用 ContentResolver 的 update()方法执行更新操作就可以了。注意这里我们为了不想让 Book 表中其他的行受到影响,在调用 Uri.parse()方法时,给内容 URI的尾部增加了一个 id,而这个 id 正是添加数据时所返回的。这就表示我们只希望更新刚刚添加的那条数据,Book 表中的其他行都不会受影响。
4)Delete From Book: 删除数据的时候,也是使用同样的方法解析了一个以 id 结尾的内容 URI,然后调用ContentResolver 的 delete()方法执行删除操作就可以了。由于我们在内容 URI 里指定了一个id,因此只会删掉拥有相应 id 的那行数据,Book 表中的其他数据都不会受影响。
程序运行效果如下:
按下Add按钮后,再按下查询,在Android Monitor中可看到:
04-25 21:49:18.327 31285-31285/com.mycompany.database D/MainActivity: book name is A Clash of Kings 04-25 21:49:18.327 31285-31285/com.mycompany.database D/MainActivity: book author is George Martin 04-25 21:49:18.327 31285-31285/com.mycompany.database D/MainActivity: book pages is 1040 04-25 21:49:18.337 31285-31285/com.mycompany.database D/MainActivity: book price is 22.85
按下Update后,再按下查询,可看到:
04-25 22:11:10.768 31285-31285/com.mycompany.database D/MainActivity: book name is A Storm of Swords 04-25 22:11:10.768 31285-31285/com.mycompany.database D/MainActivity: book author is George Martin 04-25 22:11:10.768 31285-31285/com.mycompany.database D/MainActivity: book pages is 1216 04-25 22:11:10.768 31285-31285/com.mycompany.database D/MainActivity: book price is 24.05
当按下Delete后,再按下查询,此时已没有数据显示了。
//End.