创建自己的内容提供器很简单,只需要新建一个类继承ContentProvider类,并重写其中的6个抽象方法即可。(需要全部重写)
一 URI的写法
一个标准的内容URI写法有两种:
1.路径结尾,表示访问表中所有内容:
content://com.example.app.provider/table1 // * 可以代表任意长度的任意字符,所以也可以写成下面这样 content://com.example.app.provider/*
2.id结尾,表示访问表中特定id的内容:
content://com.example.app.provider/table1/1 // # 可以代表任意长度的数字,所以也可以写成下面这样 content://com.example.app.provider/table1/#
二 借助UriMatcher类
在UriMatcher类中提供了addURI()方法,传入三个参数,分别是authority,path,和一个自定义代码。
在UriMatcher类中提供了match()方法,传入一个Uri对象,返回值就是addUri中该uri对应的自定义代码。利用这个自定义代码,我们就可以判断出调用期望访问的数据。
代码如下:
1 public class MyProvider extends ContentProvider { 2 3 4 public static final int TABLE1_DIR = 0; //访问表1的所有数据 5 public static final int TABLE1_ITEM = 1; //访问表1的单条数据 6 public static final int TABLE2_DIR = 2; 7 public static final int TABLE2_ITEM = 3; 8 9 private static UriMatcher uriMatcher; 10 11 /*静态代码块,通过adduri方法,将期望匹配的uri格式传入,并定义返回的自定义代码*/ 12 static { 13 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 14 uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR); 15 uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM); 16 uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR); 17 uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM); 18 } 19 20 21 @Override 22 public boolean onCreate() { 23 return false; 24 } 25 26 @Nullable 27 @Override 28 public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) { 29 switch (uriMatcher.match(uri)){ 30 case TABLE1_DIR: 31 //查询table1表中的所有数据 32 break; 33 case TABLE1_ITEM: 34 //查询table1表中的单条数据 35 break; 36 case TABLE2_DIR: 37 //查询table2表中的所有数据 38 break; 39 case TABLE2_ITEM: 40 //查询table2表中的单条数据 41 break; 42 default: 43 break; 44 } 45 return null; 46 } 47 ··· 48 )
除此之外,还有重写getType()方法,用于获得uri对象对应的mime类型,一个内容URI对应的MIME字符串又三部分组成:
//如果是路径结尾 vnd. + android.cursor.dir/ + vnd.<authority>.<paht> //如: vnd.android.cursor.dir/vnd.com.example.app.provider.table1 //如果是id结尾 vnd. + android.cursor.item/ + vnd.<authority>.<paht> //如 vnd.android.cursor.item/vnd.com.example.app.provider.table1
代码如下:
1 @Override 2 public String getType(@NonNull Uri uri) { 3 switch (uriMatcher.match(uri)){ 4 case TABLE1_DIR: 5 return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"; 6 case TABLE1_ITEM: 7 return "vnd.android.cursor.item/vnd.com.example.app.provider.table1"; 8 case TABLE2_DIR: 9 return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"; 10 case TABLE2_ITEM: 11 return "vnd.android.cursor.item/vnd.com.example.app.provider.table2"; 12 default: 13 break; 14 } 15 return null; 16 }
三 实例使用内容提供器
构造一个数据库的内容提供器如下:
1 public class MyContentProvider extends ContentProvider { 2 public MyContentProvider() { 3 } 4 5 public static final int BOOK_DIR = 0; 6 public static final int BOOK_ITEM = 1; 7 public static final int CATEGORY_DIR = 2; 8 public static final int CATEGORY_ITEM = 3; 9 10 public static final String AUTHORITY = "com.example.databasetest,provider"; 11 private static UriMatcher uriMatcher; 12 private MyDatabaseHelper dbHelper; 13 14 static { 15 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 16 uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR); 17 uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM); 18 uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR); 19 uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM); 20 } 21 22 @Override 23 public boolean onCreate() { 24 // TODO: Implement this to initialize your content provider on startup. 25 dbHelper = new MyDatabaseHelper(getContext(), "BookStore,db", null, 2); 26 return true; 27 } 28 29 @Override 30 public Cursor query(Uri uri, String[] projection, String selection, 31 String[] selectionArgs, String sortOrder) { 32 // TODO: Implement this to handle query requests from clients. 33 Cursor cursor = null; 34 SQLiteDatabase db = dbHelper.getReadableDatabase(); 35 switch (uriMatcher.match(uri)) { 36 case BOOK_DIR: 37 cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder); 38 break; 39 case BOOK_ITEM: 40 //getPathSegments将path部分以/为界分开,并将其放入字符串数据中,所以0对应path路径,1对应具体id 41 String bookId = uri.getPathSegments().get(1); 42 cursor = db.query("Book", projection, "id = ?", new String[]{bookId}, null, null, sortOrder); 43 break; 44 case CATEGORY_DIR: 45 cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder); 46 break; 47 case CATEGORY_ITEM: 48 String categoryId = uri.getPathSegments().get(1); 49 cursor = db.query("Category", projection, "id = ?", new String[]{categoryId}, null, null, sortOrder); 50 break; 51 default: 52 break; 53 } 54 return cursor; 55 } 56 57 @Override 58 public Uri insert(Uri uri, ContentValues values) { 59 // TODO: Implement this to handle requests to insert a new row. 60 SQLiteDatabase db = dbHelper.getWritableDatabase(); 61 Uri uriReturn = null; 62 switch (uriMatcher.match(uri)) { 63 case BOOK_DIR: 64 case BOOK_ITEM: 65 long newBookId = db.insert("Book", null, values); 66 uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId); 67 break; 68 case CATEGORY_DIR: 69 case CATEGORY_ITEM: 70 long newCategoryId = db.insert("Category", null, values); 71 uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId); 72 break; 73 default: 74 break; 75 } 76 return uriReturn; 77 } 78 79 @Override 80 public int update(Uri uri, ContentValues values, String selection, 81 String[] selectionArgs) { 82 // TODO: Implement this to handle requests to update one or more rows. 83 SQLiteDatabase db = dbHelper.getWritableDatabase(); 84 int updatedRows = 0; 85 switch (uriMatcher.match(uri)) { 86 case BOOK_DIR: 87 updatedRows = db.update("Book", values, selection, selectionArgs); 88 break; 89 case BOOK_ITEM: 90 String bookId = uri.getPathSegments().get(1); 91 updatedRows = db.update("Book", values, "id=?", new String[]{bookId}); 92 break; 93 case CATEGORY_DIR: 94 updatedRows = db.update("Category", values, selection, selectionArgs); 95 break; 96 case CATEGORY_ITEM: 97 String CategoryId = uri.getPathSegments().get(1); 98 updatedRows = db.update("Category", values, "id=?", new String[]{CategoryId}); 99 break; 100 default: 101 break; 102 } 103 return updatedRows; 104 } 105 106 @Override 107 public int delete(Uri uri, String selection, String[] selectionArgs) { 108 // Implement this to handle requests to delete one or more rows. 109 SQLiteDatabase db = dbHelper.getWritableDatabase(); 110 int deletedRows = 0; 111 switch (uriMatcher.match(uri)) { 112 case BOOK_DIR: 113 deletedRows = db.delete("Book", selection, selectionArgs); 114 break; 115 case BOOK_ITEM: 116 String deletedBookId = uri.getPathSegments().get(1); 117 deletedRows = db.delete("Book", "id=?", new String[]{deletedBookId}); 118 break; 119 case CATEGORY_DIR: 120 deletedRows = db.delete("Category", selection, selectionArgs); 121 break; 122 case CATEGORY_ITEM: 123 String deletedCategoryId = uri.getPathSegments().get(1); 124 deletedRows = db.delete("Category", "id=?", new String[]{deletedCategoryId}); 125 break; 126 default: 127 break; 128 } 129 return deletedRows; 130 } 131 132 @Override 133 public String getType(Uri uri) { 134 // TODO: Implement this to handle requests for the MIME type of the data 135 // at the given URI.\ 136 switch (uriMatcher.match(uri)) { 137 case BOOK_DIR: 138 return "vnd.android.cursor.dir/vnd.com.example.app.provider.Book"; 139 case BOOK_ITEM: 140 return "vnd.android.cursor.item/vnd.com.example.app.provider.Book"; 141 case CATEGORY_DIR: 142 return "vnd.android.cursor.dir/vnd.com.example.app.provider.category"; 143 case CATEGORY_ITEM: 144 return "vnd.android.cursor.item/vnd.com.example.app.provider.category"; 145 default: 146 break; 147 } 148 return null; 149 150 } 151 }
代码整体可以分为几部分,首先是新建常量,用于访问book和category里中的数据,在静态代码块里对UriMatcher进行了addUri的配置。
在onCreate()方法中,创建了一个MyDatabaseHelper的实例,返回true表示初始化成功。
查询query()方法中,先获取到了SQLiteDatabase的实例,传入uri参数知道用户想访问的数据,然后在调用sqlitedatabase的query()方法进行查询,并将查询的cursor参数返回。如果是访问单条的数据,使用getPathSegments将path部分以“/”为界分开,并将其放入字符串数据中,所以0对应path路径,1对应具体id。得到id通过selection和selectionArgs来进行约束查询。最后返回cursor值。
新增insert()方法中,先获取到了SQLiteDatabase的实例,传入uri参数知道用户想往那张表加数据,然后在调用SQliteDatabase的insert()方法进行添加,返回的是一个能够表示这个新增数据的uri,这个内容uri是以新增数据的id结尾。所以还需要调用uri.parse()方法将其进行解析成uri对象。
更新update()方法中,先获取到了SQLiteDatabase的实例,传入uri参数知道用户想访问的数据,然后在调用sqlitedatabase的update()方法进行更新,受影响的行数作为返回值返回。
删除delete()方法中,先获取到了SQLiteDatabase的实例,传入uri参数知道用户想访问的数据,然后在调用sqlitedatabase的delete()方法进行删除,被删除的行数作为返回值返回。
最后是getType()方法,和之前的getType方法完全相同。
另外,内容提供器需要在AndroidManifest.xml中注册,一般as已经帮我们进行注册好了。
构建另外一个应用来使用我们的databasetest的内容提供器,新建一个应用,页面为四个按钮,布局如下:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical"> 5 6 <Button 7 android:id="@+id/add_data" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:text="add to book"/> 11 12 <Button 13 android:id="@+id/query_data" 14 android:text="query from book" 15 android:layout_width="match_parent" 16 android:layout_height="wrap_content" /> 17 <Button 18 android:id="@+id/update_data" 19 android:text="update book" 20 android:layout_width="match_parent" 21 android:layout_height="wrap_content" /> 22 <Button 23 android:id="@+id/delete_data" 24 android:text="delete from book" 25 android:layout_width="match_parent" 26 android:layout_height="wrap_content" /> 27 </LinearLayout>
然后在主函数中为四个按钮注册点击事件,并赋予相应的逻辑。
1 public class MainActivity extends AppCompatActivity { 2 3 private String newId; 4 5 static final String path = "content://com.example.databasetest.provider/book"; 6 7 @Override 8 protected void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); 10 setContentView(R.layout.activity_main); 11 12 Button addData = (Button) findViewById(R.id.add_data); 13 addData.setOnClickListener(new View.OnClickListener() { 14 @Override 15 public void onClick(View view) { 16 Uri uri = Uri.parse(path); 17 ContentValues values = new ContentValues(); 18 values.put("name", "a *"); 19 values.put("author", "Mask"); 20 values.put("pages", 1002); 21 values.put("price", 12.22); 22 Uri newUri = getContentResolver().insert(uri, values); 23 newId = newUri.getPathSegments().get(1); 24 } 25 }); 26 27 Button queryData = (Button) findViewById(R.id.query_data); 28 queryData.setOnClickListener(new View.OnClickListener() { 29 @Override 30 public void onClick(View view) { 31 Cursor cursor = null; 32 Uri uri = Uri.parse(path); 33 cursor = getContentResolver().query(uri, null, null, null, null); 34 if (cursor != null) { 35 while (cursor.moveToNext()) { 36 String name = cursor.getString(cursor.getColumnIndex("name")); 37 String author = cursor.getString(cursor.getColumnIndex("author")); 38 int pages = cursor.getInt(cursor.getColumnIndex("pages")); 39 double price = cursor.getDouble(cursor.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 50 Button updateData = (Button) findViewById(R.id.update_data); 51 updateData.setOnClickListener(new View.OnClickListener() { 52 @Override 53 public void onClick(View view) { 54 Uri uri = Uri.parse(path + "/" + newId); 55 ContentValues values = new ContentValues(); 56 values.put("name","The b"); 57 values.put("pages",12); 58 values.put("price",12.22); 59 getContentResolver().update(uri,values,null,null); 60 } 61 }); 62 63 Button deleteData = (Button) findViewById(R.id.delete_data); 64 deleteData.setOnClickListener(new View.OnClickListener() { 65 @Override 66 public void onClick(View view) { 67 Uri uri = Uri.parse(path + "/" + newId); 68 getContentResolver().delete(uri,null,null); 69 } 70 }); 71 } 72 }
其中我们将常用的uri设为一个常量path,每个内容Uri通过有path常量和“id”组成。而我们基本的操作就是先将内容uri进行解析,然后通过getContentResolver中的增删改查四种方法,传入相关的uri和values参数,进行操作。
其中增加中返回一个uri对象,这个对象中包含了新增数据的id,所以通过getPathSegments()方法将id取出,之后在更新和删除数据中,将这个id加在内容Uri尾部,使其只针对这一行数据进行操作。