世事洞明皆学问,人情练达即文章。这篇文章主要讲述Android开发艺术探索读书笔记——进程间通信相关的知识,希望能为你提供帮助。
1. 多进程使用场景 1) 应用某些模块因为特殊需求需要运行在单独进程中。如消息推送,
使消息推送进程与应用进程能单独存活,
消息推送进程不会因为应用程序进程crash而受影响。
2) 为加大一个应用可使用的内存,
需要多进程来获取多份内存空间。
2. 如何开启多进程 给四大组件(
Activity、Service、Receiver、ContentProvider)
在androidMainfest中指定android:process属性指定。
- 如果进程以”:”开头的进程, 代表应用的私有进程, 其他应用的组件不可以和它跑在同一个进程中;
- 进程不以”:”开头的进程属于全局进程, 其他应用可通过shareUID可以和它跑在同一个进程中。
- 若两个不同应用设置了相同的shareUID,它们之间想共享数据, 还需要有相同的签名才可以。
Android会为每一个应用/每一个进程分配一个独立的虚拟机, 不同虚拟机在内存分配上有不同的地址空间, 这就导致不同虚拟机中访问同一个类对象会产生多个副本。
(2) 线程同步机制失效;
因为不同进程不是同一块内存, 不同进程锁的不是同一对象。
(3) SharedPreferences可靠性下降;
SharedPreferences底层是通过读/写XML文件实现的, 并发写可能会出问题, 所以它不支持多个进程同时去执行写操作, 否则会导致一定几率的数据丢失。
(4) Application会创建多次;
当一个组件跑在一个新进程中, 系统会给它重新分配独立虚拟机, 这其实就是启动一个应用的过程, 故运行在不同进程中的组件属于不同的虚拟机和不同的Application。
4. 数据序列化 Intent和Binder传输数据时, 或是对象持久化转存/通过网络传输给其他客户端时, 需要使用Parcelable或Serializable将对象转换成可以传输的形式。
(1) Serializable接口
Serializable是java提供的一个序列化接口, 它是一个空接口, 想要某个类实现序列化, 只需要相应类实现Serializable接口即可。Serializable是借助ObjectOutputStream和ObjectInputStream实现对象的序列化和反序列化
一般在相应类实现Serializable类中还会定义一个final long型的serialVersionUID, 不用它也能实现对象的序列化, 它是用来辅助反序列化的。序列化时会把当前类的serialVersionUID写入序列化文件中, 当反序列化系统会检测文件中的serialVersionUID, 看它是否和当前类的serialVersionUID一致, 如果一致说明序列化的类的版本和当前类的版本相同, 这时可反序化成功; 否则说明当前类和序列化的类相比发生了变换, 如增加或减少某个成员变量, 这时无法正常反序列化。
(2) Parcelable接口
在序列化过程中需要实现的功能有:
1) 序列化: 由writeToParcel方法完成, 最终通过Parcel中的一系列的write方法完成;
2) 反序列化: 由CREATOR完成, 内部标识了如何创建序列化对象和数组, 最终通过Parcel的一系列read方法来完成反序列化;
3) 内容描述符: 由describeContents方法来完成, 几乎所有情况下该方法都返回0,仅当当前对象中存在文件描述符时返回1。
(3)两者区别
Serializable是Java中序列化接口, 使用简单但开销大, 序列和反序列化需要大量I/O操作;
Parcelable是Android中特有的序列化接口, 使用复杂但开销小, 效率高, 是Android推荐的序列化方法;
Parcelable主要用在内存序列化上,如果将对象序列化到存储设备或将对象序列化后通过网络传输, 过程会比较复杂, 建议使用Serializable。
5. Binder Binder是什么?
- Binder是Android中的一个类, 它继承了IBinder接口;
- 从IPC角度来说, Binder是Android中一种跨进程通信的方式;
- Binder还可以理解为一种虚拟的物理设备, 它的设备驱动是/dev/binder;
- 从AndroidFramework角度来说, Binder是ServiceManager连接各种Manager( ActivityManager, WindowManager) 和相应ManagerService的桥梁;
- 从Android应用层来说, Binder是客户端和服务端进行通信的媒介, 当bindService时, 服务端会返回一个包含了服务端业务的Binder对象, 通过这个Binder对象, 客户端就可以获取服务端提供的服务或数据, 这里的服务包括普通服务和基于AIDL的服务。
6.2 使用文件共享 两个进程通过读/写同一个文件来交换数据, 还可以序列化一个对象到文件系统中, 从另一个进程中恢复这个对象。它的实现原理是通过ObjectOutputStream将文件写入文件中, 再通过ObjectInputSteam将文件恢复。通过文件共享的方式有一定局限性, 如并发读/写, 读出的内容可能不是最新的, 并发写就可能导致数据混乱。因此尽量避免并发写这种操作, 或考虑用线程同步来限制多个线程的写操作。文件共享适合在对数据要求不高的进程之间通信。
SharedPreferences是Android提供的一种轻量级存储方案, 它通过键值对方式存储数据, 底层它是采用XML来存储键值对。由于系统对SharedPreferences的读写有一定的缓存策略, 即在内存中会有一份SharedPreferences文件的缓存, 在多进程模式下, 系统对它的读/写就变得不可靠, 当面对高并发读/写访问时, SharedPreferences会有很大几率会丢失数据。因此不建议在进程间通信中使用SharedPreferences。
6.3 使用Messenger Messenager可以在不同进程中传递Message对象, 在Message中放入我们要传递的数据。它的底层实现是AIDL, 下面是Messenger的两个构造方法:
public Messenger(Handler target) {
mTarget =
target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget =
IMessenger.Stub.asInterface(target);
}
不管是IMessenger还是Stub.asInterface, 这种使用方法都表明它的底层是AIDL, 它一次只处理一个请求, 不存在线程同步问题。
实现步骤:
(1) 服务端进程
1) 定义一个Service用于客户端的绑定, 建立一个Handler, 在handleMessage里处理客户端发送过来的消息。
//用ServiceHandler接收并处理来自于客户端的消息
private class ServiceHandler extends Handler {
@
Override
public void handleMessage(Message msg) {
if(msg.what =
=
RECEIVE_MESSAGE_CODE){
Bundle data =
msg.getData();
if(data !=
null){
String str =
data.getString("
msg"
);
}
//通过Message的replyTo获取到客户端自身的Messenger,
//Service可以通过它向客户端发送消息
clientMessenger =
msg.replyTo;
if(clientMessenger !=
null){
Message msgToClient =
Message.obtain();
msgToClient.what =
SEND_MESSAGE_CODE;
//可以通过Bundle发送跨进程的信息
Bundle bundle =
new Bundle();
bundle.putString("
msg"
, "
你好,
客户端,
我是MyService"
);
msgToClient.setData(bundle);
try{
clientMessenger.send(msgToClient);
}catch (RemoteException e){
e.printStackTrace();
Log.e("
DemoLog"
, "
向客户端发送信息失败: "
+
e.getMessage());
}
}
}
}
}
2) 通过Handler创建一个Messenger对象
//serviceMessenger是Service自身的Messenger,
其内部指向了ServiceHandler的实例
//客户端可以通过IBinder构建Service端的Messenger,
从而向Service发送消息,
//并由ServiceHandler接收并处理来自于客户端的消息
private Messenger serviceMessenger =
new Messenger(new ServiceHandler());
3) 在Service的onBind中通过Messenger.getBinder()返回底层的Binder对象。
@
Override
public IBinder onBind(Intent intent) {
Log.i("
DemoLog"
, "
MyServivce ->
onBind"
);
//获取Service自身Messenger所对应的IBinder,
并将其发送共享给所有客户端
return serviceMessenger.getBinder();
}
4) 注册服务, 让其运行在单独进程中
<
service
android:name=
"
.MyService"
android:enabled=
"
true"
android:process=
"
:remote"
>
<
/service>
(2) 客户端进程
1) 绑定服务端的Service
private ServiceConnection conn =
new ServiceConnection() {
@
Override
public void onServiceConnected(ComponentName name, IBinder binder) {
//客户端与Service建立连接
Log.i("
DemoLog"
, "
客户端 onServiceConnected"
);
//我们可以通过从Service的onBind方法中返回的IBinder初始化一个指向Service端的Messenger
serviceMessenger =
new Messenger(binder);
isBound =
true;
Message msg =
Message.obtain();
msg.what =
SEND_MESSAGE_CODE;
//此处跨进程Message通信不能将msg.obj设置为non-Parcelable的对象,
应该使用Bundle
//msg.obj =
"
你好,
MyService,
我是客户端"
;
Bundle data =
new Bundle();
data.putString("
msg"
, "
你好,
MyService,
我是客户端"
);
msg.setData(data);
//需要将Message的replyTo设置为客户端的clientMessenger,
//以便Service可以通过它向客户端发送消息
msg.replyTo =
clientMessenger;
try {
Log.i("
DemoLog"
, "
客户端向service发送信息"
);
serviceMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
Log.i("
DemoLog"
, "
客户端向service发送消息失败: "
+
e.getMessage());
}
}@
Override
public void onServiceDisconnected(ComponentName name) {
//客户端与Service失去连接
serviceMessenger =
null;
isBound =
false;
Log.i("
DemoLog"
, "
客户端 onServiceDisconnected"
);
}
};
Intent intent =
new Intent();
ComponentName componentName =
new ComponentName(packageName, serviceNmae);
intent.setComponent(componentName);
try{
Log.i("
DemoLog"
, "
客户端调用bindService方法"
);
bindService(intent, conn, BIND_AUTO_CREATE);
}catch(Exception e){
e.printStackTrace();
Log.e("
DemoLog"
, e.getMessage());
}
}
绑定成功后用服务端返回的IBinder对象创建一个Messenger, 通过它向服务端发送Message消息。
如果需要服务端给客户端发送消息, 需要在Handler的handleMessage方法里, 根据客户端发送过来的Message.replyTo获取到客户端的Messenger对象, 就可以向客户端发送消息了。同时客户端需要在服务连接的onServiceConnected方法中, 将客户端的Messenger对象通过Message.replyTo给收到服务端发送过来的Message对象, 这样就实现双方通信。
6.4 AIDL 6.4.1 在服务端新建需要的AIDL类
【Android开发艺术探索读书笔记——进程间通信】(1)先新建一个Book.java实现Parcelable接口, 表示一个图书的信息类;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Book.java
*/
public class Book implements Parcelable{
private String name;
private int price;
public Book(){}
public String getName() {
return name;
}public void setName(String name) {
this.name =
name;
}public int getPrice() {
return price;
}public void setPrice(int price) {
this.price =
price;
}public Book(Parcel in) {
name =
in.readString();
price =
in.readInt();
}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(name);
dest.writeInt(price);
}/**
* 参数是一个Parcel,用它来存储与传输数据
* @
param dest
*/
public void readFromParcel(Parcel dest) {
//注意,
此处的读值顺序应当是和writeToParcel()方法中一致的
name =
dest.readString();
price =
dest.readInt();
}//方便打印数据
@
Override
public String toString() {
return "
name : "
+
name +
"
, price : "
+
price;
}
}
( 2) 新建一个Book.aidl, 表示Book类在AIDL中的声明;
// Book.aidl
//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
//注意:
Book.aidl与Book.java的包名应当是一样的
//注意parcelable是小写
parcelable Book;
( 3) 新建一个IBookManger.aidl接口, 里面包含相应的方法;
// IBookManger.aidl
//导入所需要使用的非默认支持数据类型的包
import com.lypeer.ipcclient.Book;
interface IBookManger {//所有的返回值前都不需要加任何东西,
不管是什么数据类型
List<
Book>
getBooks();
Book getBook();
}
该方法保存后, 会在gen目录下生成一个IBookManger.java类, 它是系统为BookManger.aidl生成的Binder类, 继承android.os.IInterface, 它自己也还是个接口。
它包括以下内容:
1) 定义了一个String型的DESCRIPTOR, Binder的唯一标识;
2) asInterface(android.os.IBinder obj)方法, 将服务端的Binder对象转换成客户端所需的AIDL接口类型对象, 当客户端和服务端处于同一进程, 直接返回服务端的Stub对象本身, 否则返回系统封装后的Stub.proxy对象。
3) asBinder方法: 返回当前Binder对象。
4) onTransact方法: 运行在服务端的Binder线程池中, 当客户端发起远程请求, 远程请求会通过系统封装后交由此方法处理, 该方法原型为public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端通过code可以确定客户端请求的是哪一个方法, 从data中取出目标方法所需的参数, 执行相应方法后, 将返回值写入reply中, 如果此方法返回false,客户端会请求失败。
5) IBookManger.aidl接口中声明的方法的代理实现, 此方法运行在客户端, 当客户端调用此方法时, 首先需要创建此方法需要的输入类型Parcel对象_data, 输出类型Parcel对象_reply和返回值对象, 接着将参数信息写入_data中( 若有参数的话) , 再调用transact方法发起RPC( 远程过程调用) , 当前线程挂起, 服务端的onTransact方法会被调用, 直到RPC过程返回后, 当前线程继续执行, 并从_reply中取出RPC过程的返回结果; 最后返回_reply中的数据。
6) 声明了IBookManger.aidl中声明的方法;
7) 声明了相应的整型id分别用于标识声明的方法, 该id用于标识在transact过程中判断客户端请求的到底是哪个方法;
8) 声明了一个类部类Stub, 继承android.os.Binder实现IBookManager.aidl中类接口, 当客户端和服务端处于同一进程, 方法调用不会走跨进程的transact过程, 若位于不同进程, 则由Stub的内部代理Proxy, 调用跨进程transact过程。
需要注意的是: 客户端发起请求时, 当前线程会挂起直至服务端返回数据, 若方法是一个耗时操作, 不能在UI线程中发起此远程请求。服务端的Binder方法运行在Binder线程池中, 所以不管操作是否耗时, 都应采用同步的方法去实现。
(4)linkToDeath unlinkToDeath
由于Binder运行在服务端进程中, 如果服务端进程由于某种原因终止, 这时客户端服务端的Binder连接断裂, 会导致远程调用失败。但客户端很可能不知道, 解决此问题的办法, 利用Binder提供的两个配对方法linkToDeath和unlinkToDeath, 通过linkToDeath可以给Binder设置一个死亡代理, 当Binder被终止时, 我们会接收到通知, 这时可通过重新发起请求恢复连接。
实现方法:
1) 先声明一个DeathRecipient对象, 它是一个接口, 内部只有一个方法binderDied,我们需要实现该方法, 当Binder死亡时, 系统回调binderDied方法, 我们可以移出之前binder代理并重新建立远程服务。
private IBinder.DeathRecipient mDeathRecipient =
new IBinder.DeathRecipient() {
@
Override
public void binderDied() {
if (mBookManager =
=
null) {
return;
}
mBookManger.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManger =
null;
//重新绑定远程服务
}
};
2) 在客户端绑定远程服务成功后, 给binder设置死亡代理;
mService =
IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0);
6.4.2 远程服务端Serivce的实现
(1)Serivce建立
我们需要新建一个Service, 称为AIDLService, 代码如下:
/**
1. 服务端的AIDLService.java
*/
public class AIDLService extends Service {public final String TAG =
this.getClass().getSimpleName();
//包含Book对象的list
private List<
Book>
mBooks =
new ArrayList<
>
();
//由AIDL文件生成的IBookManager
private final IBookManager.Stub mBookManager =
new IBookManager.Stub() {
@
Override
public List<
Book>
getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "
invoking getBooks() method , now the list is : "
+
mBooks.toString());
if (mBooks !=
null) {
return mBooks;
}
return new ArrayList<
>
();
}
}@
Override
public void addBook(Book book) throws RemoteException {
synchronized (this) {
if (mBooks =
=
null) {
mBooks =
new ArrayList<
>
();
}
if (book =
=
null) {
Log.e(TAG, "
Book is null in In"
);
book =
new Book();
}
//尝试修改book的参数,
主要是为了观察其到客户端的反馈
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,
观察客户端传过来的值
Log.e(TAG, "
invoking addBooks() method , now the list is : "
+
mBooks.toString());
}
}
};
@
Override
public void onCreate() {
super.onCreate();
Book book =
new Book();
book.setName("
Android开发艺术探索"
);
book.setPrice(28);
mBooks.add(book);
}@
Nullable
@
Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("
on bind,intent =
%s"
, intent.toString()));
return mBookManager;
//在onBinder中返回服务端的Binder对象
}
}
(2)注册服务
<
service android:name=
"
aidl.AIDLService"
android:process=
"
:remote"
>
<
/service>
6.4.3 客户端的实现
/**
2. 客户端的AIDLActivity.java
*/
public class AIDLActivity extends AppCompatActivity {//由AIDL文件生成的Java类
private IBookManager mBookManager =
null;
//标志当前与服务端连接状况的布尔值,
false为未连接,
true为连接中
private boolean mBound =
false;
//包含Book对象的list
private List<
Book>
mBooks;
@
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}/**
* 按钮的点击事件,
点击之后调用服务端的addBookIn方法
*
* @
param view
*/
public void addBook(View view) {
//如果与服务端的连接处于未连接状态,
则尝试连接
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "
当前与服务端处于未连接状态,
正在尝试重连,
请稍后再试"
, Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager =
=
null) return;
Book book =
new Book();
book.setName("
APP研发录In"
);
book.setPrice(30);
try {
mBookManager.addBook(book);
//通过服务端的Binder对象调服务端方法
Log.e(getLocalClassName(), book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}/**
* 尝试与服务端建立连接
*/
private void attemptToBindService() {
Intent intent =
new Intent();
intent.setAction("
aidl.AIDLService"
);
intent.setPackage("
com.xx.packagename"
);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
//绑定服务
}@
Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}@
Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound =
false;
}
}private ServiceConnection mServiceConnection =
new ServiceConnection() {
@
Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "
service connected"
);
mBookManager =
IBookManager.Stub.asInterface(service);
mBound =
true;
if (mBookManager !=
null) {
try {
mBooks =
mBookManager.getBooks();
//调用服务端的getBooks方法
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}@
Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "
service disconnected"
);
mBound =
false;
}
};
}
通过获取服务端的Binder对象, 就可以和服务端进行通信了。
6.4.4 其他知识点
(1)跨进程listener接口
如果在服务端定声明了接口, 客户端进行注册和反注册, 会抛出异常, 无法反注册, 因为在多进程中, Binder会把客户端传过来的对象重新转化成一个新对象, 这样虽然客户端注册和反注册使用的是同一个对象, 但是通过Binder传递到服务端却变成两个对象, 对象的跨进程传输本质都是反序列化过程。
可以使用RemoteCallbackList, 它是系统专门用来删除跨进程listener接口。它实现原理利用底层Binder对象是同一个, 只要遍历服务端所有listener,找出和解注册listener具有相同Binder对象的服务端listener, 并把它删除掉。同时RemoteCallbackList还有一个作用, 当客户端进程终止, 它会移除客户端所注册的所有listener.
(2)权限验证
在注册服务时添加permission权限验证, 或是在onTransact方法中进行权限验证;
(3) AIDL访问流程总结:
先创建一个Service和一个AIDL接口, 再创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法; 在Service的onBind方法中返回这个对象, 再在客户端就可以绑定服务端service, 建立连接后就可以访问远程服务端的方法了。
6.5 ContentProvider 和Messenger一样, ContentProvider底层同样是Binder.系统预置了很多ContentProvider, 如通讯录、日程表等, 只需通过ContentResolver的query/update/insert/delete就可以跨进程访问这些信息。具体实现步骤如下:
1) 新建一个继承自系统ContentProvider的Provider, 并重写onCreate, query, getType, insert, delete, update六个方法。
这六个进程运行在ContentProvider的进程中, 除了onCreate由系统回调运行在主线程中, 其他五个方法运行在Binder线程池中。
2) 注册Provider
<
provider android:name=
"
.provider.XXProvider"
android:authorities=
"
com.xx.xx.provider"
android:permission=
"
com.xx.PROVIDER"
android:process=
"
:provider"
>
<
/provider>
3) 在另一个进程中访问我们定义的ContentProvider
Uri uri =
Uri.parse("
content://com.xx.xx.provider"
);
getContentResolver().query(uri, null, null, null, null);
getContentResolver().query(uri,)
4) ContentProvider数据源发生变化时, 可通过ContentResolver的notifyChange方法来通知外界数据发生改变, 外界可通过ContentResolver的registerContentObserver方法来注册观察者, 通过unregisterContentObserver方法来解除观察者。
6.6 Socket Socket分为流式套接字和用户数据报套接字, 分别是对应于网络的传输控制层中的TCP/UDP
TCP: 面向连接的协议, 稳定的双向通信功能, 连接需要“三次握手”, 提供了超时重传机制, 具有很高的稳定性;
UDP: 面向无连接协议, 不稳定的单向通信功能, 也可提供双向通信功能。效率高, 不能保证数据一定能正确传输。
实现步骤:
1)服务端在新建一个Service, 并建立TCP服务
@
Override
public void onCreate() {
new Thread(new TcpServer()).start();
//阻塞监听客户端连接
super.onCreate();
}@
Override
public IBinder onBind(Intent intent) {
throw null;
}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();
//通过数据流的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();
}
2)注册服务
<
service
android:name=
"
.SocketServerService"
android:process=
"
:remote"
/>
3) 客户端先建立连接服务端socket
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);
}
}
4) 客户端和服务端通信, 通过while循环不断去读取服务器发送过来的消息
// 接收服务器端的消息
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);
}
}
);
}
}
5) 客户端在onCreate中单开线程启动连接服务
Intent service =
new Intent(this, SocketServerService.class);
startService(service);
new Thread() {
@
Override
public void run() {
connectSocketServer();
}
}.start();
6.7 进程间通信方式比较
文章图片
推荐阅读
- LeetCode042 Trapping Rain Water
- Android Study 之 初识ButterKnife(8.5.1)及简单运用
- Android4.4之后休眠状态下Alarm不准时的问题
- Android UI设计与开发使用ViewPager实现欢迎引导页面
- PowerPoint2007幻灯片母版查看_PowerPoint专区
- PowerPoint幻灯片母版版式背景图片设置_PowerPoint专区
- WPS文字中的标尺要怎样设置_WPS office
- PowerPoint主题颜色与背景样式的设置_PowerPoint专区
- PowerPoint2007其他版面元素的运用_PowerPoint专区