前面学习的数据持久化技术包括文件存储、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对象,然后我们从该对象中取出查询的结果。

具体的应用示例如下:

 //查询
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
} //添加
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values); //更新
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new
String[] {"text", "1"}); //删除
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });

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

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

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" > <Button
android:id="@+id/ReadContract"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/readContractButtonName"/> <ListView
android:id="@+id/contract"
android:layout_width="wrap_content"
android:layout_height="wrap_content" > </ListView> </LinearLayout>

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

 public class MainActivity extends Activity {

     private Button readContract ;
private ListView contractView ;
ArrayAdapter<String> adapter ;
List<String> contractsList = new ArrayList<String>() ; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); readContract = (Button) findViewById(R.id.ReadContract) ;
contractView = (ListView) findViewById(R.id.contract) ;
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contractsList) ; readContract.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//contractView.setAdapter(adapter)必须在点击事件监控中设置,否则没有显示
contractView.setAdapter(adapter);
readContract() ;
}
});
} private void readContract() {
Cursor cursor = null ;
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI ;
try {
cursor = getContentResolver().query(uri, null, null, null, null) ;
while(cursor.moveToNext()){
String displayName = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)) ;
String number = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER)) ;
contractsList.add(displayName + "\n" + number) ;
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();
}
}
}
}

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对象所对应的自定义代码。根据代码可知调用方访问的是那张表的数据。

 public class MyProvider extends ContentProvider {

     /*
* MyProvider中新增四个整形常量,其中TABLE1_DIR表示访问table1表中的所有数据,
* TABLE1_ITEM表示访问的table1表中的单条数据,TABLE2_DIR表示访问table2表中的所有数据,
* TABLE2_ITEM表示访问的table2表中的单条数据。
*/
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher; /*
* 上面定义常量以后,接着在静态代码块里,创建UriMatcher的实例,并调用addURI()方法,将期望匹配的内容URI格式传递进去,
16 * 注意这里传入的路径参数是可以使用通配符的。所以,对于隐式数据,我们不匹配进来就可以避免让其他应用程序进行访问。然后当query()方法被调用的时候,
* 就会通过UriMatcher的match()方法对传入的Uri对象进行匹配,如果发现UriMatcher中某个内容URI格式成功匹配了该Uri对象,则
* 返回相应的自定义代码,然后就可以判断期望访问的到底是什么数据了。这里只使用query()方法做了一个示范,其实
* insert(),update(),delete()这几个方法的实现也是差不多的,它们都会携带Uri这个参数,然后同样利用
* UriMatcher的match()方法判断出调用期望访问的是哪一张表,在对该表中的数据进行相应的操作就可以了。
*/
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.jack.contactstest.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.jack.contactstest.provider", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.jack.contactstest.provider", "table2", TABLE2_DIR);
uriMatcher.addURI("com.jack.contactstest.provider", "table2/#", TABLE2_ITEM);
} /*
* onCreate()方法初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回
* true表示内容提供器初始化成功,返回false则表示失败。注意只有当存在ContentResolver尝试访问
* 我们程序中的数据时,内容提供器才会被初始化。
*/
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
return false;
} /*
* query()方法从内容提供器中查询数据。使用uri参数来确定查询哪张表,projection参数用于确定查询
* 哪些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,
* 查询的结果存放在Cursor对象中返回。
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询table1表中的所有数据
break;
case TABLE1_ITEM:
// 查询table1表中的单条数据
break;
case TABLE2_DIR:
// 查询table2表中的所有数据
break;
case TABLE2_ITEM:
// 查询table2表中的单条数据
break;
}
return null;
}
。。。
}

  到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用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()方法的实现。

 public class ExampleProvider extends ContentProvider

     /*
* Defines a handle to the database helper object. The MainDatabaseHelper class is defined
* in a following snippet.
*/
private MainDatabaseHelper mOpenHelper; // Defines the database name
private static final String DBNAME = "mydb"; // Holds the database object
private SQLiteDatabase db; public boolean onCreate() { /*
* Creates a new helper object. This method always returns quickly.
* Notice that the database itself isn't created or opened
* until SQLiteOpenHelper.getWritableDatabase is called
*/
mOpenHelper = new SQLiteOpenHelper(
getContext(), // the application context
DBNAME, // the name of the database)
null, // uses the default SQLite cursor
1 // the version number
); return true;
} ... // Implements the provider's insert method
public Cursor insert(Uri uri, ContentValues values) {
// Insert code here to determine which table to open, handle error-checking, and so forth ... /*
* Gets a writeable database. This will trigger its creation if it doesn't already exist.
*
*/
db = mOpenHelper.getWritableDatabase();
}
}

  以下代码时是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类,代码如下所示:

 public class DatabaseProvider extends ContentProvider {

     //自定义代码
public static final int BOOK_DIR=0;
public static final int BOOK_ITEM=1;
public static final int CATEGORY_DIR=2;
public static final int CATEGORY_ITEM=3;
//权限
public static final String AUTHORITY="com.jack.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
//静态代码块进行初始话
static {
uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
} @Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
//删除数据
SQLiteDatabase db=dbHelper.getWritableDatabase();
int deleteRows=0;
switch(uriMatcher.match(uri)){
case BOOK_DIR:
deleteRows=db.delete("book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId=uri.getPathSegments().get(1);
deleteRows=db.delete("book", "id=?", new String[]{bookId});
break;
case CATEGORY_DIR:
deleteRows=db.delete("category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId=uri.getPathSegments().get(1);
deleteRows=db.delete("category", "id=?",new String[]{categoryId});
break;
default:
break;
}
return deleteRows;//被删除的行数作为返回值返回
} @Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch(uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.jack.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.jack.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.jack.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.jack.databasetest.provider.category";
}
return null;
} @Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
//添加数据
SQLiteDatabase db=dbHelper.getWritableDatabase();
Uri uriReturn=null;
switch(uriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId=db.insert("book", null, values);
uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId=db.insert("category", null, values);
uriReturn=Uri.parse("content://"+AUTHORITY+"/book/"+newCategoryId);
break;
default:
break;
}
/*
* insert()方法要求返回一个能够表示这条新增数据的URI,所以需要调用Uri.parse()方法来将一个内容
* URI解析成Uri对象,当然这个内容是以新增数据的id结尾的。
* */
return uriReturn;
} @Override
public boolean onCreate() {
// TODO Auto-generated method stub
dbHelper=new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;//返回true表示内容提供器初始化成功,这时数据库就已经完成了创建或升级操作
} @Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
//查询数据
SQLiteDatabase db=dbHelper.getReadableDatabase();//获得SQLiteDatabase对象
Cursor cursor=null;
switch(uriMatcher.match(uri)){
case BOOK_DIR:
//进行查询
cursor=db.query("book", projection, selection, selectionArgs,
null, null, sortOrder);
break;
case BOOK_ITEM:
//进行查询
/*Uri对象的getPathSegments()方法会将内容URI权限之后的部分以“、”符号进行分割,并把分割后的结果
* 放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id,得到id后,在通过
* selection和selectionArgs参数就实现了查询单条数据的功能。
* */
String bookId=uri.getPathSegments().get(1);
cursor=db.query("book", projection, "id=?", new String[]{bookId},
null, null, sortOrder);
break;
case CATEGORY_DIR:
//进行查询
cursor=db.query("category", projection, selection, selectionArgs,
null, null, sortOrder);
break;
case CATEGORY_ITEM:
//进行查询
String categoryId=uri.getPathSegments().get(1);
cursor=db.query("book", projection, "id=?", new String[]{categoryId},
null, null, sortOrder);
break;
default:
break;
}
return cursor;
} @Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
SQLiteDatabase db=dbHelper.getWritableDatabase();
int updatedRows=0;
//更新数据
switch(uriMatcher.match(uri)){
case BOOK_DIR:
updatedRows=db.update("book", values, selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId=uri.getPathSegments().get(1);
updatedRows=db.update("book", values, "id=?", new String[]{bookId});
break;
case CATEGORY_DIR:
updatedRows=db.update("category", values, selection,selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId=uri.getPathSegments().get(1);
updatedRows=db.update("book", values, "id=?", new String[]{categoryId});
break;
default:
break;
}
return updatedRows;//受影响的行数作为返回值
} }

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

 <pre name="code" class="html"><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jack.databasetest"
android:versionCode="1"
android:versionName="1.0" > <uses-sdk
android:minSdkVersion="13"
android:targetSdkVersion="17" /> <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.jack.databasetest.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> <provider
android:name="com.jack.databasetest.DatabaseProvider"
android:authorities="com.jack.databasetest.provider"
android:exported="true"
></provider> </application> </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中的代码,如下所示:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
> <Button
android:id="@+id/add_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="add data to book"
/> <Button
android:id="@+id/query_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="query from book"
/> <Button
android:id="@+id/update_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="update book"
/> <Button
android:id="@+id/delete_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="delete data from book"
/> </LinearLayout>

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

 package com.jack.providertest;

 import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button; public class MainActivity extends Activity { private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); Button addData=(Button) findViewById(R.id.add_data);
addData.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
//添加数据
Uri uri=Uri.parse("content://com.jack.databasetest.provider/book");
ContentValues values=new ContentValues();
values.put("name", "a clash of kings");
values.put("author", "george martin");
values.put("pages", 1050);
values.put("price", 88.9);
Uri newUri=getContentResolver().insert(uri, values);//插入数据
newId=newUri.getPathSegments().get(1);
}
}); Button queryData=(Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new OnClickListener(){ @Override
public void onClick(View v) {
// TODO Auto-generated method stub
//查询数据
Uri uri=Uri.parse("content://com.jack.databasetest.provider/book");
Cursor cursor=getContentResolver().query(uri, null, null, null, null);
if(cursor!=null){
while(cursor.moveToNext()){
String name=cursor.getString(cursor.getColumnIndex("name"));
String author=cursor.getString(cursor.getColumnIndex("author"));
int pages=cursor.getInt(cursor.getColumnIndex("pages"));
double price=cursor.getDouble(cursor.getColumnIndex("price")); Log.d("MainActivity", "book name is "+name);
Log.d("MainActivity", "book author is "+author);
Log.d("MainActivity", "book pages is "+pages);
Log.d("MainActivity", "book price is "+price);
}
cursor.close();
}
} }); Button updateData=(Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new OnClickListener(){ @Override
public void onClick(View v) {
// TODO Auto-generated method stub
//更新数据
Uri uri=Uri.parse("content://com.jack.databasetest.provider/book/"+newId);
ContentValues values=new ContentValues();
values.put("name", "a storm of swords");
values.put("pages", 1216);
values.put("price", 77.8);
getContentResolver().update(uri, values, null, null);
} }); Button deleteData=(Button) findViewById(R.id.delete_data);
deleteData.setOnClickListener(new OnClickListener(){ @Override
public void onClick(View v) {
// TODO Auto-generated method stub
//删除数据
Uri uri=Uri.parse("content://com.jack.databasetest.provider/book/"+newId);
getContentResolver().delete(uri, null, null);
} }); } @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} }

  

  现在运行下ProviderTest项目,显示如下:

  点击add data to book,此时数据应该已经添加到DatabaseTest程序的数据库中了 ,我们通过点击query form book按钮来检查下,打印日志如下:

  然后点击下update book按钮来更新数据,在点击下query from book按钮进行检查,结果如下:

  

Android基础总结(6)——内容提供器的更多相关文章

  1. <Android基础> (七)内容提供器

    第七章 内容提供器 7.1 内容提供器(Content Provider) 主要应用于在不同的应用程序之间实现数据共享功能.允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性. 7.2 ...

  2. Android学习之基础知识十—内容提供器(Content Provider)

    一.跨程序共享数据——内容提供器简介 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能 ...

  3. android学习十二(android的Content Provider(内容提供器)的使用)

    文件存储和SharePreference存储以及数据存储一般为了安全,最好用于当前应用程序中訪问和存储数据.内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能 ...

  4. Android入门(十四)内容提供器-实现跨程序共享实例

    原文链接:http://www.orlion.ga/661/ 打开SQLite博文中创建的 DatabaseDemo项目,首先将 MyDatabaseHelper中使用 Toast弹出创建数据库成功的 ...

  5. android: 创建自己的内容提供器

    我们学习了如何在自己的程序中访问其他应用程序的数据.总体来说思 路还是非常简单的,只需要获取到该应用程序的内容 URI,然后借助 ContentResolver 进行CRUD 操作就可以了.可是你有没 ...

  6. Android搜索框以及内容提供器

    先看结果: 相关的官方文档在这里:Creating a Search Interface Android官方提供了两种方式: 弹出一个Dialog,覆盖当前的Activity界面 在AppBar中扩展 ...

  7. android笔记 : Content provider内容提供器

    内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能. 内容提供器的用法一般有两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内 ...

  8. Android入门(十三)内容提供器

    原文链接:http://www.orlion.ga/612/ 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一 ...

  9. Android学习笔记(二十)——自定义内容提供器

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 如果我们想要实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以通过新建一个类去继承 Conten ...

随机推荐

  1. 为什么要用VisualSVN Server,而不用Subversion?

    为什么要用VisualSVN Server,而不用Subversion? [SVN 服务器的选择] - 摘自网络 http://www.cnblogs.com/haoliansheng/archive ...

  2. [系统集成] CI持续集成项目简介

    一.问题的产生 公司的多个部门围绕着产品开发.测试.发布.维护,设置有不同的岗位和系统,这些岗位和系统缺少有效的整合,没有实现自动化,效率不是很高,因此就有了CI(持续集成)的项目需求. 二.解决方案 ...

  3. SchemaExport的使用

    @Test public void testCreateDB(){ Configuration cfg = new Configuration().configure(); SchemaExport ...

  4. RMAN_学习实验2_RMAN Duplicate复制数据库过程(案例)

    待整理 对于基于生产环境下的数据库的版本升级或者测试新的应用程序的性能及其影响,备份恢复等等,我们可以采取从生产环境以克隆的方式将其克隆到本地而不影响生产数据库的正常使用.实现这个功能我们可以借助rm ...

  5. Codeforces Round #358 (Div. 2)B. Alyona and Mex

    B. Alyona and Mex time limit per test 1 second memory limit per test 256 megabytes input standard in ...

  6. MST_prim

    刚刚发了mst 的kruskal,现在再来一发,说一说prim咯. prim适用于稠密图. 与kruskal不同,prim是从一个点开始,不断加入新的点直至连通所有点. 讲讲prim的过程,我们假定有 ...

  7. oracle 运维基础

    setupDatabase() { runStr=" cd $BASE_INSTALL_DIR/database nohup ./runInstaller -silent -force -r ...

  8. 触发隐藏链接进行文件下载,click无响应

    function invokeClick(element) { if (element.click) element.click(); //判断是否支持click() 事件 else if (elem ...

  9. 内网Linux对公网开启SSH

    1.首先你得装个Linux,过程就不说了.Linux可以装在Vmware上,但是网络适配器选项选的是桥接模式 2.在linux上安装SSH服务,如果有就自动忽略 3.在路由器上开启端口映射. 这里我用 ...

  10. Log4Net在Windows服务中不能记录日志 z

    解决方案: 在Windows安装服务的“serviceProcessInstaller1”中修改Account属性为LocalSystem.见下图 后来查了一下这个Account属性 说白了还是权限的 ...