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来进行进程间通信的服务端和客户端的代码。

最新文章

  1. BPM公文管理解决方案分享
  2. Asp.Net 数据库连接字符串
  3. [转]各种移动GPU压缩纹理的使用方法
  4. 不就ideas嘛,谁没有!
  5. input属性控制弹出键盘类型
  6. 小巧方便的MVC后端验证码,供大家学习借鉴
  7. fsck检查和修复文件系统
  8. wp8 json2csharp
  9. How to Be Good at Mathematics
  10. 【HDOJ】1540 Tunnel Warfare
  11. 使用扩展名获取mimetype
  12. Linux crontab定时器设置(定期执行java程序)(转)
  13. 使用Boost program_options控制程序输入
  14. 关于H5的一些杂思细想(一)
  15. 【安装】Matlab7.0简介及安装
  16. Orchard之Module升级
  17. 全国城市一卡通一级TSM平台业务架构及意义
  18. 使用 pm2-web 监控 pm2 服务运行状态
  19. Python 集合(set)的使用总结
  20. 8.scala:特质

热门文章

  1. Map文件从IDA到OD
  2. 博客文章编辑器 Cmd Markdown
  3. 欧拉路&amp;&amp;欧拉回路
  4. python学习之【第二篇】:Python中的数字及其所具有的方法
  5. NOI导刊集训感言
  6. Vim 便捷 | 自己的Vim
  7. re模块的基本使用
  8. python习题——随机整数生成类
  9. Okhttp3源码解析
  10. 美团店铺数据抓取 token解析与生成