【Android - IPC】之ContentProvider简介
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简介的更多相关文章
- android IPC及原理简介
什么是Android操作系统,所谓的Android:是基于Linux内核的软件平台和操作系统,早期由Google开发,后由开放手机联盟Open Handset Alliance)开发. Linux ...
- Android ContentProvider 简介
当在系统中部署一个又一个Android应用之后,系统里将会包含多个Android应用,有时候就需要在小同的应用之问芡亭数据,比如现在有一个短信接收应用,用户想把接收到的陌生短信的发信人添加到联系人管理 ...
- 【Android - IPC】之Binder机制简介
参考资料: 1.<Android开发艺术探索>第二章2.3.3 Binder 2.[Android Binder设计与实现-设计篇] 3.[Android Binder机制介绍] 1. 什 ...
- android IPC通信(上)-sharedUserId&&Messenger
看了一本书,上面有一章解说了IPC(Inter-Process Communication,进程间通信)通信.决定结合曾经的一篇博客android 两个应用之间的通信与调用和自己的理解来好好整理总结一 ...
- Android四大组件--ContentProvider详解(转)
一.相关ContentProvider概念解析: 1.ContentProvider简介在Android官方指出的Android的数据存储方式总共有五种,分别是:Shared Preferences. ...
- Android组件系列----ContentProvider内容提供者
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- 【转】Android 防破解技术简介
http://www.cnblogs.com/likeandroid/p/4888808.html Android 防破解技术简介 这几年随着互联网的不断发展,Android App 也越来越多!但是 ...
- Android开发之ContentProvider(内容提供者)
1. ContentProvider简介 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.虽然使用其他方法也可以对外共享数据,但数据访问 ...
- Android IPC 结篇
一.概述 Android 的 IPC 方式有 Bundle .共享文件.AIDL .Messenger .ContentProvider .Socket ,我们在实现进程间通信时要选择哪一种方式来实现 ...
- Android 防破解技术简介
Android 防破解技术简介 这几年随着互联网的不断发展,Android App 也越来越多!但是随之而来的问题也越来越多,这其中比较令人头疼的问题就是:有些不法分子利用反编译技术破解 App,修改 ...
随机推荐
- 优化 Git Commit Message
目前很多项目都是通过 Git 进行管理的,Git 每次提交代码的过程中 提交说明 commit message 是必须的.但仅仅必须是不够的,好的提交说明可以帮助我们提高项目的整体质量. 作用与优点 ...
- 使用VM虚拟机安装Linux系统详细流程
最近新换了个电脑,所以需要重新安装虚拟机和Linux系统,话不多说,看流程吧 1.安装vm,这个就不说了,打开VM 2.点击安装虚拟机 3.选择自定义安装 4.选择稍后安装 5.选择要安装的系统 6. ...
- CSPS模拟 75
我身边 mikufun:矩阵树学学学 Lrefrain:矩阵题刷刷刷 Dybala:神仙定理康康康 skyh:讨论讨论讨论(most mei face) DeepinC:我过样例了! Mouding: ...
- CSPS模拟 58
爆炸 没算内存见祖宗 为什么偏偏这次卡内存我没算 T1 HashMap各种水 T2 智障背包!但是卡内存! T3 Dashspeed 考试用点分治+线段树水到了80 实际上是个没见过的套路题 在之前的 ...
- Hive数据仓库你了解了吗
在工作中我们经常使用的数据库,数据库一般存放的我们系统中常用的数据,一般为百万级别.如果数据量庞大,达到千万级.亿级又需要对他们进行关联运算,该怎么办呢? 前面我们已经介绍了HDFS和MapReduc ...
- win10系统任务栏点击没有反应
今天碰到了一个神奇的bug任务栏上的图标怎么也没反应,鼠标点击不了,win+R命令行也出来不了. 经过一番研究,发现CTRL+ALT+DEL,可以打开任务管理器.所以来了一番操作: 1.先打开任务管理 ...
- 『题解』Codeforces9D How many trees?
更好的阅读体验 Portal Portal1: Codeforces Portal2: Luogu Description In one very old text file there was wr ...
- javascript中判断数据类型
编写javascript代码的时候常常要判断变量,字面量的类型,可以用typeof,instanceof,Array.isArray(),等方法,究竟哪一种最方便,最实用,最省心呢?本问探讨这个问题. ...
- 5种常见Bean映射工具的性能比对
本文由 JavaGuide 翻译自 https://www.baeldung.com/java-performance-mapping-frameworks .转载请注明原文地址以及翻译作者. 1. ...
- 雅虎日本如何用 Pulsar 构建日均千亿的消息平台
雅虎日本是一家雅虎和软银合资的日本互联网公司,是日本最受欢迎的门户网站之一.雅虎日本的互联网服务在日本市场占主导地位. 下图从三个维度显示了雅虎日本的经营规模.第一个是服务数量,雅虎日本提供上百种互联 ...