前言
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
终于讲到了重点,Messenger
对Handler
和AIDL
进行封装,实现了简单的数据传送,一次只能处理一个请求,所以不用考虑进程同步的问题.
服务端进程
第一篇里面画了一张图,在其基础上修改一下就可以变成Messenger的工作机制图解:
服务端需要一个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
,通过这个对象就可以给服务端发送消息.
如果需要服务端能够回应
- 创建
Message
对象 - 创建一个新的
Handler
- 通过
Handler
创建Messenger
- 将这个
Messenger
通过Message
的replyTo
参数传给服务端 - 服务端通过
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
处理服务端返回的数据了.
工作原理图
AIDL
- Messenger不适用于大量的并发请求,它是对AIDL的封装,适用范围更窄,有时使用一更些开源库的时候可能回想,为什么不再封装一下呢,那样使用起来简单了,后来我慢慢明白,封装会导致可DIY度降低,封装度越高,可DIY度越低,那么库的适用范围就越窄,那有很多需求就不能满足了
- 我们需要夸进程调用服务端方法的时候Messenger无法做到。
AIDL
的具体实现还是BinderbinderService
的时候,服务端会返回一个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!
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();