Android使用内容提供器(ContentProvider)实现跨程序数据共享

文章目录

Android使用内容提供器(ContentProvider)实现跨程序数据共享

新建内容提供器DatabaseProvider

简单起见,我们还是在上一章中DatabaseTest项目的基础上继续开发,通过内容提供器来给它加入外部访问接口。打开DatabaseTest项目,首先将MyDatabaseHelper中使用Toast弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用Toast。然后创建一个内容提供器,右击com.example.databasetest包→New→Other→Content Provider,会弹出如图所示的窗口:

可以看到,这里我们将内容提供器命名为DatabaseProvider,authority指定为com.example.databasetest.provider,Exported属性表示是否允许外部程序访问我们的内容提供器,Enabled属性表示是否启用这个内容提供器。将两个属性都勾中,点击Finish完成创建。

修改DatabaseProvider中的代码

接着我们修改DatabaseProvider中的代码,如下所示:

java 复制代码
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.example.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 boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs, String sortOrder) {
//查询数据
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("Book", projection, selection, selectionArgs, null,
null, sortOrder);
break;
case BOOK_ITEM:
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("Category", projection, "id = ?", new String[]
{ categoryId }, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
//添加数据
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 + "/category/" +
newCategoryId);
break;
default:
break;
}
return uriReturn;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[]
selectionArgs) {
//更新数据
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("Category", values, "id = ?", new String[]
{ categoryId });
break;
default:
break;
}
return updatedRows;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//删除数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deletedRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deletedRows = db.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
break;
case CATEGORY_DIR:
deletedRows = db.delete("Category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deletedRows = db.delete("Category", "id = ?", new String[]
{ categoryId });
break;
default:
break;
}
return deletedRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.
provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.
provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.
provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.
provider.category";
}
return null;
}
}

代码虽然很长,不过不用担心,这些内容都非常容易理解,因为使用到的全部都是上一小节中我们学到的知识。首先在类的一开始,同样是定义了4个常量,分别用于表示访问Book表中的所有数据、访问Book表中的单条数据、访问Category表中的所有数据和访问Category表中的单条数据。然后在静态代码块里对UriMatcher进行了初始化操作,将期望匹配的几种URI格式添加了进去。

接下来就是每个抽象方法的具体实现了,先来看下onCreate()方法,这个方法的代码很短,就是创建了一个MyDatabaseHelper的实例,然后返回true表示内容提供器初始化成功,这时数据库就已经完成了创建或升级操作。

接着看一下query()方法,在这个方法中先获取到了SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要访问哪张表,再调用SQLiteDatabase的query()进行查询,并将Cursor对象返回就好了。注意当访问单条数据的时候有一个细节,这里调用了Uri对象的getPathSegments()方法,它会将内容URI权限之后的部分以"/"符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id了。得到了id之后,再通过selection和selectionArgs参数进行约束,就实现了查询单条数据的功能。

再往后就是insert()方法,同样它也是先获取到了SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要往哪张表里添加数据,再调用SQLiteDatabase的insert()方法进行添加就可以了。注意insert()方法要求返回一个能够表示这条新增数据的URI,所以我们还需要调用Uri.parse()方法来将一个内容URI解析成Uri对象,当然这个内容URI是以新增数据的id结尾的。

接下来就是update()方法了,相信这个方法中的代码已经完全难不倒你了。也是先获取SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要更新哪张表里的数据,再调用SQLiteDatabase的update()方法进行更新就好了,受影响的行数将作为返回值返回。下面是delete()方法,是不是感觉越到后面越轻松了?因为你已经渐入佳境,真正地找到窍门了。这里仍然是先获取到SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要删除哪张表里的数据,再调用SQLiteDatabase的delete()方法进行删除就好了,被删除的行数将作为返回值返回。

最后是getType()方法,这个方法中的代码完全是按照上一节中介绍的格式规则编写的,相信已经没有什么解释的必要了。这样我们就将内容提供器中的代码全部编写完了。

AndroidManifest.xml文件中注册provider

另外还有一点需要注意,内容提供器一定要在AndroidManifest.xml文件中注册才可以使用。不过幸运的是,由于我们是使用Android Studio的快捷方式创建的内容提供器,因此注册这一步已经被自动完成了。打开AndroidManifest.xml文件瞧一瞧,代码如下所示:

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.databasetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<provider
android:name=".DatabaseProvider"
android:authorities="com.example.databasetest.provider"
android:enabled="true"
android:exported="true">
</provider>
</application>
</manifest>

可以看到,<application>标签内出现了一个新的标签<provider>,我们使用它来对DatabaseProvider这个内容提供器进行注册。android:name属性指定了DatabaseProvider的类名,android:authorities属性指定了DatabaseProvider的authority,而enabled和exported属性则是根据我们刚才勾选的状态自动生成的,这里表示允许DatabaseProvider被其他应用程序进行访问。

现在DatabaseTest这个项目就已经拥有了跨程序共享数据的功能了,我们赶快来尝试一下。首先需要将DatabaseTest程序从模拟器中删除掉,以防止上一章中产生的遗留数据对我们造成干扰。

然后运行一下项目,将DatabaseTest程序重新安装在模拟器上了。接着关闭掉DatabaseTest这个项目,并创建一个新项目ProviderTest,我们就将通过这个程序去访问DatabaseTest中的数据。

修改activity_main.xml中的代码

还是先来编写一下布局文件吧,修改activity_main.xml中的代码,如下所示:

xml 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/add_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add 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 From Book" />
</LinearLayout>

布局文件很简单,里面放置了4个按钮,分别用于添加、查询、修改和删除数据。

修改MainActivity中的代码

然后修改MainActivity中的代码,如下所示:

java 复制代码
public class MainActivity extends AppCompatActivity {
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 View.OnClickListener() {
@Override
public void onClick(View v) {
//添加数据
Uri uri = Uri.parse("content://com.example.databasetest. provider/
book");
ContentValues values = new ContentValues();
values.put("name", "A Clash of Kings");
values.put("author", "George Martin");
values.put("pages", 1040);
values.put("price", 22.85);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
});
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//查询数据
Uri uri = Uri.parse("content://com.example.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 View.OnClickListener() {
@Override
public void onClick(View v) {
//更新数据
Uri uri = Uri.parse("content://com.example.databasetest. provider/
book/" + newId);
ContentValues values = new ContentValues();
values.put("name", "A Storm of Swords");
values.put("pages", 1216);
values.put("price", 24.05);
getContentResolver().update(uri, values, null, null);
}
});
Button deleteData = (Button) findViewById(R.id.delete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//删除数据
Uri uri = Uri.parse("content://com.example.databasetest. provider/
book/" + newId);
getContentResolver().delete(uri, null, null);
}
});
}
}

可以看到,我们分别在这4个按钮的点击事件里面处理了增删改查的逻辑。添加数据的时候,首先调用了Uri.parse()方法将一个内容URI解析成Uri对象,然后把要添加的数据都存放到ContentValues对象中,接着调用ContentResolver的insert()方法执行添加操作就可以了。注意insert()方法会返回一个Uri对象,这个对象中包含了新增数据的id,我们通过getPathSegments()方法将这个id取出,稍后会用到它。

查询数据的时候,同样是调用了Uri.parse()方法将一个内容URI解析成Uri对象,然后调用ContentResolver的query()方法去查询数据,查询的结果当然还是存放在Cursor对象中的。之后对Cursor进行遍历,从中取出查询结果,并一一打印出来。

更新数据的时候,也是先将内容URI解析成Uri对象,然后把想要更新的数据存放到ContentValues对象中,再调用ContentResolver的update()方法执行更新操作就可以了。注意这里我们为了不想让Book表中的其他行受到影响,在调用Uri.parse()方法时,给内容URI的尾部增加了一个id,而这个id正是添加数据时所返回的。这就表示我们只希望更新刚刚添加的那条数据,Book表中的其他行都不会受影响。

删除数据的时候,也是使用同样的方法解析了一个以id结尾的内容URI,然后调用ContentResolver的delete()方法执行删除操作就可以了。由于我们在内容URI里指定了一个id,因此只会删掉拥有相应id的那行数据,Book表中的其他数据都不会受影响。

运行ProviderTest项目

现在运行一下ProviderTest项目,点击一下Add To Book按钮,此时数据就应该已经添加到DatabaseTest程序的数据库中了,我们可以通过点击Query From Book按钮来检查一下,打印日志如图所示:

然后点击一下Update Book按钮来更新数据,再点击一下Query From Book按钮进行检查,结果如图所示:

最后点击Delete From Book按钮删除数据,此时再点击Query From Book按钮就查询不到数据了。

由此可以看出,我们的跨程序共享数据功能已经成功实现了!现在不仅是ProviderTest程序,任何一个程序都可以轻松访问DatabaseTest中的数据,而且我们还丝毫不用担心隐私数据泄漏的问题。

如果对你有帮助,就一键三连呗(点赞+收藏+关注),我会持续更新更多干货~~

相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui