三枪干掉 IPC 机制之二

1,477 阅读14分钟

前言

IPC真是一块很难啃的骨头,在这一个星期,我边考试边看,但是始终进度走不了,今天花了一天时间,又看了一遍,所有例子都敲了一遍总算有所收货.

Bundle

Bundle我认为不能称作是IPC的一种方式,把它定义为一种在进程间传递传输数据的一个容器更合适,

  • 它将你需要传输的数据包装成一个包裹传送.它支持在Activity,Service,Receiver之间在Intent中传输数据.
  • 要注意的是,要传输的数据必须可以序列化.
  • 如果是自定义的序列化对象,那么要实现Parcelable或者Serializable接口.

如果数据不支持放入Bundle

如果还是要坚持采用IPC的方式会比较麻烦,可以从A启动B进程的一个后台Service,这样Service执行的结果就可以直接传给B,不必再经历IPC的过程.

文件共享

很容易理解,就是A将需要传输的数据写入文件,B再从文件里读取数据.
Linux里面文件的操作是可以并发读写的,我认为并发写可能会比较容易出问题,所以这种方式其实可用性不高.

因此我们要尽量避免并发写这种情况的发生或者考虑使用线程同步来闲置多个线程的写操作.

SharedPreferences的实现就是一种文件共享,它将数据格式化为xml文件存储在/data/data/packge name/shared_prefs下面(有root权限可以读取查看).

因此SharedPreferences并发读写也是不可靠的,再加上Android系统对SharedPreferences有一定的缓存策略,因此在多进程模式下并发读写就更不可靠了,面对高并发读写会有很大几率丢失数据.

Messenger

终于讲到了重点,MessengerHandlerAIDL进行封装,实现了简单的数据传送,一次只能处理一个请求,所以不用考虑进程同步的问题.

服务端进程

第一篇里面画了一张图,在其基础上修改一下就可以变成Messenger的工作机制图解:

Binder工作机制

服务端需要一个Service来处理客户端的请求,Messenger的特殊之处就是同时创建一个Handler来处理客户端发过来的消息,通过这个Handler来创建一个Messenger对象并且在Service的onBind()中返回这个Messenger的底层Binder.

public class MessagerService extends Service {
    private static final String TAG = MessagerService.class.getSimpleName();
    public MessagerService() {
     * 处理客户端发送过来的Message
    public static class MessagerHandler extends Handler{
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0x01:
                    Messenger mClient = msg.replyTo;
                    Message replyMessage = Message.obtain(null,0x02);
                    Bundle bundle = new Bundle();
                    bundle.putString("msg","this is a message from service");
                    replyMessage.setData(bundle);
                    try {
                        mClient.send(replyMessage);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    Log.d(TAG,"receive msg from client:"+msg.getData().getString("msg"));
                    break;
                default:
                    super.handleMessage(msg);
     * @param target The Handler that will receive sent messages.
     * 这里MessagerHnadler是接受消息的handler
     * 创建一个与MessagerHnadler关联的messager,
     * 将其binder返回给客户端,客户端可以通过这个Binder传送消息给服务端
    private final Messenger messenger = new Messenger(new MessagerHandler());
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();

客户端进程

绑定Service->返回Binder对象->创建Messenger,通过这个对象就可以给服务端发送消息.
如果需要服务端能够回应

  1. 创建Message对象
  2. 创建一个新的Handler
  3. 通过Handler创建Messenger
  4. 将这个Messenger通过MessagereplyTo参数传给服务端
  5. 服务端通过replyTo回应客户端
private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName name, IBinder service) {
        Messenger mService = new Messenger(service);
        Message message = Message.obtain(null,0x01);
        Bundle data = new Bundle();
        data.putString("msg","this is a msg from client");
        message.setData(data);
        message.replyTo = mGetReplyMessager;
        try {
            mService.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
    public void onServiceDisconnected(ComponentName name) {

onCreate()

Intent intent = new Intent(MainActivity.this,MyService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);

就可以绑定远程service

其中,接收远程Service返回数据的mGetReplyMessager是这么实现的

public static class MessagerHnadler extends Handler{
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0x02:
                    System.out.println(msg.getData().getString("msg"));
                    break;
                default:
                    super.handleMessage(msg);
                    break;

private Messenger mGetReplyMessager = new Messenger(new MessagerHnadler());

再将mGetReplyMessager作为replyTo返回就可以在MessagerHnadler处理服务端返回的数据了.

工作原理图

Messenger工作原理图

AIDL

  • Messenger不适用于大量的并发请求,它是对AIDL的封装,适用范围更窄,有时使用一更些开源库的时候可能回想,为什么不再封装一下呢,那样使用起来简单了,后来我慢慢明白,封装会导致可DIY度降低,封装度越高,可DIY度越低,那么库的适用范围就越窄,那有很多需求就不能满足了
  • 我们需要夸进程调用服务端方法的时候Messenger无法做到。
    AIDL的具体实现还是Binder

    binderService的时候,服务端会返回一个BInder对象,通过这个BInder对象,客户端可以获取服务端提供的服务或者数据。
    甚至调用服务端方法


所以BInder才是夸进程通信的实质,AIDL提供了一种实现BInder的方法,让我们更容易实现易用的BInder

服务端

Messenger一样,服务端也是一个Service,这个Service实现了AIDL接口,这个Stub()是我们申明的.aidl文件生成的,.aidl文件里面我们需要写入暴露给Client的接口,其实就是一些抽象方法,在Service中实现他们,将对象BInder返回到Client,Client可以通过这个Binder来调用远程方法。

AIDL接口创建

AIDL的作用就是生成BInder服务端实现方法,上一篇讲过我们其实可以手写Binder服务端实现方法,但是系统提供了更简便的实现方法,我们何必舍近求远。在.adil文件中我们需要申明所有需要暴露给客户端的接口(所有想给客户端调用的方法的抽象)。要注意:

  • 自定义的Parcelable对象必须显式import进去
  • 如果是自定义Parcelable对象,需建一个同名AIDL文件,其中申明用到的类

    packge XXXX.XXXX;
    parcelable Book;
  • 标记参数方向,输出型参数out,输入型参数in,输入输出型参数inout

  • AIDL接口只支持方法,不支持申明静态常量。
  • 客户端和服务端AIDL的包结构必须一致。

定义接口

package com.bigmercu.ipc;
import com.bigmercu.ipc.Book;
import com.bigmercu.ipc.IOnNewBookArrivedListener;
interface IBookManager {
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
    List getBooklist();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);

这里使用了观观察者模式,声明注册和取消注册观察者,用于及时获取图书更新信息.

观察者也需要定义接口

package com.bigmercu.ipc;
import com.bigmercu.ipc.Book;
interface IOnNewBookArrivedListener {
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     void onNewBook(in Book newBook);

声明自定义Parcelable对象

package com.bigmercu.ipc;
parcelable Book;

最后别忘了Book类要实现Parcelable接口.

完整服务端代码

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class MyService extends Service {
    public static final String TAG = "BMS";
    private AtomicBoolean isServiceDestroyed = new AtomicBoolean(false);
    private CopyOnWriteArrayList bookList = new CopyOnWriteArrayList();
    private RemoteCallbackList listenerList = new RemoteCallbackList<>();
    public MyService() {
    private Binder mBinder = new IBookManager.Stub() {
        public List getBooklist() throws RemoteException {
            return bookList;
        public void addBook(Book book) throws RemoteException {
            onNewBookArrived(book);
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            listenerList.register(listener);
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            listenerList.unregister(listener);
    private void onNewBookArrived(Book book)throws RemoteException{
        bookList.add(book);
        final int N = listenerList.beginBroadcast();
        for(int i = 0;i < N;i++){
            IOnNewBookArrivedListener l = listenerList.getBroadcastItem(i);
            if(l != null){
                l.onNewBook(book);
        listenerList.finishBroadcast();
    private class ServiceWorker implements Runnable{
        public void run() {
            while (!isServiceDestroyed.get()){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                int bookid = bookList.size() + 1;
                Book newBook = new Book(bookid,"hello" + bookid);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
    public void onCreate() {
        super.onCreate();
        bookList.add(new Book(1,"hello"));
        bookList.add(new Book(2,"word"));
        new Thread(new ServiceWorker()).start();
    public void onDestroy() {
        isServiceDestroyed.set(true);
        super.onDestroy();
    public IBinder onBind(Intent intent) {
        return mBinder;

服务端最重要的就是实现stub得到Binder对象,然后将这个对象返回给Clidet
这里面有一个CopyOnWriteArrayList,是一种可以支持并发读写的list,多线程势必要考虑并发读写的问题.

客户端

实现观察者模式

onNewBookArrived这个方法中,我将书添加到图书列表里去,并且将通知所有注册在案的listener端有新书到达,执行他们的onNewBook方法,在Listener端(Activity里),我们可以这样收到有新书到达的消息

private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
        public void onNewBook(Book newBook) throws RemoteException {
            Log.d(TAG,newBook.toString());
            mHnadler.obtainMessage(0x03,newBook).sendToTarget();

这样得到的其实还是一个Binder,将这个Binder作为listener注册到服务端

private ServiceConnection mConnection1 = new ServiceConnection() {
       public void onServiceConnected(ComponentName name, IBinder service) {
           iBookManager = com.bigmercu.ipc.IBookManager.Stub.asInterface(service);
           try {
               iBookManager.registerListener(listener);
           } catch (RemoteException e) {
               e.printStackTrace();
           try {
               List list = iBookManager.getBooklist();
               Log.d(TAG,"list type:" + list.getClass().getCanonicalName());
               Log.d(TAG,"list book:" + list.toString());
               iBookManager.addBook(new Book(3,"Android开发艺术探索"));
               iBookManager.addBook(new Book(4,"编程之美"));
               list = iBookManager.getBooklist();
               Log.d(TAG,"list book:" + list.toString());
           } catch (RemoteException e) {
               e.printStackTrace();
       public void onServiceDisconnected(ComponentName name) {
           iBookManager = null;
           Log.d(TAG,"service died");

回过头去看Service的操作其实就是将这个Listener添加到一个特殊的list,RemoteCallbackList里面,当有书籍添加操作的时候就遍历list

final int N = listenerList.beginBroadcast();
for(int i = 0;i < N;i++){
    IOnNewBookArrivedListener l = listenerList.getBroadcastItem(i);
    if(l != null){
        l.onNewBook(book);
        listenerList.finishBroadcast();

哈,真是特殊的遍历方法,就可以取出AIDL执行里面的方法,也就是onNewBook,其实这个方法就在客户端上,并且是messenger实现的,这时候在客户端用Handler处理消息就可以得到监听返回的消息.

完整客户端代码

public class MainActivity extends AppCompatActivity {
    private com.bigmercu.ipc.IBookManager iBookManager;
    private static final String TAG = MainActivity.class.getSimpleName();
    private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
        public void onNewBook(Book newBook) throws RemoteException {
            Log.d(TAG,newBook.toString());
            mHnadler.obtainMessage(0x03,newBook).sendToTarget();
    private Handler mHnadler = new Handler(){
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 0x03:
                    Log.d(TAG,"A new book arrived:"+ msg.obj.toString());
                    break;
                default:
                    super.handleMessage(msg);
    private ServiceConnection mConnection1 = new ServiceConnection() {
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = com.bigmercu.ipc.IBookManager.Stub.asInterface(service);
            try {
                iBookManager.registerListener(listener);
            } catch (RemoteException e) {
                e.printStackTrace();
            try {
                List list = iBookManager.getBooklist();
                Log.d(TAG,"list type:" + list.getClass().getCanonicalName());
                Log.d(TAG,"list book:" + list.toString());
                iBookManager.addBook(new Book(3,"Android开发艺术探索"));
                iBookManager.addBook(new Book(4,"编程之美"));
                list = iBookManager.getBooklist();
                Log.d(TAG,"list book:" + list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
        public void onServiceDisconnected(ComponentName name) {
            iBookManager = null;
            Log.d(TAG,"service died");
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("heo","hello");
        Intent intent = new Intent(MainActivity.this,MyService.class);
        bindService(intent,mConnection1, Context.BIND_AUTO_CREATE);
    protected void onDestroy() {
        if(iBookManager != null && iBookManager.asBinder().isBinderAlive()){
            try {
                iBookManager.unRegisterListener(listener);
            } catch (RemoteException e) {
                e.printStackTrace();
        unbindService(mConnection1);
        super.onDestroy();

再整体来看就很简单了.

权限设置

设置权限


  android:name="com.bigmercu.ipc.permission.ACCESS"
  android:protectionLevel="normal"/>

在OnBinder中

   public IBinder onBind(Intent intent) {
       int check = checkCallingOrSelfPermission("com.bigmercu.ipc.permission.ACCESS");
       Log.d(TAG, String.valueOf(check));
       if(check == PackageManager.PERMISSION_DENIED){
           return null;
       return mBinder;

要注意几点

  • RemoteCallbackList是系统提供专门用于删除跨进程listener的接口
  • 不要轻易在服务端开进程去执行异步任务,因为服务端本身就运行在Binder线程池中,可以执行耗时任务
  • 由于客户端的onServiceConnected,onServiceDisconnected都运行在UI线程中,所以不能执行耗时任务(可以交由Handler执行)
  • listener其实服务端客户端身份互换了,服务端调用客户端方法也不可执行耗时操作(自己领会)
  • 两种Binder死亡重启方法
    • 给Binder设置DeathRecipient监听,当Binder死亡的时候我们会收到binderDied回调.
    • 在onServiceDisconnected中重新链接远程服务

ContentProvider

ContentProvider是Android提供的专门用于不同应用之间进行数据共享的方式

原理分析

前面说到过,不同应用共享数据其实就相当于不同进程,拥有同样的sharUID,所以ContentProvider完全适用于进程间通讯.ContentProvider的底层实现还是Binder,对你没看错,Android靠谱一点的IPC底层实现都是Binder.

数据存储

  • 以表的方式组织数据,有行列,每一行是一条记录,每一列是一个数据段.可以包括多个表(怎么看都是一个数据库)
  • 支持图片,文件,视频等大多数数据
  • 对底层数据存储没有特别要求,可以用文件,也可以用SQLite等方式

实现步骤

  • 声明权限和ContentProvider标识
  • 写底层实现Util类(数据库,文件 etc.)
  • 继承ContentProvider接口,实现其抽象方法,包括query,insert,update,delete,onCreate,getType六个方法.

声明标识

android:authorities="com.bigmercu.ipc_contentprovider.MyContentProvider"唯一的标识了一个ContentProvider.
android:process=":provider" 让他运行在新线程中

DatabasesHelper

ContentProvider提供了一个数据存储的接口,具体底层怎么实现我们需要自己实现.

 * Created by bigmercu on 16/6/30.
 * Email:bigmercu@gmail.com
 public class DBHelper 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 PTIMARY KEY,"+ "name TEXT)";
    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PTIMARY KEY,"+ "name TEXT,"+"sex INT)";
    public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    public DBHelper(Context mContext) {
        super(mContext,DB_NAME,null,DB_VERSION);
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

这是一个标准的DBHelper,没啥好说的.

进入provider编码

ContentProvider通过Uri辨别外界要访问的表,在ContentProvider内部是通过Uri_Code来辨别的(一个int),所以我们要把Uri和Uri_Code关联起来,使用uriMarcher来关联他俩,这样我们在访问ContentProvider就可以通过Uri辨识需要访问的表了.

  • onCreate获取数据库访问对象
  • query,delete,update,insert就分别对应数据库中的插,删,改,增了
  • getType:Implement this to handle requests for the MIME type of the data at the given URI.

源码都很简单,一读就懂,其中

int row = mDB.update(table,values,selection,selectionArgs);
if(row >0){
    getContext().getContentResolver().notifyChange(uri,null);

@return the number of rows affected`update返回值是受影响的行数.

getContext().getContentResolver().notifyChange(uri,null);可以通知订阅变化的Observer

使用 SQLiteDatabase 的query,update,insert,delete 因为存在多线程并发访问,所以要做好线程同步,单个DB对象不需要考虑线程同步

/content:
public class MyContentProvider extends ContentProvider {
    public MyContentProvider() {
    /**/
    /**/    private static final String AUTUORITY = "com.bigmercu.ipc_contentprovider.MyContentProvider";
    /**/    private static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTUORITY + "/book");
    /**/
    /**/    private static final int USER_URI_CODE = 1;
    /**/    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    /**/    static {
    /**/       uriMatcher.addURI(AUTUORITY,"user",USER_URI_CODE);
    /**/
    private Context mContext;
    private SQLiteDatabase mDB;
    public boolean onCreate() {
        Log.d(TAG,"onCreate  thread:" + Thread.currentThread().getName());
        mContext = getContext();
        initProViderData();
        return true;
    private void initProViderData() {
        mDB = new DBHelper(mContext).getWritableDatabase();
        mDB.execSQL("delete from " + DBHelper.BOOK_TABLE_NAME);
        mDB.execSQL("delete from " + DBHelper.USER_TABLE_NAME);
        mDB.execSQL("insert into book values(1,'swift');");
        mDB.execSQL("insert into book values(2,'c plus plus');");
        mDB.execSQL("insert into user values(1,'echo',0);");
        mDB.execSQL("insert into user values(2,'bigmercu',1);");
    private String getTableName(Uri uri){
        String tableName = null;
        switch (uriMatcher.match(uri)){
            case BOOK_URI_CODE:
                tableName = DBHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DBHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        return tableName;
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Log.d(TAG,"query  thread:" + Thread.currentThread().getName());
        String table = getTableName(uri);
        if(table == null){
            throw new IllegalArgumentException("Unsupported URI" + uri);
        return mDB.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.d(TAG,"insert");
        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;
    public String getType(Uri uri) {
        return null;
    public Uri insert(Uri uri, ContentValues values) {
        Log.d(TAG,"insert");
        String table = getTableName(uri);
        if(table == null){
            throw new IllegalArgumentException("Unsupported URI" + uri);
        mDB.insert(table,null,values);
        return uri;
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        Log.d(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;

在Activty中使用

很简单,将数据封装成ContentValues以后通过getContentResolver().insert(bookUri,values);即可插入数据
遍历数据需要使用Cursor,关于query内部的参数看下:

*  uri The URI, using the content:
    *         retrieve.
    *  projection A list of which columns to return. Passing null will
    *         return all columns, which is inefficient.
    *  selection A filter declaring which rows to return, formatted as an
    *         SQL WHERE clause (excluding the WHERE itself). Passing null will
    *         return all rows for the given URI.
    * @param selectionArgs You may include ?s in selection, which will be
    *         replaced by the values from selectionArgs, in the order that they
    *         appear in the selection. The values will be bound as Strings.
    * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
    *         clause (excluding the ORDER BY itself). Passing null will use the
    *         default sort order, which may be unordered.
    * @return A Cursor object, which is positioned before the first entry, or null


public class MainActivity extends AppCompatActivity {
    private static  final String TAG = MainActivity.class.getSimpleName();
    private EditText bookNum,bookName;
    private TextView textArea;
    private Button submit;
    private FloatingActionButton downData;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        submit.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                String name = bookNum.getText().toString();
                String id = bookName.getText().toString();
                Log.d(TAG,"name is:" + name + "\n" + "id id:" + id);
                if(bookNum.getText() != null && bookName.getText() != null){
                    Uri bookUri = Uri.parse("content://com.bigmercu.ipc_contentprovider.MyContentProvider/book");
                    ContentValues values = new ContentValues();
                    values.put("_id",id);
                    values.put("name",name);
                    getContentResolver().insert(bookUri,values);
        });
        downData.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Uri bookUri = Uri.parse("content://com.bigmercu.ipc_contentprovider.MyContentProvider/book");
                Cursor bookCursor = getContentResolver().query(bookUri,new String[]{"_id","name"},null,
                        null,null);
                while (bookCursor.moveToNext()){
                    Book book = new Book();
                    book.bookId = bookCursor.getInt(0);
                    book.bookName = bookCursor.getString(1);
                    textArea.append(book.toString());
                    textArea.append("\n");
                bookCursor.close();
        });
    private void initView(){
        bookName = (EditText) findViewById(R.id.bookNum);
        bookNum = (EditText) findViewById(R.id.bookName);
        textArea = (TextView) findViewById(R.id.showArea);
        submit = (Button) findViewById(R.id.submit);
        downData = (FloatingActionButton) findViewById(R.id.floatingActionsubmit3);

效果图

效果

BTW,今天试用了一下最新的ConstraintLayout,Cool!

layout

Socket

Service

 * Created by Bigmercu on 16/7/18.
 * Email:bigmercu@gmail.com
 public class TCPServerServices extends Service {
    private static final String TAG = TCPServerServices.class.getSimpleName();
    private boolean isServiceDestroyed = false;
    private String[] mDefinedMessages = new String[]{
        "力拔山兮气盖世。" ,
            "虞兮虞兮奈若何!",
            "时不利兮骓不逝。",
            "骓不逝兮可奈何!",
    public void onCreate() {
        System.out.println("onCreated");
        new Thread(new TCPServer()).start();
        super.onCreate();
    private class TCPServer implements Runnable{
        ("resource")
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(8688);
            } catch (IOException e) {
                Log.e(TAG,"establish tab server filed");
                e.printStackTrace();
            while (!isServiceDestroyed){
                try {
                    final Socket client = serverSocket.accept();
                    new Thread(){
                        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 br = new BufferedReader(new InputStreamReader(client.getInputStream()));
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
        pw.println("欢迎进入聊天室!");
        while (!isServiceDestroyed){
            String str = br.readLine();
            System.out.println("Msg from c:" + str);
            if(str == null){
                break;
            int i = new Random().nextInt(mDefinedMessages.length);
            String msg = mDefinedMessages[i];
            pw.println(msg);
            System.out.println("Server send:" + msg);
        br.close();
        pw.close();
        client.close();
        System.out.println("client quit");
    public void onDestroy() {
        isServiceDestroyed = true;
        super.onDestroy();
    public IBinder onBind(Intent intent) {
        return null;

Client

public class MainActivity extends AppCompatActivity {
    private Toolbar toolbar;
    private EditText editText;
    private TextView textView;
    private FloatingActionButton fab;
    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    private static final int MESSAGE_SOCKET_CONNECTED = 2;
    private Socket socket;
    private PrintWriter pw;
    ("HandlerLeak")
    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_RECEIVE_NEW_MSG:
                    textView.setText(textView.getText() + (String) msg.obj);
                    break;
                case MESSAGE_SOCKET_CONNECTED:
                    fab.setEnabled(true);
                    fab.setBackgroundColor(Color.BLACK);
                    break;
                default:
                    break;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        initView();
        Intent intent = new Intent(this, TCPServerServices.class);
        startService(intent);
        new Thread(){
            public void run() {
                getConnection();
        }.start();
    ("SimpleDateFormat")
    private String formatDateTaime(long time){
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
    private void getConnection() {
        Socket clientSocket = null;
        while (clientSocket == null) {
            try {
                clientSocket = new Socket("localhost", 8688);
                socket = clientSocket;
                pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                System.out.println("server connect success");
            } catch (IOException e) {
                SystemClock.sleep(1000);
                System.out.println("server connect filed,retrying");
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            while (!MainActivity.this.isFinishing()){
                String msg = br.readLine();
                System.out.println("receive:" + msg);
                if(msg != null){
                    String time = formatDateTaime(System.currentTimeMillis());
                    String msgGet = "SERVER" + time +":"+ msg + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,msgGet).sendToTarget();
            System.out.println("quit...");
            br.close();
            pw.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
    private void initView() {
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        editText = (EditText) findViewById(R.id.editText);
        textView = (TextView) findViewById(R.id.textView);
        fab = (FloatingActionButton) findViewById(R.id.fab_send);
        fab.setEnabled(false);
        fab.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                String msg = editText.getText().toString();
                if(!TextUtils.isEmpty(msg) && pw  != null){
                    pw.println(msg);
                    editText.setText("");
                    String time = formatDateTaime(System.currentTimeMillis());
                    String msgPut = "CLIENT" + time +":"+ msg + "\n";
                    textView.setText(textView.getText() + msgPut);
                    Snackbar.make(view, "Message sended", Snackbar.LENGTH_LONG)
                            .setAction("OK", null).show();
                }else {
                    Snackbar.make(view, "Message Filed", Snackbar.LENGTH_LONG)
                            .setAction("OK", null).show();
        });
    protected void onDestroy() {
        if(socket != null){
            try {
                socket.shutdownInput();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
        super.onDestroy();

socket

思维导图

Android进程间通信