Android基础总结(6)——内容提供器

  前面学习的数据持久化技术包括文件存储、SharedPreferences存储以及数据库存储技术保存的数据都只能被当前应用程序所访问。虽然文件存储和SharedPreferences存储中提供了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE这两种操作模式,可以实现不同应用程序间的数据共享,但是这两种模式在Android4.2版本中就已经被废弃了。目前,Android系统推荐使用一种更加安全可靠的内容提供器技术。

内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问的安全性。不同于文件存储和SharedPreferences存储中的两种全局可读写操作模式,内容提供器可以选择只对一部分数据进行共享,从而保证我们程序中的隐私数据不被泄露。

1、内容提供器的用法

  内容提供器的用法一般有两种:

  • 使用现有的内容提供器来读取和操作相应程序中的数据
  • 创建自己的内容提供器给我们的程序的数据提供外部的接口

2、访问其他程序中的数据

  当一个应用程序通过内容提供器对其数据提供了外部访问接口,任何其他的应用程序就都可以对这部分数据进行访问。Android系统中自带的电话簿、短信、媒体库等程序都提供了类似的访问接口,这就让第三方应用程序可以充分地利用这部分数据来实现更好的功能。

  对每一个应用程序而言,如果想要访问内容提供器中的共享的数据,就一定要借助ContentResolver类,具体的步骤如下:

  1. 通过Context中的getContentResolver()方法获取ContentResolver类的实例对象;
  2. 利用ContentResolver中提供的一系列方法对数据进行CRUD操作,其中insert()方法用于插入数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。ContentResolver中的CRUD方法都是不接受表名参数,而是使用一个Uri参数代替,这个参数被称为内容URI给内容提供器中的数据提供了唯一的标识符,它主要由两部分组成:权限+路径。其中权限用于对不同的应用程序作区分,一般为了避免冲突都会采用程序包名的方式对权限进行命名,eg:某个程序的包名为com.example.app,那么对应的权限可以命名为com.example.app.provider。路径则是用于对同一应用程序中不同的表作区分的通常都会添加到权限的后面。eg:某个应用程序的数据库中存在两张表,table1和table2,这时可以将路径分别命名为/table1和/table2,然后将权限和路径进行组合,内容RUI就变成com.example.app.provider/table1和com.example.app.provider/table2。不过目前我们还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议的声明,因此,内容URI最标准的格式写法是:content:com.example.app.provider/table1和content:com.example.app.provider/table2)。我们通过Uri.parse(String uriString)方法将URI字符串转化为Uri对象,然后将该对象传入对应的操作中来指定要操作的数据所在的表。
  3. 查询方法query()方法返回的仍然是一个Cursor对象,然后我们从该对象中取出查询的结果。

具体的应用示例如下:

 1 //查询
 2 Cursor cursor = getContentResolver().query(
 3 uri,
 4 projection,
 5 selection,
 6 selectionArgs,
 7 sortOrder);
 8 if (cursor != null) {
 9 while (cursor.moveToNext()) {
10 String column1 = cursor.getString(cursor.getColumnIndex("column1"));
11 int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
12 }
13 cursor.close();
14 }
15 
16 //添加
17 ContentValues values = new ContentValues();
18 values.put("column1", "text");
19 values.put("column2", 1);
20 getContentResolver().insert(uri, values);
21 
22 //更新
23 ContentValues values = new ContentValues();
24 values.put("column1", "");
25 getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new
26 String[] {"text", "1"});
27 
28 //删除
29 getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });

  下面我们尝试读取手机中的联系人的姓名和电话:

  首先我们在布局中简单地设置一个按钮和一个ListView,点击按钮后读取联系人,ListView用于显示

 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/ReadContract"
 8         android:layout_width="wrap_content"
 9         android:layout_height="wrap_content"
10         android:text="@string/readContractButtonName"/>
11 
12     <ListView
13         android:id="@+id/contract"
14         android:layout_width="wrap_content"
15         android:layout_height="wrap_content" >
16 
17     </ListView>
18 
19 </LinearLayout>

  然后,我们在Activity的onCreate()方法为按钮添加点击事件,点击之后调用我们自己写的一个读取联系人的方法readContrast(),在该方法中我们获取系统联系人的存放的Uri,然后通过获取的ContentResolver对象的query()方法进行查询,将查询结果添加到List中,然后显示出了

 1 public class MainActivity extends Activity {
 2 
 3     private Button readContract ;
 4     private ListView contractView ;
 5     ArrayAdapter<String> adapter ;
 6     List<String> contractsList = new ArrayList<String>() ;
 7     
 8     @Override
 9     protected void onCreate(Bundle savedInstanceState) {
10         super.onCreate(savedInstanceState);
11         setContentView(R.layout.activity_main);
12         
13         readContract = (Button) findViewById(R.id.ReadContract) ;
14         contractView = (ListView) findViewById(R.id.contract) ;
15         adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contractsList) ;
16 
17         readContract.setOnClickListener(new OnClickListener() {            
18             @Override
19             public void onClick(View v) {
20                 //contractView.setAdapter(adapter)必须在点击事件监控中设置,否则没有显示
21                 contractView.setAdapter(adapter);
22                 readContract() ;
23             }
24         });
25     }
26     
27     private void readContract() {
28         Cursor cursor = null ;
29         Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI ;
30         try {
31             cursor = getContentResolver().query(uri, null, null, null, null) ;
32             while(cursor.moveToNext()){
33                 String displayName = cursor.getString(cursor.getColumnIndex(
34                         ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)) ;
35                 String number = cursor.getString(cursor.getColumnIndex(
36                         ContactsContract.CommonDataKinds.Phone.NUMBER)) ;
37                 contractsList.add(displayName + "\n" + number) ; 
38             }
39         }catch (Exception e){
40             e.printStackTrace();
41         }finally {
42             if(cursor != null){
43                 cursor.close();
44             }
45         }
46     }
47 }

3、创建自己的内容提供器向外提供内容访问接口

  如果想要实现扩程序共享数据的功能,官方推荐的方式是使用内容提供器。我们可以通过新建一个类去继承ContentProvider的方式来创建一个自己的内容提供器。ContentProvider类中有六个抽象方法,我们在使用子类继承它时,需要全部实现这六个方法:

  • public boolean onCreate() :初始化的时候调用,通常在这里完成对数据库的创建和升级等操作,返回true表示内容提供器初始化成功,返回false表示初始化失败。注意,只有放存在VontentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化。
  • public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):从内容提供器中查询数据。查询结果存放在Cursor对象中返回
  • public Uri insert(Uri uri, ContentValues values) :向内容提供器中添加一条数据。添加完成后,返回一个用于表示这条新纪录的URI
  • public int delete(Uri uri, String selection, String[] selectionArgs):从内容提供器中删除数据。被删除的行数将作为返回值返回
  • public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) :更新内容提供器中已有的数据。受影响的行数将作为返回值返回
  • public String getType(Uri uri):根据传入的内容URI来返回相应的MIME类型。

注意这些方法有与ContentResolver完全相同的方法签名。显然,这里我们需要自定义向外提供的对我们的数据的操作的实现。这样其他应用程序通过内容提供器来访问当前数据库中的共享数据的时候可以按照我们的要求得到相应的结果。所以,实现这方法时应该考虑以下事情:

  1. 所有的这些方法除了onCreate()以外,都能够同时被多线程调用,因此它们必须是线程安全的
  2. 避免在onCreate()方法中做长时操作。直到实际需要的时候才初始化任务。有关原因会在“实现onCreate()方法”章节中进行更多的讨论。
  3. 尽管你必须实现这些方法,但是你的代码除了返回预定的数据类型之外,不做任何事情。例如,你可能会想阻止其他应用程序把数据插入到某些表中,你能够通过这种方法忽略对insert()方法的调用,并且返回“0”。

  所以,在这里,我们首先需要解析内容URI,一个标准的URI有两种写法

  1. content://com.example.app.provider/table1 :这就表示调用方期望访问的是com.example.app这个应用的table1表中的数据
  2. content://com.example.app.provider/table1/1 :这就表示调用方期望访问的是com.example.app这个应用的table1表中id为1的数据

  所以解析内容URI的目的就是要弄清楚所访问内容URI到底是哪一种,从而确定我们要访问的数据是表格的所有数据还是部分数据。以路径结尾就表示期望访问该表中所有的数据,以id结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下:

  1. *:表示匹配任意长度的任意字符
  2. # :表示匹配任意长度的数字

  所以,

  • 一个能够匹配任意表的内容URI格式就可以写出:content://com.example.app.provider/*
  • 一个能够匹配table1表中任意一行数据的内容URI格式就可以写成:content://com.example.app.provider/table1/#

  然后我们可以借助URIMatcher可以实现内容URI的匹配。URIMatcher提供了addURI(),接受三个参数(权限.路径,自定义代码)。返回一个匹配这个人Uri对象所对应的自定义代码。根据代码可知调用方访问的是那张表的数据。

 1 public class MyProvider extends ContentProvider {
 2 
 3     /*
 4      * MyProvider中新增四个整形常量,其中TABLE1_DIR表示访问table1表中的所有数据,
 5      * TABLE1_ITEM表示访问的table1表中的单条数据,TABLE2_DIR表示访问table2表中的所有数据,
 6      * TABLE2_ITEM表示访问的table2表中的单条数据。
 7      */
 8     public static final int TABLE1_DIR = 0;
 9     public static final int TABLE1_ITEM = 1;
10     public static final int TABLE2_DIR = 2;
11     public static final int TABLE2_ITEM = 3;
12     private static UriMatcher uriMatcher;
13 
14     /*
15      * 上面定义常量以后,接着在静态代码块里,创建UriMatcher的实例,并调用addURI()方法,将期望匹配的内容URI格式传递进去,
16      * 注意这里传入的路径参数是可以使用通配符的。所以,对于隐式数据,我们不匹配进来就可以避免让其他应用程序进行访问。然后当query()方法被调用的时候,
17      * 就会通过UriMatcher的match()方法对传入的Uri对象进行匹配,如果发现UriMatcher中某个内容URI格式成功匹配了该Uri对象,则
18      * 返回相应的自定义代码,然后就可以判断期望访问的到底是什么数据了。这里只使用query()方法做了一个示范,其实
19      * insert(),update(),delete()这几个方法的实现也是差不多的,它们都会携带Uri这个参数,然后同样利用
20      * UriMatcher的match()方法判断出调用期望访问的是哪一张表,在对该表中的数据进行相应的操作就可以了。
21      */
22     static {
23         uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
24         uriMatcher.addURI("com.jack.contactstest.provider", "table1", TABLE1_DIR);
25         uriMatcher.addURI("com.jack.contactstest.provider", "table1/#", TABLE1_ITEM);
26         uriMatcher.addURI("com.jack.contactstest.provider", "table2", TABLE2_DIR);
27         uriMatcher.addURI("com.jack.contactstest.provider", "table2/#", TABLE2_ITEM);
28     }
29 
30     /*
31      * onCreate()方法初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回
32      * true表示内容提供器初始化成功,返回false则表示失败。注意只有当存在ContentResolver尝试访问
33      * 我们程序中的数据时,内容提供器才会被初始化。
34      */
35     @Override
36     public boolean onCreate() {
37         // TODO Auto-generated method stub
38         return false;
39     }
40 
41     /*
42      * query()方法从内容提供器中查询数据。使用uri参数来确定查询哪张表,projection参数用于确定查询
43      * 哪些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,
44      * 查询的结果存放在Cursor对象中返回。
45      */
46     @Override
47     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
48         // TODO Auto-generated method stub
49         switch (uriMatcher.match(uri)) {
50         case TABLE1_DIR:
51             // 查询table1表中的所有数据
52             break;
53         case TABLE1_ITEM:
54             // 查询table1表中的单条数据
55             break;
56         case TABLE2_DIR:
57             // 查询table2表中的所有数据
58             break;
59         case TABLE2_ITEM:
60             // 查询table2表中的单条数据
61             break;
62         }
63         return null;
64     }      
65     。。。
66 }

   到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用ContentResolver来访问我们程序中的数据。那么如何才能保证隐私数据不会泄漏出去呢?其实多亏了内容提供器的良好机制,这个问题已经已经在不知不觉中被解决了。因为所有的CRUD操作都一定要匹配到相应的内容URI格式才能进行,而我们当然不可能向UriMatcher中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问到,安全问题也就不存在了。

实现各方法应该注意的问题:

  实现query()方法

  ContentProvider.query()方法必须返回一个Cursor对象,如果执行失败,就抛出一个异常。如果你正在使用一个SQLite数据库做为你的数据存储,你能够通过调用SQLiteDatabase类的一个query()方法,就能简单的返回Cursor对象。如果查询不到匹配的行,那么返回的Cursor对象的getCount()方法就会返回0。只有在查询过程期间发生了内部错误,你才应该返回null。

  如果你不适用SQLite数据库做为数据存储,那么就要使用Cursor的一个具体子类。如,MatrixCursor类实现了每行是一个对象数组的游标,这个类用addRow()方法来添加新行。

  记住,Android系统必须能够跨进程来传递异常。在处理的查询错误中,下列异常可用于进程间传递:

  1. IllegalArgumentException(如果提供器收到一个无效的内容资源标识,可以选择抛出这个异常)
  2. NullPointerException

  实现insert()方法

  insert()方法使用ContentValues参数中的值把一行新的数据添加到相应的表中。如果在ContentValues参数中没有列名,你可能是想要使用提供器代码或数据库模式来提供默认值。

  这个方法应该返回新插入行的内容资源标识。使用withAppendedId()方法给这个新行追加一个_ID(或其他主键)值。

  实现delete()方法

  不要使用delete()方法从你的数据存储中物理的删除行。因为如果你的提供器使用了同步适配器,你就应该使用“delete”标识来标记要删除的行,而不是把完全的删除行。同步适配器会在从提供器中删除它们之前检查要删除的行,并且从服务端删除它们。

  实现update()方法

  Update()方法需要与insert()方法使用的相同的ContentValues参数,以及与delete()方法和query()方法相同的selection和selectionArgs参数。这种方法允许你在这些方法之间重用代码。

getType()是所有内容提供器都必须提供的方法.用于获取Uri对象对应的MIME类型.MINE由三部分组成:

  1. 必须以 vnd 开头。
  2. 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/。
  3. 最后接上 vnd.<authority>.<path>。

  示例如下,两种不同类型的内容URI的返回的类型如下:

  • content://com.example.app.provider/table1 ==>vnd.android.cursor.dir/vnd.com.example.app.provider.table1
  • content://com.example.app.provider/table1/1 ==>vnd.android.cursor.item/vnd. com.example.app.provider.table1 对于隐私数据不提供对应的URI外部程序就无法访问了.

  实现onCreate()方法

  Android系统会在提供器启动时调用onCreate()方法。你只应该在这个方法中执行快速的初始任务,并且要把数据库的创建和数据的装载延迟到提供器接收到实际的数据请求之后。如果在你onCreate()方法中你执行了长时任务,会降低提供器的启动速度,从而降低提供器对其他应用程序的响应速度。

  例如,如果你使用SQLite数据库,而且在onCreate()方法中创建了一个新的SQLiteOpenHelper对象,然后在首次打开数据时,创建SQL表。要做这项工作,首先调用getWritableDatabase()方法,它会自动的调用SQLiteOpenHelper.onCreate()方法。

  以下两段代码演示了ContentProvider.onCreate()方法和SQLiteOpenHelper.onCreate()方法之间的交互。

  第一段代码时ContentProvider.onCreate()方法的实现。

 1 public class ExampleProvider extends ContentProvider
 2 
 3     /*
 4      * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
 5      * in a following snippet.
 6      */
 7     private MainDatabaseHelper mOpenHelper;
 8 
 9     // Defines the database name
10     private static final String DBNAME = "mydb";
11 
12     // Holds the database object
13     private SQLiteDatabase db;
14 
15     public boolean onCreate() {
16 
17         /*
18          * Creates a new helper object. This method always returns quickly.
19          * Notice that the database itself isn't created or opened
20          * until SQLiteOpenHelper.getWritableDatabase is called
21          */
22         mOpenHelper = new SQLiteOpenHelper(
23             getContext(),        // the application context
24             DBNAME,              // the name of the database)
25             null,                // uses the default SQLite cursor
26             1                    // the version number
27         );
28 
29         return true;
30     }
31 
32     ...
33 
34     // Implements the provider's insert method
35     public Cursor insert(Uri uri, ContentValues values) {
36         // Insert code here to determine which table to open, handle error-checking, and so forth
37 
38         ...
39 
40         /*
41          * Gets a writeable database. This will trigger its creation if it doesn't already exist.
42          *
43          */
44         db = mOpenHelper.getWritableDatabase();
45     }
46 }

  以下代码时是SQLiteOpenHelper.onCreate()方法的实现,而且包括了一个助手类:

// A string that defines the SQL statement for creating a table
private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
    "main " +                       // Table's name
    "(" +                           // The columns in the table
    " _ID INTEGER PRIMARY KEY, " +
    " WORD TEXT"
    " FREQUENCY INTEGER " +
    " LOCALE TEXT )";
...
/**
 * Helper class that actually creates and manages the provider's underlying data repository.
 */
protected static final class MainDatabaseHelper extends SQLiteOpenHelper {

    /*
     * Instantiates an open helper for the provider's SQLite data repository
     * Do not do database creation and upgrade here.
     */
    MainDatabaseHelper(Context context) {
        super(context, DBNAME, null, 1);
    }

    /*
     * Creates the data repository. This is called when the provider attempts to open the
     * repository and SQLite reports that it doesn't exist.
     */
    public void onCreate(SQLiteDatabase db) {

        // Creates the main table
        db.execSQL(SQL_CREATE_MAIN);
    }
}

  下面进行实战,体验一下跨程序共享的功能。 简单起见,我们使用上一篇博客的DatabaseTest的项目,在该项目的基础上进行修改继续开发,通过内容提供器给它加入外部访问接口。打开DatabaseTest项目,首先将MyDatabaseHelper中使用Toast弹出创建数据成功的提示去掉,因为跨程序访问时我们不能直接使用Toast。然后添加一个DatabaseProvider类,代码如下所示:

  1 public class DatabaseProvider extends ContentProvider {
  2 
  3     //自定义代码
  4     public static final int BOOK_DIR=0;
  5     public static final int BOOK_ITEM=1;
  6     public static final int CATEGORY_DIR=2;
  7     public static final int CATEGORY_ITEM=3;
  8     //权限
  9     public static final String AUTHORITY="com.jack.databasetest.provider";
 10     private static UriMatcher uriMatcher;
 11     private MyDatabaseHelper dbHelper;
 12     //静态代码块进行初始话
 13     static {
 14         uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
 15         uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
 16         uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
 17         uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
 18         uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
 19     }
 20 
 21 
 22 
 23     @Override
 24     public int delete(Uri uri, String selection, String[] selectionArgs) {
 25         // TODO Auto-generated method stub
 26         //删除数据
 27         SQLiteDatabase db=dbHelper.getWritableDatabase();
 28         int deleteRows=0;
 29         switch(uriMatcher.match(uri)){
 30         case BOOK_DIR:
 31             deleteRows=db.delete("book", selection, selectionArgs);
 32             break;
 33         case BOOK_ITEM:
 34             String bookId=uri.getPathSegments().get(1);
 35             deleteRows=db.delete("book", "id=?", new String[]{bookId});
 36             break;
 37         case CATEGORY_DIR:
 38             deleteRows=db.delete("category", selection, selectionArgs);
 39             break;
 40         case CATEGORY_ITEM:
 41             String categoryId=uri.getPathSegments().get(1);
 42             deleteRows=db.delete("category", "id=?",new String[]{categoryId});
 43             break;  
 44         default:
 45             break;
 46         }
 47         return deleteRows;//被删除的行数作为返回值返回
 48     }
 49 
 50     @Override
 51     public String getType(Uri uri) {
 52         // TODO Auto-generated method stub
 53         switch(uriMatcher.match(uri)){
 54         case BOOK_DIR:
 55             return "vnd.android.cursor.dir/vnd.com.jack.databasetest.provider.book";
 56         case BOOK_ITEM:
 57             return "vnd.android.cursor.item/vnd.com.jack.databasetest.provider.book";
 58         case CATEGORY_DIR:
 59             return "vnd.android.cursor.dir/vnd.com.jack.databasetest.provider.category";
 60         case CATEGORY_ITEM:
 61             return "vnd.android.cursor.item/vnd.com.jack.databasetest.provider.category";   
 62         }
 63         return null;
 64     }
 65 
 66     @Override
 67     public Uri insert(Uri uri, ContentValues values) {
 68         // TODO Auto-generated method stub
 69         //添加数据
 70         SQLiteDatabase db=dbHelper.getWritableDatabase();
 71         Uri uriReturn=null;
 72         switch(uriMatcher.match(uri)){
 73         case BOOK_DIR:
 74         case BOOK_ITEM:
 75             long newBookId=db.insert("book", null, values);
 76             uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);
 77             break;
 78         case CATEGORY_DIR:
 79         case CATEGORY_ITEM:
 80             long newCategoryId=db.insert("category", null, values);
 81             uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newCategoryId);
 82             break;  
 83         default:
 84             break;
 85         }
 86         /*
 87          * insert()方法要求返回一个能够表示这条新增数据的URI,所以需要调用Uri.parse()方法来将一个内容
 88          * URI解析成Uri对象,当然这个内容是以新增数据的id结尾的。
 89          * */
 90         return uriReturn;
 91     }
 92 
 93     @Override
 94     public boolean onCreate() {
 95         // TODO Auto-generated method stub
 96         dbHelper=new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
 97         return true;//返回true表示内容提供器初始化成功,这时数据库就已经完成了创建或升级操作
 98     }
 99 
100     @Override
101     public Cursor query(Uri uri, String[] projection, String selection,
102             String[] selectionArgs, String sortOrder) {
103         // TODO Auto-generated method stub
104         //查询数据
105         SQLiteDatabase db=dbHelper.getReadableDatabase();//获得SQLiteDatabase对象
106         Cursor cursor=null;
107         switch(uriMatcher.match(uri)){
108         case BOOK_DIR:
109             //进行查询
110             cursor=db.query("book", projection, selection, selectionArgs,
111                     null, null, sortOrder);
112             break;
113         case BOOK_ITEM:
114             //进行查询
115             /*Uri对象的getPathSegments()方法会将内容URI权限之后的部分以“、”符号进行分割,并把分割后的结果
116              * 放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id,得到id后,在通过
117              * selection和selectionArgs参数就实现了查询单条数据的功能。
118              * */
119             String bookId=uri.getPathSegments().get(1);
120             cursor=db.query("book", projection, "id=?", new String[]{bookId}, 
121                     null, null, sortOrder);
122             break;
123         case CATEGORY_DIR:
124             //进行查询
125             cursor=db.query("category", projection, selection, selectionArgs,
126                     null, null, sortOrder);
127             break;
128         case CATEGORY_ITEM:
129             //进行查询
130             String categoryId=uri.getPathSegments().get(1);
131             cursor=db.query("book", projection, "id=?", new String[]{categoryId}, 
132                     null, null, sortOrder);
133             break;  
134         default:
135             break;
136         }
137         return cursor;
138     }
139 
140     @Override
141     public int update(Uri uri, ContentValues values, String selection,
142             String[] selectionArgs) {
143         // TODO Auto-generated method stub
144         SQLiteDatabase db=dbHelper.getWritableDatabase();
145         int updatedRows=0;
146         //更新数据
147         switch(uriMatcher.match(uri)){
148         case BOOK_DIR:
149             updatedRows=db.update("book", values, selection,selectionArgs);
150             break;
151         case BOOK_ITEM:
152             String bookId=uri.getPathSegments().get(1);
153             updatedRows=db.update("book", values, "id=?", new String[]{bookId});
154             break;
155         case CATEGORY_DIR:
156             updatedRows=db.update("category", values, selection,selectionArgs);
157             break;
158         case CATEGORY_ITEM:
159             String categoryId=uri.getPathSegments().get(1);
160             updatedRows=db.update("book", values, "id=?", new String[]{categoryId});
161             break;  
162         default:
163             break;
164         }
165         return updatedRows;//受影响的行数作为返回值
166     }
167 
168 }

  上面的功能,在注释已经说名了,就不多说了,经过上面的步骤,内容提供器的代码全部编写完了,不过离跨实现程序数据共享的功能还差了一小步,因为还需要将内容提供器在AndroidManifest.xml文件中注册才可以,如下所示:

 1 <pre name="code" class="html"><?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3     package="com.jack.databasetest"
 4     android:versionCode="1"
 5     android:versionName="1.0" >
 6 
 7     <uses-sdk
 8         android:minSdkVersion="13"
 9         android:targetSdkVersion="17" />
10 
11     <application
12         android:allowBackup="true"
13         android:icon="@drawable/ic_launcher"
14         android:label="@string/app_name"
15         android:theme="@style/AppTheme" >
16         <activity
17             android:name="com.jack.databasetest.MainActivity"
18             android:label="@string/app_name" >
19             <intent-filter>
20                 <action android:name="android.intent.action.MAIN" />
21 
22                 <category android:name="android.intent.category.LAUNCHER" />
23             </intent-filter>
24         </activity>
25 
26 
27         <provider 
28             android:name="com.jack.databasetest.DatabaseProvider"
29             android:authorities="com.jack.databasetest.provider"
30             android:exported="true"
31         ></provider>
32 
33     </application>
34 
35 </manifest>

android:exported=”true”这个属性,值加了上面的android:name=”com.jack.databasetest.DatabaseProvider” android:authorities=”com.jack.databasetest.provider”属性,程序访问出现安全问题了,百度后,说是需要android:exported=”true”这个属性,才能跨程序被其他的程序访问。我试试了下,当中需要这个属性,不然后面进行跨程序访问的时候会出现错误。

  现在DatabaseTest这个项目就已经拥有了跨程序共享数据的功能了,现在我们来试试。首先需要将DatabaseTest程序从模拟器中删除掉,以防止以前的遗留数据对我们产生影响。然后运行下项目,将DatabaseTest程序重写安装在模拟器上。接着关闭这个项目,并创建一个新项目ProviderTest,我们就通过这个程序去访问DatabaseTest中的数据。  先修改下ProviderTest的布局文件activity_main.xml中的代码,如下所示:

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical"
 6     >
 7 
 8     <Button 
 9         android:id="@+id/add_data"
10         android:layout_width="match_parent"
11         android:layout_height="wrap_content"
12         android:text="add data to book"
13         />
14 
15     <Button 
16         android:id="@+id/query_data"
17         android:layout_width="match_parent"
18         android:layout_height="wrap_content"
19         android:text="query from book"
20         />
21 
22     <Button 
23         android:id="@+id/update_data"
24         android:layout_width="match_parent"
25         android:layout_height="wrap_content"
26         android:text="update book"
27         />
28 
29     <Button 
30         android:id="@+id/delete_data"
31         android:layout_width="match_parent"
32         android:layout_height="wrap_content"
33         android:text="delete data from book"
34         />
35 
36 
37 </LinearLayout>

  放置了四个按钮,分别用来添加数据,查询,修改和删除数据。然后在修改MainActivity中的代码,如下所示:

  1 package com.jack.providertest;
  2 
  3 import android.net.Uri;
  4 import android.os.Bundle;
  5 import android.app.Activity;
  6 import android.content.ContentValues;
  7 import android.database.Cursor;
  8 import android.util.Log;
  9 import android.view.Menu;
 10 import android.view.View;
 11 import android.view.View.OnClickListener;
 12 import android.widget.Button;
 13 
 14 public class MainActivity extends Activity {
 15 
 16     private String  newId;
 17     @Override
 18     protected void onCreate(Bundle savedInstanceState) {
 19         super.onCreate(savedInstanceState);
 20         setContentView(R.layout.activity_main);
 21 
 22         Button addData=(Button) findViewById(R.id.add_data);
 23         addData.setOnClickListener(new OnClickListener() {
 24 
 25             @Override
 26             public void onClick(View v) {
 27                 // TODO Auto-generated method stub
 28                 //添加数据
 29                 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book");
 30                 ContentValues values=new ContentValues();
 31                 values.put("name", "a clash of kings");
 32                 values.put("author", "george martin");
 33                 values.put("pages", 1050);
 34                 values.put("price", 88.9);
 35                 Uri newUri=getContentResolver().insert(uri, values);//插入数据
 36                 newId=newUri.getPathSegments().get(1);
 37             }
 38         });
 39 
 40 
 41         Button queryData=(Button) findViewById(R.id.query_data);
 42         queryData.setOnClickListener(new OnClickListener(){
 43 
 44             @Override
 45             public void onClick(View v) {
 46                 // TODO Auto-generated method stub
 47                 //查询数据
 48                 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book");
 49                 Cursor cursor=getContentResolver().query(uri, null, null, null, null);
 50                 if(cursor!=null){
 51                     while(cursor.moveToNext()){
 52                         String name=cursor.getString(cursor.getColumnIndex("name"));
 53                         String author=cursor.getString(cursor.getColumnIndex("author"));
 54                         int pages=cursor.getInt(cursor.getColumnIndex("pages"));
 55                         double price=cursor.getDouble(cursor.getColumnIndex("price"));
 56 
 57                         Log.d("MainActivity", "book name is "+name);
 58                         Log.d("MainActivity", "book author is "+author);
 59                         Log.d("MainActivity", "book pages is "+pages);
 60                         Log.d("MainActivity", "book price is "+price);
 61                     }
 62                     cursor.close();
 63                 }
 64             }
 65 
 66         });
 67 
 68 
 69         Button updateData=(Button) findViewById(R.id.update_data);
 70         updateData.setOnClickListener(new OnClickListener(){
 71 
 72             @Override
 73             public void onClick(View v) {
 74                 // TODO Auto-generated method stub
 75                 //更新数据
 76                 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book/"+newId);
 77                 ContentValues values=new ContentValues();
 78                 values.put("name", "a storm of swords");
 79                 values.put("pages", 1216);
 80                 values.put("price", 77.8);
 81                 getContentResolver().update(uri, values, null, null);
 82             }
 83 
 84         });
 85 
 86 
 87         Button deleteData=(Button) findViewById(R.id.delete_data);
 88         deleteData.setOnClickListener(new OnClickListener(){
 89 
 90             @Override
 91             public void onClick(View v) {
 92                 // TODO Auto-generated method stub
 93                 //删除数据
 94                 Uri uri=Uri.parse("content://com.jack.databasetest.provider/book/"+newId);
 95                 getContentResolver().delete(uri, null, null);
 96             }
 97 
 98         });
 99 
102     }
103 
104     @Override
105     public boolean onCreateOptionsMenu(Menu menu) {
106         // Inflate the menu; this adds items to the action bar if it is present.
107         getMenuInflater().inflate(R.menu.main, menu);
108         return true;
109     }
110 
111 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android开发与分享

【Android】Realm详解

69511
来自专栏Android知识点总结

P1-Android基于MVP实现号码归属地查询

1142
来自专栏开发之途

Android IPC机制(2)-AIDL

1634
来自专栏刘望舒

Android资源动态加载以及相关原理分析

思考 一般情况下,我们在设计一个插件化框架的时候,要解决的无非是下面几个问题: 四大组件的动态注册 组件相关的类的加载 资源的动态加载 实际上从目前的主流插件...

3278
来自专栏7号代码

Android网络与数据存储——SQLite

Android提供了一个管理数据库的工具类SQLiteOpenHelper,用于管理数据库的创建和版本更新,创建SQLiteOpenHelper的子类,并实现它...

1762
来自专栏QQ音乐技术团队的专栏

浅谈ViewModel

1 主要功能 Activity、Fragment存活期间的数据存储; bind同一Activity的多个Fragment间数据共享; 独立或与LiveData...

1.3K9
来自专栏刘望舒

APK是如何被解析的?

在本系列的前面文章中,我介绍了PackageInstaller的初始化和安装APK过程、PMS处理APK的安装和PMS的创建过程,这些文章中经常会涉及到一个类,...

2703
来自专栏西二旗一哥

iOS - autoreleasepool and @autoreleasepool

+ 在一个自动引用计数的环境中(并不是垃圾回收机制),一个包含了多个对象的 NSAutoreleasePool 对象能够接收 autorelease 消息并且...

1534
来自专栏非著名程序员

Android 内存泄露简介、典型情景及检测解决

什么是内存泄露? Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到...

2118
来自专栏Android机器圈

Android数据绑定技术一,企业级开发

PS:数据绑定,顾名思义是数据与一些控件或者用户账号等绑定,这样用的好处是便于管理。代码清晰,量少。 首先要了解什么是数据绑定? 为什么要用数据绑定? 怎么用数...

3578

扫码关注云+社区

领取腾讯云代金券