1、Content Provider简介

  Content Provider是Android中提供的一种专门用于不同应用之间进行数据共享的方式,从这一点来看,它天生就适合IPC(Inter-Process Communication,进程间通信)。Content Provider的底层实现是Binder,但它比AIDL要简单很多,因此系统已经给我们做了封装。

  Android系统为我们预置了许多Content Provider,比如通讯录信息、日程表信息等,要想访问这些应用的信息,只需要调用ContentResolver的insert、update、delete、query四个方法即可。但是,我们往往不止需要这些应用的数据,因此,在很多情况下,我们需要创建自定义的Content Provider。

  Content Provider主要是以表格的形式来组织数据的,并且可以包含多张表。除了表格的形式,Content Provider还支持文件格式,比如图片、视频等。

2、Content Provider实例

  在这个例子中,我们要在客户端操作服务端的SQLite数据库,包括查询、删除、添加和修改的操作。

2.1、服务端

  在服务端中,我们先创建一个SQLite数据库,名字叫做server.db,数据库中有一张数据表,名字叫做book表,book表中有两个字段:bookId表示书籍的编号,bookName表示书籍的名称。

  我们创建一个MyDBOpenHelper类继承自SQLiteOpenHelper,来初始化数据库中的信息,代码如下:

package my.itgungnir.server;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; /**
* 操作数据库的SQLiteOpenHelper工具类
* Created by ITGungnir on 2017/4/6.
*/
public class MyDBOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "server.db";
public static final String TABLE_NAME = "book"; private static final int DB_VERSION = 1; public MyDBOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
} @Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(bookId INTEGER PRIMARY KEY, bookName TEXT);");
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}

  有了数据库之后,我们就可以创建一个Content Provider,用来提供操作数据库的接口。

  我们创建一个Content Provider类BookProvider,继承自ContentProvider类,并实现onCreate()、insert()、update()、delete()、query()和getType()这六个方法。其中,onCreate()方法是ContentProvider创建的时候调用的方法,可以在这个方法中做一些初始化的操作;getType()方法用来返回一个 Uri 请求所对应的MIME类型(媒体类型,比如图片、视频等),如果不关注这个选项,可以直接返回null或“*/*”;剩下的四个方法用于CRUD操作,即实现对数据表的增删改查功能。注意:这六个方法均运行在ContentProvider的进程中,除了onCreate()方法运行在主线程之外,其他五个方法都运行在Binder线程池中。

  下面是我们创建的BookProvider类中的代码:

package my.itgungnir.server;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; /**
* 服务端的ContentProvider
* Created by ITGungnir on 2017/4/6.
*/
public class BookProvider extends ContentProvider {
private static final String AUTHORITY = "my.itgungnir.server.book_provider";
private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
private static final int URI_CODE = 0x001;
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); static {
URI_MATCHER.addURI(AUTHORITY, "book", URI_CODE);
} private Context context;
private SQLiteDatabase database; private String getTableName(Uri uri) {
if (URI_MATCHER.match(uri) == URI_CODE) {
return MyDBOpenHelper.TABLE_NAME;
}
return null;
} private void initData() {
database = new MyDBOpenHelper(context).getWritableDatabase();
database.execSQL("DELETE FROM " + MyDBOpenHelper.TABLE_NAME + ";");
database.execSQL("INSERT INTO " + MyDBOpenHelper.TABLE_NAME + " VALUES(1, 'Android');");
database.execSQL("INSERT INTO " + MyDBOpenHelper.TABLE_NAME + " VALUES(2, 'iOS');");
database.execSQL("INSERT INTO " + MyDBOpenHelper.TABLE_NAME + " VALUES(3, 'HTML5');");
} @Override
public boolean onCreate() {
context = getContext();
initData();
return true;
} @Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return database.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
} @Nullable
@Override
public String getType(@NonNull Uri uri) {
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return null;
} @Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
database.insert(tableName, null, values);
context.getContentResolver().notifyChange(uri, null);
return uri;
} @Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int count = database.delete(tableName, selection, selectionArgs);
if (count > 0) {
context.getContentResolver().notifyChange(uri, null);
}
return count;
} @Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
String tableName = getTableName(uri);
if (tableName == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int count = database.update(tableName, values, selection, selectionArgs);
if (count > 0) {
context.getContentResolver().notifyChange(uri, null);
}
return count;
}
}

  从上面的代码中可以看到,Content Provider是通过Uri来区分外界想要访问的数据集合的。为了知道外界要访问的是哪张数据表,我们需要为每张表都定义单独的Uri和Uri Code,并将Uri和Uri Code相关联,这一步可以通过UriMatcher的AddUri()方法来实现。这样一来,我们就可以根据外界请求的Uri来得到Uri Code,进而知道外界想要访问哪张表,从而进行相应的数据操作。

  需要注意的是,query()、update()、insert()和delete()四个方法是存在多线程的并发访问的,因此方法内部最好做好线程同步,但是,本例中由于只有一个SQLiteDatabase,因此可以自动处理线程同步,原因是:SQLiteDatabase内部对数据库的操作时有同步处理的。但是,如果有多个SQLiteDatabase对象来操作数据库,就无法保证线程的同步了。

  由于Content Provider属于Android四大组件,因此,需要在项目的Menifest文件中进行注册。在注册声明Content Provider的时候,需要提供一个name属性和一个authorities属性,name属性是要注册的Content Provider的包路径,而authorities属性是Content Provider的唯一标识。另外,如果服务端和客户端在不同的进程中,我们还需要指定Content Provider的exported属性为true,否则会报错:Permission Denied。下面是我们项目服务端Conttent Provider的注册代码:

<provider
android:name=".BookProvider"
android:authorities="my.itgungnir.server.book_provider"
android:exported="true">
</provider>

  以上就是服务端的代码,可以直接运行。

2.2、客户端

  在客户端,我们提供增、删、改、查四个按钮,点击按钮进行对应的操作。界面如下图所示:

  从图中可以看到,点击“Query”按钮的时候,从服务端远程查询所有的书籍信息,显示到最上面的TextView中;点击“Delete”按钮的时候,从上面的EditText中获取要删除的书籍的ID,然后删除书籍;点击“Insert”按钮的时候,从上面的两个EditText中分别去除要添加的书籍的ID和名称,添加到服务端数据库中;点击“Update”按钮的时候,从上面的两个EditText中取出要更新的书籍的ID和名称,然后进行修改。下面是这个界面的布局代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="15.0dip"> <TextView
android:id="@+id/client_tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16.0sp" /> <Button
android:id="@+id/client_btn_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15.0dip"
android:onClick="function_query"
android:text="Query" /> <EditText
android:id="@+id/client_input_deleteid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Book ID" /> <Button
android:id="@+id/client_btn_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15.0dip"
android:onClick="function_delete"
android:text="Delete" /> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15.0dip"
android:orientation="horizontal"> <EditText
android:id="@+id/client_input_insertid"
android:layout_width="0.0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Book ID" /> <EditText
android:id="@+id/client_input_insertname"
android:layout_width="0.0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Book Name" />
</LinearLayout> <Button
android:id="@+id/client_btn_insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15.0dip"
android:onClick="function_insert"
android:text="Insert" /> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15.0dip"
android:orientation="horizontal"> <EditText
android:id="@+id/client_input_updateid"
android:layout_width="0.0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Book ID" /> <EditText
android:id="@+id/client_input_updatename"
android:layout_width="0.0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Book Name" />
</LinearLayout> <Button
android:id="@+id/client_btn_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15.0dip"
android:onClick="function_update"
android:text="Update" /> </LinearLayout>

  在MainActiviy中,获取到界面中的控件,指定好Content Provider的Uri,然后就可以写四个Button的点击事件了。MainActivity中的代码如下:

package my.itgungnir.client;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast; public class MainActivity extends AppCompatActivity {
private static final String TAG = "Client"; private TextView tv_result;
private EditText input_deleteId, input_updateId, input_updateName, input_insertId, input_insertName; private Uri uri;
private ContentResolver resolver; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initComponents();
} private void initComponents() {
tv_result = (TextView) findViewById(R.id.client_tv_result);
input_deleteId = (EditText) findViewById(R.id.client_input_deleteid);
input_updateId = (EditText) findViewById(R.id.client_input_updateid);
input_updateName = (EditText) findViewById(R.id.client_input_updatename);
input_insertId = (EditText) findViewById(R.id.client_input_insertid);
input_insertName = (EditText) findViewById(R.id.client_input_insertname);
uri = Uri.parse("content://my.itgungnir.server.book_provider/book");
resolver = getContentResolver();
} public void function_query(View v) {
String resultStr = "Book List:\n";
Cursor cursor = resolver.query(uri, null, null, null, null);
while (cursor.moveToNext()) {
Book book = new Book(cursor.getInt(cursor.getColumnIndex("bookId")), cursor.getString(cursor.getColumnIndex("bookName")));
resultStr += book.toString();
}
cursor.close();
tv_result.setText(resultStr);
} public void function_delete(View v) {
String bookIdStr = input_deleteId.getEditableText().toString().trim();
if ("".equals(bookIdStr)) {
toast("Book ID cannot be empty!");
} else {
int count = resolver.delete(uri, "bookId=?", new String[]{bookIdStr});
if (count > 0) {
toast("Delete Successfully.");
input_deleteId.setText("");
} else {
toast("Delete Failed...");
}
}
} public void function_insert(View v) {
String bookId = input_insertId.getEditableText().toString().trim();
String bookName = input_insertName.getEditableText().toString().trim();
if ("".equals(bookId) || "".equals(bookName)) {
toast("Both input cannot be empty!");
} else {
ContentValues values = new ContentValues();
values.put("bookId", Integer.parseInt(bookId));
values.put("bookName", bookName);
resolver.insert(uri, values);
toast("Insert successfully.");
input_insertId.setText("");
input_insertName.setText("");
}
} public void function_update(View v) {
String bookId = input_updateId.getEditableText().toString().trim();
String bookName = input_updateName.getEditableText().toString().trim();
if ("".equals(bookId) || "".equals(bookName)) {
toast("Both input cannot be empty!");
} else {
ContentValues values = new ContentValues();
values.put("bookName", bookName);
int count = resolver.update(uri, values, "bookId=?", new String[]{bookId});
if (count > 0) {
toast("Update successfully.");
input_updateId.setText("");
input_updateName.setText("");
}
}
} private void toast(String text) {
Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
}
}

  以上就是使用Android四大组件中的Content Provider来进行进程间通信的服务端和客户端的代码。

【Android - IPC】之ContentProvider简介的更多相关文章

  1. android IPC及原理简介

    什么是Android操作系统,所谓的Android:是基于Linux内核的软件平台和操作系统,早期由Google开发,后由开放手机联盟Open Handset Alliance)开发.   Linux ...

  2. Android ContentProvider 简介

    当在系统中部署一个又一个Android应用之后,系统里将会包含多个Android应用,有时候就需要在小同的应用之问芡亭数据,比如现在有一个短信接收应用,用户想把接收到的陌生短信的发信人添加到联系人管理 ...

  3. 【Android - IPC】之Binder机制简介

    参考资料: 1.<Android开发艺术探索>第二章2.3.3 Binder 2.[Android Binder设计与实现-设计篇] 3.[Android Binder机制介绍] 1. 什 ...

  4. android IPC通信(上)-sharedUserId&amp;&amp;Messenger

    看了一本书,上面有一章解说了IPC(Inter-Process Communication,进程间通信)通信.决定结合曾经的一篇博客android 两个应用之间的通信与调用和自己的理解来好好整理总结一 ...

  5. Android四大组件--ContentProvider详解(转)

    一.相关ContentProvider概念解析: 1.ContentProvider简介在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences. ...

  6. Android组件系列----ContentProvider内容提供者

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  7. 【转】Android 防破解技术简介

    http://www.cnblogs.com/likeandroid/p/4888808.html Android 防破解技术简介 这几年随着互联网的不断发展,Android App 也越来越多!但是 ...

  8. Android开发之ContentProvider(内容提供者)

    1. ContentProvider简介 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.虽然使用其他方法也可以对外共享数据,但数据访问 ...

  9. Android IPC 结篇

    一.概述 Android 的 IPC 方式有 Bundle .共享文件.AIDL .Messenger .ContentProvider .Socket ,我们在实现进程间通信时要选择哪一种方式来实现 ...

  10. Android 防破解技术简介

    Android 防破解技术简介 这几年随着互联网的不断发展,Android App 也越来越多!但是随之而来的问题也越来越多,这其中比较令人头疼的问题就是:有些不法分子利用反编译技术破解 App,修改 ...

随机推荐

  1. 优化 Git Commit Message

    目前很多项目都是通过 Git 进行管理的,Git 每次提交代码的过程中 提交说明 commit message 是必须的.但仅仅必须是不够的,好的提交说明可以帮助我们提高项目的整体质量. 作用与优点 ...

  2. 使用VM虚拟机安装Linux系统详细流程

    最近新换了个电脑,所以需要重新安装虚拟机和Linux系统,话不多说,看流程吧 1.安装vm,这个就不说了,打开VM 2.点击安装虚拟机 3.选择自定义安装 4.选择稍后安装 5.选择要安装的系统 6. ...

  3. CSPS模拟 75

    我身边 mikufun:矩阵树学学学 Lrefrain:矩阵题刷刷刷 Dybala:神仙定理康康康 skyh:讨论讨论讨论(most mei face) DeepinC:我过样例了! Mouding: ...

  4. CSPS模拟 58

    爆炸 没算内存见祖宗 为什么偏偏这次卡内存我没算 T1 HashMap各种水 T2 智障背包!但是卡内存! T3 Dashspeed 考试用点分治+线段树水到了80 实际上是个没见过的套路题 在之前的 ...

  5. Hive数据仓库你了解了吗

    在工作中我们经常使用的数据库,数据库一般存放的我们系统中常用的数据,一般为百万级别.如果数据量庞大,达到千万级.亿级又需要对他们进行关联运算,该怎么办呢? 前面我们已经介绍了HDFS和MapReduc ...

  6. win10系统任务栏点击没有反应

    今天碰到了一个神奇的bug任务栏上的图标怎么也没反应,鼠标点击不了,win+R命令行也出来不了. 经过一番研究,发现CTRL+ALT+DEL,可以打开任务管理器.所以来了一番操作: 1.先打开任务管理 ...

  7. 『题解』Codeforces9D How many trees?

    更好的阅读体验 Portal Portal1: Codeforces Portal2: Luogu Description In one very old text file there was wr ...

  8. javascript中判断数据类型

    编写javascript代码的时候常常要判断变量,字面量的类型,可以用typeof,instanceof,Array.isArray(),等方法,究竟哪一种最方便,最实用,最省心呢?本问探讨这个问题. ...

  9. 5种常见Bean映射工具的性能比对

    本文由 JavaGuide 翻译自 https://www.baeldung.com/java-performance-mapping-frameworks .转载请注明原文地址以及翻译作者. 1. ...

  10. 雅虎日本如何用 Pulsar 构建日均千亿的消息平台

    雅虎日本是一家雅虎和软银合资的日本互联网公司,是日本最受欢迎的门户网站之一.雅虎日本的互联网服务在日本市场占主导地位. 下图从三个维度显示了雅虎日本的经营规模.第一个是服务数量,雅虎日本提供上百种互联 ...