使用ContentProvider实现进程间通信
(1)ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,和Messenger一样,ContentProvider的底层实现同样也是Binder,他的使用比较简单主要原因是系统为我们做了封装。系统其实预置了许多的ContentProvider,比如通讯录信息,日程表信息,等等,要跨进程的访问这些,只需要通过ContentProvider的query,Update,insert和delete方法即可。
(2)自定义一个ContentProvider很简单,只需要继承ContentProvider类并且实现它的六个抽象方法即可。onCreat代表ContentProvider的创建,一般初始化的工作都在这个方法中进行,getType用来返回一个Uri的请求对应的MIME类型(媒体类型),比如图片,视频等,这个类型比较复杂。剩下的都是操作数据表的。依据Binder的原理,这6个方法都在ContentProvider进程中,除了onCreat有系统回调并运行在主线程,其他的均由外界回调并运行在Binder线程池。
(3)ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,他们都具有行和列的层次性,行往往对应一条数据,而列对应一条记录中的一个字段,这点和数据库类似。ContentProvider还支持文件数据,比如图片、视频等。
(4)文件数据和表格数据的结构不同,因此处理这类数据时可以在ContentProvider中返回文件的句柄给外界,从而让外界来访问ContentProvider中的文件信息。Android中的MediaStore就是文件类型的ContentProvider。
(5)ContentProvider对底层的数据存储结构没有具体要求,可以使用SQLite数据库,也可以使用文件,甚至是内存中的一个对象来保存数据。
实例如下:
/**
* 自定义一个ContentProvider,实现其六个抽象的方法,并添加打印信息
* 观察其主要回调在那个线程
*/
public class UserProvider extends ContentProvider {
private static final String TAG="UserProvider";
@Override
public boolean onCreate() {
Log.e(TAG,"onCreate,当前的线程:"+Thread.currentThread().getName());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.e(TAG,"query,当前的线程:"+Thread.currentThread().getName());
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.e(TAG,"getType");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e(TAG,"insert");
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG,"delete");
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG,"update");
return 0;
}
}
然后在清单文件中注册
<provider
android:name=".provider.UserProvider"
<!--必须且唯一的值,它是ContentProvider的唯一标识 -->
android:authorities="com.byd.test.provider"
<!--自定义权限,要想使用必须声明相应的权限才行-->
android:permission="com.byd.test.permission.PROVIDER"
<!--让其运行在独立的进程中-->
android:process=":provider" />
自定义的权限:
<permission
<!--权限的名称-->
android:name="com.byd.test.permission.PROVIDER"
<!--权限的解释->
android:label="@string/provider_string"
<!--权限的等级-->
android:protectionLevel="normal" />
注册完之后,我们只需要在外部应用中去访问它了
public class ProviderActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
/**
* 唯一标识了UserProvider,而这个标识正是我们在清单文件中为UserProvider添加的
*authorities属性所指定的值,
*/
Uri uri=Uri.parse("content://com.byd.test.provider");
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
getContentResolver().query(uri,null,null,null,null);
}
}
我们在另一个应用中使用ContentResolver对象的query方法查询三次了UserProvider中的数据,看Log日志:oncreat在主线程中回调,而query在Binder线程池中回调。
10-17 01:25:07.703 3068-3068/com.byd.fuangde.text:provider E/UserProvider: onCreate,当前的线程:main
10-17 01:25:07.704 3068-3085/com.byd.fuangde.text:provider E/UserProvider: query,当前的线程:Binder_2
10-17 01:25:07.704 3068-3084/com.byd.fuangde.text:provider E/UserProvider: query,当前的线程:Binder_1
10-17 01:25:07.704 3068-3085/com.byd.fuangde.text:provider E/UserProvider: query,当前的线程:Binder_2
完整的流程就是上面的,我们为了能更好的演示一下他的作用,接下来完整一下整个项目
,首先自定义毅哥 数据库来管理用户信息。我们直接使用SQLite。
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME="user_provider.db";
public static final String USER_TABLE_NAME="user";
private static final int VERSION=0;
//建表语句
private String CREATE_USER_TABLE="CREATE TABLE IF NOT EXISTS"+USER_TABLE_NAME+"(_id INTEGER PRIMARY KEY,name,sex)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
//用于创建数据库表的方法,执行数据库建表语句
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_USER_TABLE);
}
//每次都会调用,用于版本更新
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(oldVersion<newVersion){
//删除旧表,建立新表
db.execSQL("drop table if exists user");
onCreate(db);
}
}
// 此时,并没有数据库,只有heper在调用:
// getWritableDatabase(); //两个方法得到的db都可以读写操作,当磁盘满了
// getReadableDatabase(); //第二种不会继续向内存中写,第一种会尝试去写
// 时才会创建
}
完善后的UserProvider:
public class UserProvider extends ContentProvider {
private static final String TAG="UserProvider";
private static final String AUTHORITY="com.byd.test.provider";
private static final Uri USER_CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/user");
private static final int USER_URI_CODE=0;
private static UriMatcher matcher = null;
private Context context;
private SQLiteDatabase mDb;
static {
matcher=new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(AUTHORITY,"user",USER_URI_CODE);
}
private String getTableName(Uri uri){
String tabName=null;
switch(matcher.match(uri)){
case USER_URI_CODE:
tabName= DbOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tabName;
}
@Override
public boolean onCreate() {
Log.e(TAG,"onCreate,当前的线程:"+Thread.currentThread().getName());
context=getContext();
//初始化数据库
mDb=new DbOpenHelper(context).getWritableDatabase();
new Thread(new Runnable() {
@Override
public void run() {
mDb.execSQL("insert into user values(4,'jack','男');");
}
}).start();
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.e(TAG,"query,当前的线程:"+Thread.currentThread().getName());
String table=getTableName(uri);
Cursor cursor=null;
if(table == null){
throw new IllegalArgumentException("Unsupported URI:"+uri);
}
cursor=mDb.query(table,projection,selection,selectionArgs,null,null,sortOrder);
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.e(TAG,"getType");
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.e(TAG,"insert");
String table=getTableName(uri);
if(table == null){
throw new IllegalArgumentException("Unsupported URI:"+uri);
}
long id = mDb.insert(table, null, values);
return ContentUris.withAppendedId(uri,id);
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG,"delete");
String table=getTableName(uri);
if(table == null){
throw new IllegalArgumentException("Unsupported URI:"+uri);
}
int count= mDb.delete(table,selection,selectionArgs);
if(count>0){
getContext().getContentResolver().notifyChange(uri,null);
}
return count;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.e(TAG,"update");
String table=getTableName(uri);
if(table == null){
throw new IllegalArgumentException("Unsupported URI:"+uri);
}
int row=mDb.update(table,values,selection,selectionArgs);
if(row>0)
{
getContext().getContentResolver().notifyChange(uri,null);
}
return row;
}
}
我们在外部访问一下:
public class ProviderActivity extends AppCompatActivity {
private static final String TAG="ProviderActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
/**
* 唯一标识了UserProvider,而这个标识正是我们在清单文件中为UserProvider添加的
*authorities属性所指定的值,
*/
Uri uri=Uri.parse("content://com.byd.test.provider/user");
Cursor cursor = getContentResolver().query(uri, new String[]{"_id", "name", "sex"}, null, null, null);
while(cursor.moveToNext())
{
User user=new User();
user.userId=cursor.getInt(0);
user.userName=cursor.getString(1);
Log.e(TAG,user.toString());
}
cursor.close();
}
}
log日志:
10-17 04:00:16.950 8688-8688/? E/UserProvider: onCreate,当前的线程:main
10-17 04:00:16.952 8688-8696/? I/art: Debugger is no longer active
10-17 04:00:16.955 8688-8705/? E/UserProvider: query,当前的线程:Binder_2
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='1', usereName='tom'}
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='2', usereName='rose'}
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='3', usereName='jack'}
10-17 04:00:16.967 8670-8670/com.byd.fuangde.text E/ProviderActivity: User{userId='4', usereName='jack'}
可以看到已经可以正常处理请求了,由于ProviderActivity和Provider运行在不同的进程中,因此也就是实现了进程间通信。
使用Socket实现进程间通信
Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字,分别对应网络传输层协议中的tcp和udp协议,前者是面向连接的协议,连接的建立需要通过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,具有很高的稳定性。后者是无连接的,提供不稳定的单向通信功能,但是效率较高。
接下来编写一个小型简易的聊天室来展示使用Socket实现进程间通信,首先设计服务端,当Service启动时,会在线程中建立TCP服务,这里监听一个客户端连接请求的端口,当有客户端连进来是,会生成一个新的Socket,通过每次新创建的Socket就可以分别和不同的客户端通信了。服务端每收到一个客户端的消息就会回复客户端一个信息,当客户端断开连接后服务端这边也会 相应的关闭Socket并结束通话线程。
服务端的代码:
public class TCPServerService extends Service {
//定义一个布尔类型的值来记录Server是否被销毁 false:存活 true:被销毁
private boolean mIsServiceDestory=false;
private String [] mDefineMessages=new String[]{"你好啊。爱猴","你是哪里人","你是干啥的啊","你知道吗你可牛逼了"};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestory=true;
super.onDestroy();
}
public class TcpServer implements Runnable{
@SuppressWarnings("resource")
@Override
public void run() {
ServerSocket serverSocket=null;
try {
//这里监听本地的8688端口
serverSocket=new ServerSocket(8688);
} catch (IOException e) {
System.err.println("establish tcp server failed,port:8688");
e.printStackTrace();
return;
}
while(!mIsServiceDestory){
//接收客户端请求
try {
final Socket clientSocket=serverSocket.accept();
System.out.println("accept");
new Thread(){
@Override
public void run() {
try {
responseClient(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void responseClient(Socket client) throws IOException {
//接受客户端信息
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
//用于向客户端发送消息
PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
out.println("欢迎来到byd的聊天室");
while(!mIsServiceDestory)
{
String str=in.readLine();
System.out.println("msg from client:"+str);
if (str== null){
//客户端断开连接
break;
}
int i=new Random().nextInt(mDefineMessages.length);
String msg=mDefineMessages[i];
out.println(msg);
System.out.println("send:"+msg);
System.out.println("client quit.");
//关闭流
out.close();
in.close();
client.close();
}
}
}
}
客户端
public class TCPClientActivity extends AppCompatActivity {
//收到新消息
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
//确定socket是否连接
private static final int MESSAGE_SOCKET_CONNECTED = 2;
@BindView(R.id.show_msg)
TextView showMsg;
@BindView(R.id.input_msg_et)
EditText inputMsgEt;
@BindView(R.id.send_msg_btn)
Button sendMsgBtn;
private Socket mClientSocket;
private PrintWriter mWriter;
@SuppressLint("HandlerLeak")
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what)
{
case MESSAGE_RECEIVE_NEW_MSG:
showMsg.setText(showMsg.getText()+(String) msg.obj);
break;
case MESSAGE_SOCKET_CONNECTED:
sendMsgBtn.setEnabled(true);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
ButterKnife.bind(this);
//开启后端service
Intent intent = new Intent(this, TCPServerService.class);
startService(intent);
new Thread() {
@Override
public void run() {
connectTCPServer();
}
}.start();
}
/**
* 当听关闭客户端时,关闭socket
*/
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
@OnClick(R.id.send_msg_btn)
public void onViewClicked() {
final String msg = inputMsgEt.getText().toString().trim();
if (!TextUtils.isEmpty(msg)) {
mWriter.println(msg);
inputMsgEt.setText("");
String time = new SimpleDateFormat("HH:MM:ss").format(new Date(System.currentTimeMillis()));
final String showMessage = "self" + time + ":" + msg + "\n";
showMsg.setText(showMsg.getText() + showMessage);
}
}
//连接Server
private void connectTCPServer() {
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("localhost", 8688);
mClientSocket=socket;
mWriter=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
//通过Handler通知已经连上了socket
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
System.out.println("connect tcp server failed,retry...");
}
}
try {
//接收服务端的消息
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//当客户端还未退出时
while (!TCPClientActivity.this.isFinishing())
{
String msg = br.readLine();
System.out.println("receive:"+msg);
if(msg!=null)
{
String time = new SimpleDateFormat("HH:MM:ss").format(new Date(System.currentTimeMillis()));
final String showMessage = "server" + time + ":" + msg + "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showMessage).sendToTarget();
}
}
System.out.println("quit...");
mWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/show_msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
android:padding="@dimen/activity_horizontal_margin"
android:text="@string/app_name"
android:textSize="16sp"/>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="50dp"
android:orientation="horizontal"
android:layout_alignParentBottom="true">
<EditText
android:id="@+id/input_msg_et"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="6"
android:textSize="16sp"
/>
<Button
android:id="@+id/send_msg_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:text="发送"
android:textSize="16sp"
android:enabled="false"
android:background="@color/colorPrimary"
android:textColor="#ffffff"/>
</LinearLayout>
</RelativeLayout>
项目已经放到github上,大家可以clone下来自己实现看一下。附一下地址:项目在git上面的地址