0. 前置知识
其实android中追根溯源只有两种进程间通信方式,其他的方式都是通过封装这两种方式而得到的: Binder与Socket Android——Binder机制. Android中Socket通信的简单实现.
首先我们需要知道几点:RPC,IDL,IPC分别是什么。
RPC :
- Remote Procedure Call (远程过程调用) 是一种计算机通讯协议,为我们定义了计算机 C 中的程序如何调用另外一台计算机 S 的程序
- RPC 是 Client/Server 模式,客户端对服务器发出请求,服务器收到请求并且根据客户端提供的参数进行操作,然后结果返回给客户端
- RPC 位于 OSI 模型中的会话层
IDL :
- Interface Description Language (接口定义语言),通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流
- RPC 只是一种协议,规定了通信的规则
- 因为客户端与服务端平台的差异性,为了统一处理不同的实现,需要定义一个共同的接口,即就是IDL
IPC :
- Inter-Process Communication (进程间通信)
- Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”
- 只有允许不同应用的客户端用 IPC 方式调用远程方法,并且想要在服务中处理多线程时,才有必要使用 AIDL
- 如果需要调用远程方法,但不需要处理并发 IPC,就应该通过实现一个 Binder 创建接口
- 如果您想执行 IPC,但只是传递数据,不涉及方法调用,也不需要高并发,就使用 Messenger 来实现接口
- 如果需要处理一对多的进程间数据共享(主要是数据的 CRUD),就使用 ContentProvider
- 如果要实现一对多的并发实时通信,就使用 Socket
1.Intent
Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle实现了 Parcelable 接口,可以在不同的进程间进行传输。
在一个进程中启动了另一个进程的 Activity,Service 和 Receiver ,可以在Bundle 中附加要传递的数据通过 Intent 发送出去。
可以看看,Android——Bundle浅析
2. 文件共享
- Windows 上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而 Android 系统基于 Linux ,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,尽管这样可能会出问题。
- 可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同)
- SharedPreferences 是个特例,系统对它的读 / 写有一定的缓存策略,即内存中会有一份ShardPreferences 文件的缓存,系统对他的读 / 写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很多大的几率丢失数据。因此,IPC 不建议采用 SharedPreferences
3. Messenger
Messenger是一种轻量级的 IPC 方案,它的底层实现是 AIDL ,可以在不同进程中传递 Message 对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。
服务端进程:服务端创建一个 Service 来处理客户端请求,同时通过一个Handler 对象来实例化一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。
代码语言:javascript复制public class MessengerService extends Service {
private static final String TAG = MessengerService.class.getSimpleName();
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MSG_FROM_CLIENT:
Log.d(TAG, "receive msg from client: msg = [" msg.getData().getString(Constants.MSG_KEY) "]");
Toast.makeText(MessengerService.this, "receive msg from client: msg = [" msg.getData().getString(Constants.MSG_KEY) "]", Toast.LENGTH_SHORT).show();
Messenger client = msg.replyTo;
Message replyMsg = Message.obtain(null, Cons
tants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString(Constants.MSG_KEY, "我已经收到你的消息,稍后回复你!");
replyMsg.setData(bundle);
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
客户端进程:首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。
代码语言:javascript复制public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private Messenger mGetReplyMessenger = new Messenger(new MessageHandler());
private Messenger mService;
private class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MSG_FROM_SERVICE:
Log.d(TAG, "received msg form service: msg =[" msg.getData().getString(Constants.MSG_KEY) "]");
Toast.makeText(MainActivity.this, "receivedmsg form service: msg = [" msg.getData().getString(Constants.MSG_KEY) "]", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void bindService(View v) {
Intent mIntent = new Intent(this, MessengerService.class);
bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
public void sendMessage(View v) {
Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString(Constants.MSG_KEY, "Hello! This is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
unbindService(mServiceConnection);
super.onDestroy();
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
/** * @param name * @param service */
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null,Constants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString(Constants.MSG_KEY, "Hello! This is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/** * @param name */
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
注意:客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。只不过客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。Messenger 中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。
4. 使用 AIDL(难点)
AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。
AIDL底层也是通过Binder实现的:Android——Binder机制
Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。
AIDL 文件支持的数据类型:
- 基本数据类型;
- String 和 CharSequence
- ArrayList ,里面的元素必须能够被 AIDL 支持;
- HashMap ,里面的元素必须能够被 AIDL 支持;
- Parcelable ,实现 Parcelable 接口的对象; 注意:如果 AIDL 文件中用到了 自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。
- AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。
服务端:
服务端创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件, 将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可。
客户端:
绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。客户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。
具体使用我们看看这篇博文:Android进阶——AIDL详解 接下来让我们看一个实例:
创建两个工程,一个作为服务端,一个作为客户端,客户端绑定服务端service,然后调用方法向服务端获取书籍列表,向服务端添加书籍。
1、服务端: (1)创建aidl文件Book.aidl
创建后便可以在目录里看到aidl文件。
接下来定义Book类,注意Books类的包名必须与Book.aidl包名一样,但是不可与Book.aidl在同一个目录下。
Book.class的代码如下,其必须继承Parcelable接口:
代码语言:javascript复制package com.status.aidlproject.com.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private String bookName;
public Book(String name) {
bookName = name;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
protected Book(Parcel in) {
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(bookName);
}
public void readFromParcel(Parcel desc) {
bookName = desc.readString();
}
@Override
public String toString() {
return "bookName=" bookName;
}
}
接下来修改Book.aidl文件,将其声明为parcelable类型,并且需要注意的是,原先默认的interface接口需要去掉,否则编译会报错。
代码语言:javascript复制// Book.aidl
package com.status.aidlproject.com.aidl;
// Declare any non-default types here with import statements
parcelable Book;
//需要删除
/*interface Book { void default(String s); }*/
接下来便是要定义服务端暴露给客户端的接口了(获取书籍列表,添加书籍)
在同样的目录定义aidl文件BookManager.aidl
代码如下:
代码语言:javascript复制// BookManager.aidl
package com.status.aidlproject.com.aidl;
// Declare any non-default types here with import statements
import com.status.aidlproject.com.aidl.Book;
interface {
List<Book> getBookList();
void addBook(inout Book book);
}
注意要把包手动导进来。
接下来,make project便可以看到aidl编译成代码文件
这个文件才是我们真正需要用到的。
(2)创建Service,供客户端绑定
代码语言:javascript复制package com.status.aidlproject;
import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
import com.status.aidlproject.com.aidl.Book;
import com.status.aidlproject.com.aidl.BookManager;
import java.util.ArrayList;
import java.util.List;
public class BookService extends Service {
private final String TAG = BookService.class.getSimpleName();
private List<Book> list;
@Override
public void onCreate() {
super.onCreate();
list = new ArrayList<>();
for (int i = 0; i < 5; i ) {
Book book = new Book("第" i "本书");
list.add(book);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return bookManager;
}
private BookManager.Stub bookManager = new BookManager.Stub() {
@Override
public List<Book> getBookList() {
Log.d(TAG, "getBookList");
return list;
}
@Override
public void addBook(Book book) {
Log.d(TAG, "addBook");
if (book != null) {
list.add(book);
}
Log.d(TAG, book.toString());
}
};
@Override
public void unbindService(ServiceConnection conn) {
super.unbindService(conn);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
Manifests文件中可以这样写:
代码语言:javascript复制<service
android:name=".BookService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.BookService" />
</intent-filter>
</service>
onBind方法返回的是BookManager.Stub对象,实现里面的两个方法,客户端拿到这个对象后就可以与服务端通讯了。
2、客户端
(1)将aidl文件与Book.class拷贝到客户端的工程,注意他们的目录需要与服务端的一样,也就是说aidl与Book.class包名要与服务端的一样。
make project一下便会生成编译后的文件
(2)、编写代码,与服务端连接
代码语言:javascript复制package com.status.aidlclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.status.aidlproject.com.aidl.Book;
import com.status.aidlproject.com.aidl.BookManager;
import com.status.aidlproject.com.aidl.IMyConnect;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private final String TAG = MainActivity.class.getSimpleName();private TextView textView3;
private TextView textView4;
private TextView textView5;
private final String ACTION1 = "android.intent.action.ConnectService";
private final String ACTION2 = "android.intent.action.BookService";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView3 = findViewById(R.id.text3);
textView4 = findViewById(R.id.text4);
textView5 = findViewById(R.id.text5);
textView3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ACTION2);
// 注意在 Android 5.0以后,不能通过隐式 Intent 启动 service,必须制定包名
intent.setPackage("com.status.aidlproject");
bindService(intent, connection2, Context.BIND_AUTO_CREATE);
}
});
textView4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
if (manager != null) {
List<Book> list = manager.getBookList();
Log.d(TAG, "getBookList:" list);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
textView5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Book book = new Book("添加的书");
try {
if (manager != null) {
manager.addBook(book);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private BookManager manager;
//个实现ServiceConnection接口
ServiceConnection connection2 = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
manager = BookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
manager = null;
}
};
}
textView3用于绑定服务端
textView4用于获取服务端书籍列表
textView5用于向服务端添加书籍
5. 使用 ContentProvider
用于不同应用间数据共享,和 Messenger 底层实现同样是 Binder 和 AIDL,系统做了封装,使用简单。 系统预置了许多 ContentProvider ,如通讯录、日程表,需要跨进程访问。
使用方法: 继承 ContentProvider 类实现 6 个抽象方法,这六个方法均运行在 ContentProvider 进程中,除 onCreate 运行在主线程里,其他五个方法均由外界回调运行在 Binder 线程池中。
ContentProvider 的底层数据,可以是 SQLite 数据库,可以是文件,也可以是内存中的数据。
举个例子: ContentProvider需要媒介进行数据存储, 最常用的就是SQLite数据库., 本示例使用两张表, 书籍和用户.
代码语言:javascript复制public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private static final int DB_VERSION = 1;
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
BOOK_TABLE_NAME "(_id INTEGER PRIMARY KEY, name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
USER_TABLE_NAME "(_id INTEGER PRIMARY KEY, name TEXT, sex INT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
ContentProvider提供数据访问的接口, CRUD增删改查. 在onCreate中, 初始化数据库, 并添加数据.
代码语言:javascript复制@Override public boolean onCreate() {
showLogs("onCreate 当前线程: " Thread.currentThread().getName());
mContext = getContext();
initProviderData(); // 初始化Provider数据
return false;
}
private void initProviderData() {
mDb = new DbOpenHelper(mContext).getWritableDatabase();
mDb.execSQL("delete from " DbOpenHelper.BOOK_TABLE_NAME);
mDb.execSQL("delete from " DbOpenHelper.USER_TABLE_NAME);
mDb.execSQL("insert into book values(3,'Android');");
mDb.execSQL("insert into book values(4, 'iOS');");
mDb.execSQL("insert into book values(5, 'HTML5');");
mDb.execSQL("insert into user values(1, 'Spike', 1);");
mDb.execSQL("insert into user values(2, 'Wang', 0);");
}
CRUD的参数是Uri, 数据库需要使用表名, 为了便于从Uri映射到表名, 使用关系转换
代码语言:javascript复制private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
添加数据insert, 可以注册内容改变的监听, 插入数据时, 广播更新, 即notifyChange.
代码语言:javascript复制@Nullable @Override public Uri insert(Uri uri, ContentValues values) {
showLogs("insert");
String table = getTableName(uri);
if (TextUtils.isEmpty(table)) {
throw new IllegalArgumentException("Unsupported URI: " uri);
}
mDb.insert(table, null, values);
// 插入数据后通知改变
mContext.getContentResolver().notifyChange(uri, null);
return null;
}
删除数据delete, 返回删除数据的数量, 大于0即删除成功.
代码语言:javascript复制@Override public int delete(Uri uri, String selection, String[] selectionArgs) {
showLogs("delete");
String table = getTableName(uri);
if (TextUtils.isEmpty(table)) {
throw new IllegalArgumentException("Unsupported URI: " uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return count; // 返回删除的函数
}
修改数据update, 与删除类似, 返回修改数据的数量.
代码语言:javascript复制@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
showLogs("update");
String table = getTableName(uri);
if (TextUtils.isEmpty(table)) {
throw new IllegalArgumentException("Unsupported URI: " uri);
}
int row = mDb.update(table, values, selection, selectionArgs);
if (row > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return row; // 返回更新的行数
}
查询数据query, 返回数据库的游标, 处理数据.
代码语言:javascript复制@Nullable @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
showLogs("query 当前线程: " Thread.currentThread().getName());
String tableName = getTableName(uri);
if (TextUtils.isEmpty(tableName)) {
throw new IllegalArgumentException("Unsupported URI: " uri);
}
return mDb.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}
注意Uri和表名的转换可能为空, 使用TextUtils.isEmpty判空.
使用ContentProvider的独立进程, 模拟进程间共享数据.
代码语言:javascript复制<provider
android:name=".BookProvider"
android:authorities="org.wangchenlong.book.provider"
android:permission="org.wangchenlong.BOOK_PROVIDER"
android:process=":provider"/>
在AndroidManifest中, 把Provider注册在:provider进程中, 与主进程分离.
添加数据, 通过Uri找到ContentProvider, 使用ContentResolver的insert方法, 添加ContentValues数据.
代码语言:javascript复制public void addBooks(View view) {
Uri bookUri = BookProvider.BOOK_CONTENT_URI;
ContentValues values = new ContentValues();
values.put("_id", 6);
values.put("name", "信仰上帝");
getContentResolver().insert(bookUri, values);
}
查询数据query, 与数据库的使用方式类似, 解析出Cursor, 通过移动Cursor, 找到所有匹配的结果.
代码语言:javascript复制public void showBooks(View view) {
String content = "";
Uri bookUri = BookProvider.BOOK_CONTENT_URI;
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{
"_id", "name"}, null, null, null);
if (bookCursor != null) {
while (bookCursor.moveToNext()) {
Book book = new Book();
book.bookId = bookCursor.getInt(0);
book.bookName = bookCursor.getString(1);
content = book.toString() "n";
Log.e(TAG, "query book: " book.toString());
mTvShowBooks.setText(content);
}
bookCursor.close();
}
}
ContentProvider封装了跨进程共享的逻辑, 我们只需要Uri即可访问数据, 使用共享数据非常便捷, 需要掌握简单的使用方式.
6. Socket
- Socket也称作“套接字“,是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。
- 分为流式套接字和数据包套接字,分别对应网络传输控制层的TCP和UDP协议。
- TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。它使用三次握手协议建立连接,并且提供了超时重传机制,具有很高的稳定性。
- UDP协议则是是一种无连接的协议,且不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP传输的可靠性由应用层负责。在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多。
Socket 本身可以传输任意字节流。
谈到Socket,就必须要说一说 TCP/IP 五层网络模型:
- 应用层:规定应用程序的数据格式,主要的协议 HTTP,FTP,WebSocket,POP3 等;
- 传输层:建立“端口到端口” 的通信,主要的协议:TCP,UDP;
- 网络层:建立”主机到主机”的通信,主要的协议:IP,ARP ,IP 协议的主要作用:一个是为每一台计算机分配 IP 地址,另一个是确定哪些地址在同一子网;
- 数据链路层:确定电信号的分组方式,主要的协议:以太网协议;
- 物理层:负责电信号的传输。
Socket 是连接应用层与传输层之间接口(API)。
看看具体的函数:
代码语言:javascript复制#include <sys/socket.h>
#include <unistd.h>
#include <unistd.h>
int socket(int protofamily, int type, int protocol);//创建socket
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定socket
int listen(int sockfd, int backlog);//监听端口号
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//客户端请求建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//服务端接收连接请求
ssize_t send(int sockfd, const void *buf, size_t len, int flags); //IO写函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);//IO读函数
int close(int fd); //关闭函数
当我们使用socket来进行进程间的通信时,实际是通过将IP设置为127.0.0.1这个本地IP来实现的,Android系统为我们提供了LocalSocket来进行进程间的通信,LocalSocket的实质也是对Socket的封装,通过直接使用LocalSocket,我们省掉了设置本机IP等一系列繁琐的操作。
举个例子: 用Socket实现跨进程聊天程序
配置:
首先我们来实现服务端,当然要使用Socket我们需要在AndroidManifest.xml声明如下的权限:
代码语言:javascript复制<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
我们需要实现一个远程的Service来当作聊天程序的服务端,AndroidManifest.xml文件中配置service:
代码语言:javascript复制<service android:name=".SocketServerService" android:process=":remote" />
实现Service: 接下来我们在Service启动时,在线程中建立TCP服务,我们监听的是8688端口,等待客户端连接,当客户端连接时就会生成Socket。通过每次创建的Socket就可以和不同的客户端通信了。当客户端断开连接时,服务端也会关闭Socket并结束结束通话线程。服务端首先会向客户端发送一条消息:“您好,我是服务端”,并接收客户端发来的消息,将收到的消息进行加工再返回给客户端。
代码语言:javascript复制package com.example.liuwangshu.moonsocket;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerService extends Service {
private boolean isServiceDestroyed = false;
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket;
try {
//监听8688端口
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
return;
}
while (!isServiceDestroyed) {
try {
// 接受客户端请求,并且阻塞直到接收到消息
final Socket client = serverSocket.accept();
new Thread() {
@Override
public void run() {
try {
responseClient(client);
} 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("您好,我是服务端");
while (!isServiceDestroyed) {
String str = in.readLine();
Log.i("moon", "收到客户端发来的信息" str);
if (TextUtils.isEmpty(str)) {
//客户端断开了连接
Log.i("moon", "客户端断开连接");
break;
}
String message = "收到了客户端的信息为:" str;
// 从客户端收到的消息加工再发送给客户端
out.println(message);
}
out.close();
in.close();
client.close();
}
@Override
public void onDestroy() {
isServiceDestroyed = true;
super.onDestroy();
}
}
实现聊天程序客户端:
客户端Activity会在onCreate方法中启动服务端,并开启线程连接服务端Socket。为了确保能连接成功,采用了超时重连的策略,每次连接失败时都会重新建立连接。连接成功后,客户端会收到服务端发送的消息:“您好,我是服务端”,我们也可以在EditText输入字符并发送到服务端。
代码语言:javascript复制package com.example.liuwangshu.moonsocket;
import android.content.Intent;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
public class SocketClientActivity extends AppCompatActivity {
private Button bt_send;
private EditText et_receive;
private Socket mClientSocket;
private PrintWriter mPrintWriter;
private TextView tv_message;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_socket);
initView();
Intent service = new Intent(this, SocketServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectSocketServer();
}
}.start();
}
private void initView() {
et_receive= (EditText) findViewById(R.id.et_receive);
bt_send= (Button) findViewById(R.id.bt_send);
tv_message= (TextView) this.findViewById(R.id.tv_message);
bt_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String msg = et_receive.getText().toString();
//向服务器发送信息
if(!TextUtils.isEmpty(msg)&&null!=mPrintWriter) {
mPrintWriter.println(msg);
tv_message.setText(tv_message.getText() "n" "客户端:" msg);
et_receive.setText("");
}
}
});
}
private void connectSocketServer() {
Socket socket = null;
while (socket == null) {
try {
//选择和服务器相同的端口8688
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
} catch (IOException e) {
SystemClock.sleep(1000);
}
}
try {
// 接收服务器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!isFinishing()) {
final String msg = br.readLine();
if (msg != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tv_message.setText(tv_message.getText() "n" "服务端:" msg);
}
}
);
}
}
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/183136.html原文链接:https://javaforall.cn