Android进程间通信方式

目录

一、使用 Intent

二、使用文件共享

三、使用 Messenger

四、使用 AIDL

五、使用 ContentProvider

六、使用 Socket



一、使用 Intent

Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle 实现了 Parcelable 接口,可以在不同的进程间进行传输。

在一个进程中启动了另一个进程的 Activity,Service 和 Receiver ,可以在 Bundle 中附加要传递的数据通过 Intent 发送出去。


二、使用文件共享

Windows 上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而 Android 系统基于 Linux ,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,尽管这样可能会出问题。

可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同。)。

SharedPreferences 是个特例,系统对它的读 / 写有一定的缓存策略,即内存中会有一份 ShardPreferences 文件的缓存,系统对他的读 / 写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很多大的几率丢失数据。因此,IPC 不建议采用 SharedPreferences。


三、使用 Messenger

Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL ,可以在不同进程中传递 Message 对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。

服务端进程:服务端创建一个 Service 来处理客户端请求,同时通过一个 Handler 对象来实例化一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。

publicclassMessengerServiceextendsService {privatestaticfinalStringTAG=MessengerService.class.getSimpleName();privateclassMessengerHandlerextendsHandler {/***@param msg*/@OverridepublicvoidhandleMessage(Messagemsg) {switch (msg.what) {caseConstants.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,Constants.MSG_FROM_SERVICE);Bundle bundle=newBundle(); bundle.putString(Constants.MSG_KEY,"我已经收到你的消息,稍后回复你!"); replyMsg.setData(bundle);try { client.send(replyMsg); }catch (RemoteException e) { e.printStackTrace(); }break;default:super.handleMessage(msg); } } }privateMessenger mMessenger=newMessenger(newMessengerHandler());@Nullable@OverridepublicIBinderonBind(Intentintent) {return mMessenger.getBinder();

    }

}

客户端进程:首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo 参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。

publicclassMainActivityextendsAppCompatActivity {privatestaticfinalStringTAG=MainActivity.class.getSimpleName();privateMessenger mGetReplyMessenger=newMessenger(newMessageHandler());privateMessenger mService;privateclassMessageHandlerextendsHandler {@OverridepublicvoidhandleMessage(Messagemsg) {switch (msg.what) {caseConstants.MSG_FROM_SERVICE:Log.d(TAG,"received msg form service: msg = ["+ msg.getData().getString(Constants.MSG_KEY)+"]");Toast.makeText(MainActivity.this,"received msg form service: msg = ["+ msg.getData().getString(Constants.MSG_KEY)+"]",Toast.LENGTH_SHORT).show();break;default:super.handleMessage(msg);            }        }    }@OverrideprotectedvoidonCreate(BundlesavedInstanceState) {super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }publicvoidbindService(Viewv) {Intent mIntent=newIntent(this,MessengerService.class);        bindService(mIntent, mServiceConnection,Context.BIND_AUTO_CREATE);    }publicvoidsendMessage(Viewv) {Message msg=Message.obtain(null,Constants.MSG_FROM_CLIENT);Bundle data=newBundle();        data.putString(Constants.MSG_KEY,"Hello! This is client.");        msg.setData(data);        msg.replyTo= mGetReplyMessenger;try {            mService.send(msg);        }catch (RemoteException e) {            e.printStackTrace();        }    }@OverrideprotectedvoidonDestroy() {        unbindService(mServiceConnection);super.onDestroy();    }privateServiceConnection mServiceConnection=newServiceConnection() {/***@param name*@param service*/@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice) {            mService=newMessenger(service);Message msg=Message.obtain(null,Constants.MSG_FROM_CLIENT);Bundle data=newBundle();            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*/@OverridepublicvoidonServiceDisconnected(ComponentNamename) {

        }

    };

}

**注意:**客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。只不过客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。Messenger 中有一个 Hanlder 以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。


四、使用 AIDL

Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用 Messenger ,而且 Messenger 只适合传递消息,不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题,要知道 Messenger 本质上也是 AIDL ,只不过系统做了封装方便上层的调用而已。

AIDL 文件支持的数据类型

基本数据类型

StringCharSequence

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 线程中。

服务端访问权限管理

使用 Permission 验证,在 manifest 中声明

服务端 onBinder 方法中

publicIBinder onBind(Intent intent) {//Permission 权限验证int check= checkCallingOrSelfPermission("com.jc.ipc.ACCESS_BOOK_SERVICE");if (check==PackageManager.PERMISSION_DENIED) {returnnull;    }return mBinder;

}

Pid Uid 验证

详细代码:

// Book.aidlpackagecom.jc.ipc.aidl;parcelableBook;

// IBookManager.aidlpackage com.jc.ipc.aidl;import com.jc.ipc.aidl.Book;import com.jc.ipc.aidl.INewBookArrivedListener;// AIDL 接口中只支持方法,不支持静态常量,区别于传统的接口interfaceIBookManager {ListgetBookList();// AIDL 中除了基本数据类型,其他数据类型必须标上方向,in,out 或者 inout// in 表示输入型参数// out 表示输出型参数// inout 表示输入输出型参数voidaddBook(inBookbook);voidregisterListener(INewBookArrivedListenerlistener);voidunregisterListener(INewBookArrivedListenerlistener);

}

// INewBookArrivedListener.aidlpackagecom.jc.ipc.aidl;importcom.jc.ipc.aidl.Book;// 提醒客户端新书到来interfaceINewBookArrivedListener {voidonNewBookArrived(inBooknewBook);

}

publicclassBookManagerActivityextendsAppCompatActivity {privatestaticfinalStringTAG=BookManagerActivity.class.getSimpleName();privatestaticfinalintMSG_NEW_BOOK_ARRIVED=0x10;privateButton getBookListBtn,addBookBtn;privateTextView displayTextView;privateIBookManager bookManager;privateHandler mHandler=newHandler(){@OverridepublicvoidhandleMessage(Messagemsg) {switch (msg.what) {caseMSG_NEW_BOOK_ARRIVED:Log.d(TAG,"handleMessage: new book arrived"+ msg.obj);Toast.makeText(BookManagerActivity.this,"new book arrived"+ msg.obj,Toast.LENGTH_SHORT).show();break;default:super.handleMessage(msg);            }        }    };privateServiceConnection mServiceConn=newServiceConnection() {@OverridepublicvoidonServiceConnected(ComponentNamename,IBinderservice) {            bookManager=IBookManager.Stub.asInterface(service);try {                bookManager.registerListener(listener);            }catch (RemoteException e) {                e.printStackTrace();            }        }@OverridepublicvoidonServiceDisconnected(ComponentNamename) {        }    };privateINewBookArrivedListener listener=newINewBookArrivedListener.Stub() {@OverridepublicvoidonNewBookArrived(BooknewBook)throwsRemoteException {            mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, newBook).sendToTarget();        }    };@OverrideprotectedvoidonCreate(@NullableBundlesavedInstanceState) {super.onCreate(savedInstanceState);        setContentView(R.layout.book_manager);        displayTextView= (TextView) findViewById(R.id.displayTextView);Intent intent=newIntent(this,BookManagerService.class);        bindService(intent, mServiceConn,BIND_AUTO_CREATE);    }publicvoidgetBookList(Viewview) {try {List list= bookManager.getBookList();Log.d(TAG,"getBookList:"+ list.toString());            displayTextView.setText(list.toString());        }catch (RemoteException e) {            e.printStackTrace();        }    }publicvoidaddBook(Viewview) {try {            bookManager.addBook(newBook(3,"天龙八部"));        }catch (RemoteException e) {            e.printStackTrace();        }    }@OverrideprotectedvoidonDestroy() {if (bookManager!=null&& bookManager.asBinder().isBinderAlive()) {Log.d(TAG,"unregister listener"+ listener);try {                bookManager.unregisterListener(listener);            }catch (RemoteException e) {                e.printStackTrace();            }        }        unbindService(mServiceConn);super.onDestroy();

    }

}

publicclassBookManagerServiceextendsService {privatestaticfinalStringTAG=BookManagerService.class.getSimpleName();// CopyOnWriteArrayList 支持并发读写,实现自动线程同步,他不是继承自 ArrayListprivateCopyOnWriteArrayList mBookList=newCopyOnWriteArrayList();//对象是不能跨进程传输的,对象的跨进程传输本质都是反序列化的过程,Binder 会把客户端传递过来的对象重新转化生成一个新的对象//RemoteCallbackList 是系统专门提供的用于删除系统跨进程 listener 的接口,利用底层的 Binder 对象是同一个//RemoteCallbackList 会在客户端进程终止后,自动溢出客户端注册的 listener ,内部自动实现了线程同步功能。privateRemoteCallbackList mListeners=newRemoteCallbackList<>();privateAtomicBoolean isServiceDestroied=newAtomicBoolean(false);privateBinder mBinder=newIBookManager.Stub() {@OverridepublicListgetBookList()throwsRemoteException {return mBookList;        }@OverridepublicvoidaddBook(Bookbook)throwsRemoteException {Log.d(TAG,"addBook:"+ book.toString());            mBookList.add(book);        }@OverridepublicvoidregisterListener(INewBookArrivedListenerlistener)throwsRemoteException {            mListeners.register(listener);        }@OverridepublicvoidunregisterListener(INewBookArrivedListenerlistener)throwsRemoteException {            mListeners.unregister(listener);        }    };@OverridepublicvoidonCreate() {super.onCreate();        mBookList.add(newBook(1,"老人与海"));        mBookList.add(newBook(2,"哈姆雷特"));newThread(newServiceWorker()).start();    }privatevoidonNewBookArrived(Bookbook)throwsRemoteException {        mBookList.add(book);int count= mListeners.beginBroadcast();for (int i=0; i< count; i++) {INewBookArrivedListener listener= mListeners.getBroadcastItem(i);if (listener!=null) {                listener.onNewBookArrived(book);            }        }        mListeners.finishBroadcast();    }privateclassServiceWorkerimplementsRunnable {@Overridepublicvoidrun() {while (!isServiceDestroied.get()) {try {Thread.sleep(5000);                }catch (InterruptedException e) {                    e.printStackTrace();                }int bookId= mBookList.size()+1;Book newBook=newBook(bookId,"new book #"+ bookId);try {                    onNewBookArrived(newBook);                }catch (RemoteException e) {                    e.printStackTrace();                }            }        }    }@Nullable@OverridepublicIBinderonBind(Intentintent) {//Permission 权限验证int check= checkCallingOrSelfPermission("com.jc.ipc.ACCESS_BOOK_SERVICE");if (check==PackageManager.PERMISSION_DENIED) {returnnull;        }return mBinder;    }@OverridepublicvoidonDestroy() {        isServiceDestroied.set(true);super.onDestroy();    }}

五、使用 ContentProvider

用于不同应用间数据共享,和 Messenger 底层实现同样是 Binder 和 AIDL,系统做了封装,使用简单。 系统预置了许多 ContentProvider ,如通讯录、日程表,需要跨进程访问。 使用方法:继承 ContentProvider 类实现 6 个抽象方法,这六个方法均运行在 ContentProvider 进程中,除 onCreate 运行在主线程里,其他五个方法均由外界回调运行在 Binder 线程池中。

ContentProvider 的底层数据,可以是 SQLite 数据库,可以是文件,也可以是内存中的数据。

详见代码:

publicclassBookProviderextends ContentProvider {

    privatestaticfinalString TAG = "BookProvider";

    publicstaticfinalString AUTHORITY = "com.jc.ipc.Book.Provider";

    publicstaticfinalUri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");

    publicstaticfinalUri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");

    publicstaticfinalintBOOK_URI_CODE = 0;

    publicstaticfinalintUSER_URI_CODE = 1;

    privatestaticfinalUriMatcher sUriMatcher =new UriMatcher(UriMatcher.NO_MATCH);

    static {

        sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);

        sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);

    }

    private Context mContext;

    private SQLiteDatabase mDB;

    @Override

    publicboolean onCreate() {

        mContext = getContext();

        initProviderData();

        returntrue;

    }

    privatevoid initProviderData() {

        //不建议在 UI 线程中执行耗时操作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,'haohao',1);");

        mDB.execSQL("insert into user values(2,'nannan',0);");

    }

    @Nullable

    @Override

    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

        Log.d(TAG, "query, current thread"+ Thread.currentThread());

        String table = getTableName(uri);

        if(table ==null) {

            thrownewIllegalArgumentException("Unsupported URI" + uri);

        }

        returnmDB.query(table, projection, selection, selectionArgs,null,null, sortOrder,null);

    }

    @Nullable

    @Override

    public String getType(Uri uri) {

        Log.d(TAG, "getType");

        returnnull;

    }

    @Nullable

    @Override

    public Uri insert(Uri uri, ContentValues values) {

        Log.d(TAG, "insert");

        String table = getTableName(uri);

        if(table ==null) {

            thrownewIllegalArgumentException("Unsupported URI" + uri);

        }

        mDB.insert(table, null, values);

        // 通知外界 ContentProvider 中的数据发生变化mContext.getContentResolver().notifyChange(uri,null);

        return uri;

    }

    @Override

    publicint delete(Uri uri, String selection, String[] selectionArgs) {

        Log.d(TAG, "delete");

        String table = getTableName(uri);

        if(table ==null) {

            thrownewIllegalArgumentException("Unsupported URI" + uri);

        }

        intcount = mDB.delete(table, selection, selectionArgs);

        if(count > 0) {

            mContext.getContentResolver().notifyChange(uri, null);

        }

        return count;

    }

    @Override

    publicint update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

        Log.d(TAG, "update");

        String table = getTableName(uri);

        if(table ==null) {

            thrownewIllegalArgumentException("Unsupported URI" + uri);

        }

        introw = mDB.update(table, values, selection, selectionArgs);

        if(row > 0) {

            getContext().getContentResolver().notifyChange(uri, null);

        }

        return row;

    }

    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;

    }

}


publicclassDBOpenHelperextends SQLiteOpenHelper {

    privatestaticfinalString DB_NAME = "book_provider.db";

    publicstaticfinalString BOOK_TABLE_NAME = "book";

    publicstaticfinalString USER_TABLE_NAME = "user";

    privatestaticfinalintDB_VERSION = 1;

    privateString CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "            + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";

    privateString 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

    publicvoid onCreate(SQLiteDatabase db) {

        db.execSQL(CREATE_BOOK_TABLE);

        db.execSQL(CREATE_USER_TABLE);

    }

    @Override

    publicvoidonUpgrade(SQLiteDatabase db,intoldVersion,int newVersion) {

    }

}


publicclassProviderActivityextends AppCompatActivity {

    privatestaticfinalString TAG = ProviderActivity.class.getSimpleName();

    private TextView displayTextView;

    private Handler mHandler;

    @Override

    protectedvoid onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_provider);

        displayTextView = (TextView) findViewById(R.id.displayTextView);

        mHandler =new Handler();

        getContentResolver().registerContentObserver(BookProvider.BOOK_CONTENT_URI, true,new ContentObserver(mHandler) {

            @Override

            publicboolean deliverSelfNotifications() {

                returnsuper.deliverSelfNotifications();

            }

            @Override

            publicvoidonChange(boolean selfChange) {

                super.onChange(selfChange);

            }

            @Override

            publicvoidonChange(boolean selfChange, Uri uri) {

                Toast.makeText(ProviderActivity.this, uri.toString(), Toast.LENGTH_SHORT).show();

                super.onChange(selfChange, uri);

            }

        });

    }

    publicvoid insert(View v) {

        ContentValues values =new ContentValues();

        values.put("_id",1123);

        values.put("name", "三国演义");

        getContentResolver().insert(BookProvider.BOOK_CONTENT_URI, values);

    }

    publicvoid delete(View v) {

        getContentResolver().delete(BookProvider.BOOK_CONTENT_URI, "_id = 4",null);

    }

    publicvoid update(View v) {

        ContentValues values =new ContentValues();

        values.put("_id",1123);

        values.put("name", "三国演义新版");

        getContentResolver().update(BookProvider.BOOK_CONTENT_URI, values , "_id = 1123",null);

    }

    publicvoid query(View v) {

        Cursor bookCursor = getContentResolver().query(BookProvider.BOOK_CONTENT_URI,newString[]{"_id", "name"},null,null,null);

        StringBuilder sb =new StringBuilder();

        while (bookCursor.moveToNext()) {

            Book book =newBook(bookCursor.getInt(0),bookCursor.getString(1));

            sb.append(book.toString()).append("\n");

        }

        sb.append("--------------------------------").append("\n");

        bookCursor.close();

        Cursor userCursor = getContentResolver().query(BookProvider.USER_CONTENT_URI,newString[]{"_id", "name", "sex"},null,null,null);

        while (userCursor.moveToNext()) {

            sb.append(userCursor.getInt(0))

                    .append(userCursor.getString(1)).append(" ,")

                    .append(userCursor.getInt(2)).append(" ,")

                    .append("\n");

        }

        sb.append("--------------------------------");

        userCursor.close();

        displayTextView.setText(sb.toString());

    }

}


六、使用 Socket

Socket起源于 Unix,而 Unix 基本哲学之一就是“一切皆文件”,都可以用“打开 open –读写 write/read –关闭 close ”模式来操作。Socket 就是该模式的一个实现,网络的 Socket 数据传输是一种特殊的 I/O,Socket 也是一种文件描述符。Socket 也具有一个类似于打开文件的函数调用: Socket(),该函数返回一个整型的Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。

常用的 Socket 类型有两种:流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的 Socket ,对应于无连接的 UDP 服务应用。

Socket 本身可以传输任意字节流。

谈到Socket,就必须要说一说 TCP/IP 五层网络模型:

应用层:规定应用程序的数据格式,主要的协议 HTTP,FTP,WebSocket,POP3 等;

传输层:建立“端口到端口” 的通信,主要的协议:TCP,UDP;

网络层:建立”主机到主机”的通信,主要的协议:IP,ARP ,IP 协议的主要作用:一个是为每一台计算机分配 IP 地址,另一个是确定哪些地址在同一子网;

数据链路层:确定电信号的分组方式,主要的协议:以太网协议;

物理层:负责电信号的传输。

Socket 是连接应用层与传输层之间接口(API)。


只实现 TCP Socket 。

Client 端代码:

publicclassTCPClientActivityextendsAppCompatActivityimplements View.OnClickListener{

    privatestaticfinalString TAG = "TCPClientActivity";

    publicstaticfinalintMSG_RECEIVED = 0x10;

    publicstaticfinalintMSG_READY = 0x11;

    private EditText editText;

    private TextView textView;

    private PrintWriter mPrintWriter;

    private Socket mClientSocket;

    private Button sendBtn;

    private StringBuilder stringBuilder;

    privateHandler mHandler =new Handler(){

        @Override

        publicvoid handleMessage(Message msg) {

            switch (msg.what) {

                case MSG_READY:

                    sendBtn.setEnabled(true);

                    break;

                case MSG_RECEIVED:

                    stringBuilder.append(msg.obj).append("\n");

                    textView.setText(stringBuilder.toString());

                    break;

                default:

                    super.handleMessage(msg);

            }

    }

    };

    @Override

    protectedvoid onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.tcp_client_activity);

        editText = (EditText) findViewById(R.id.editText);

        textView = (TextView) findViewById(R.id.displayTextView);

        sendBtn = (Button) findViewById(R.id.sendBtn);

        sendBtn.setOnClickListener(this);

        sendBtn.setEnabled(false);

        stringBuilder =new StringBuilder();

        Intent intent =newIntent(TCPClientActivity.this, TCPServerService.class);

        startService(intent);

        new Thread(){

            @Override

            publicvoid run() {

                connectTcpServer();

            }

        }.start();

    }

    privateString formatDateTime(long time) {

        returnnewSimpleDateFormat("(HH:mm:ss)").format(new Date(time));

    }

    privatevoid connectTcpServer() {

        Socket socket =null;

        while(socket ==null) {

            try {

                socket =newSocket("localhost", 8888);

                mClientSocket = socket;

                mPrintWriter =newPrintWriter(new BufferedWriter(

                        new OutputStreamWriter(socket.getOutputStream())

                ), true);

                mHandler.sendEmptyMessage(MSG_READY);

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

        // receive messageBufferedReader bufferedReader =null;

        try {

            bufferedReader =newBufferedReader(new InputStreamReader(socket.getInputStream()));

        } catch (IOException e) {

            e.printStackTrace();

        }

        while(!isFinishing()) {

            try {

                String msg = bufferedReader.readLine();

                if(msg !=null) {

                    String time = formatDateTime(System.currentTimeMillis());

                    String showedMsg = "server " + time + ":" + msg

                            + "\n";

                    mHandler.obtainMessage(MSG_RECEIVED, showedMsg).sendToTarget();

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }

    @Override

    publicvoid onClick(View v) {

        if(mPrintWriter !=null) {

            String msg = editText.getText().toString();

            mPrintWriter.println(msg);

            editText.setText("");

            String time = formatDateTime(System.currentTimeMillis());

            String showedMsg = "self " + time + ":" + msg + "\n";

            stringBuilder.append(showedMsg);

        }

    }

    @Override

    protectedvoid onDestroy() {

        if(mClientSocket !=null) {

            try {

                mClientSocket.shutdownInput();

                mClientSocket.close();

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

        super.onDestroy();

    }

}

Server端代码:

publicclassTCPServerServiceextends Service {

    privatestaticfinalString TAG = "TCPServerService";

    privatebooleanisServiceDestroyed =false;

    privateString[] mMessages =new String[]{

            "Hello! Body!",

            "用户不在线!请稍后再联系!",

            "请问你叫什么名字呀?",

            "厉害了,我的哥!",

            "Google 不需要***是真的吗?",

            "扎心了,老铁!!!"    };

    @Override

    publicvoid onCreate() {

        newThread(new TCPServer()).start();

        super.onCreate();

    }

    @Override

    publicvoid onDestroy() {

        isServiceDestroyed =true;

        super.onDestroy();

    }

    @Nullable

    @Override

    public IBinder onBind(Intent intent) {

        returnnull;

    }

    privateclassTCPServerimplements Runnable {

        @Override

        publicvoid run() {

            ServerSocket serverSocket =null;

            try {

                serverSocket =newServerSocket(8888);

            } catch (IOException e) {

                e.printStackTrace();

                return;

            }

            while(!isServiceDestroyed) {

                // receive request from clienttry {

                    finalSocket client = serverSocket.accept();

                    Log.d(TAG, "=============== accept ==================");

                    new Thread(){

                        @Override

                        publicvoid run() {

                            try {

                                responseClient(client);

                            } catch (IOException e) {

                                e.printStackTrace();

                            }

                        }

                    }.start();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        }

    }

    privatevoidresponseClient(Socket client)throws IOException {

        //receive messageBufferedReader in =new BufferedReader(

                new InputStreamReader(client.getInputStream()));

        //send messagePrintWriter out =new PrintWriter(

                new BufferedWriter(

                        new OutputStreamWriter(

                                client.getOutputStream())),true);

        out.println("欢迎来到聊天室!");

        while(!isServiceDestroyed) {

            String str = in.readLine();

            Log.d(TAG, "message from client: " + str);

            if(str ==null) {

                return;

            }

            Random random =new Random();

            intindex = random.nextInt(mMessages.length);

            String msg = mMessages[index];

            out.println(msg);

            Log.d(TAG, "send Message: " + msg);

        }

        out.close();

        in.close();

        client.close();

    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266