Android开发艺术探索读书笔记——进程间通信

世事洞明皆学问,人情练达即文章。这篇文章主要讲述Android开发艺术探索读书笔记——进程间通信相关的知识,希望能为你提供帮助。
1. 多进程使用场景 1) 应用某些模块因为特殊需求需要运行在单独进程中。如消息推送, 使消息推送进程与应用进程能单独存活, 消息推送进程不会因为应用程序进程crash而受影响。
2) 为加大一个应用可使用的内存, 需要多进程来获取多份内存空间。
2. 如何开启多进程 给四大组件( Activity、Service、Receiver、ContentProvider) 在androidMainfest中指定android:process属性指定。

  • 如果进程以”:”开头的进程, 代表应用的私有进程, 其他应用的组件不可以和它跑在同一个进程中;
  • 进程不以”:”开头的进程属于全局进程, 其他应用可通过shareUID可以和它跑在同一个进程中。
  • 若两个不同应用设置了相同的shareUID,它们之间想共享数据, 还需要有相同的签名才可以。
3. 多进程会造成的问题 (1) 静态成员和单例失效, 数据同步失败;
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.android中跨进程通信方式 6.1 结合Bundle使用Intent 在一个进程中启动另一个进程的Activity, Service, Receiver组件时, 可以使用Bundle附加上需要传递的消息给远程进程, 并通过Intent发送出去。
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 进程间通信方式比较
Android开发艺术探索读书笔记——进程间通信

文章图片


    推荐阅读