本文为Android开发艺术探索的笔记,仅供学习
4.6 Socket的使用
我们可以通过Socket来进行跨进程通信。Socket也成为“套接字”,是网络通信的概念,它可分为流式套接字和用户数据报套接字,分别对应的是TCP和UDP。TCP是面向连接协议的,提供稳定的双向通信功能,TCP连接的建立需要通过“三次握手”才能完成,为了提供稳定的传输功能,TCP本身提供了超时重传机制,由此具有很高的稳定性。UPD是无连接的,提供了不稳定的单道通信功能,其本身也能实现双向通信功能,在性能上,UDP更加的高效。缺点就是不能保证数据的完整性可能会出现断包现象。
首先要使用Socket通信,一定要在xml里声明权限
注意网络请求不能放在主线程里,尤其是在4.0及以上的版本的时候,系统会认为这是一个耗时操作,会报错。
大致说一下步骤吧,首先我们要远程Service建立TCP服务,然后在主界面去连着TCP协议,连接上后就能向服务端发送消息,然后服务端会随机想客户端发送消息。
先看一下服务端的设计,当Service启动时,会在线程中建立TCP服务,这里监听的是8688端口,然后就可以等待客户端的连接请求。当有客户端连接时,就会生成一个新的Socket,通过每次新创建的Socket就可以分别和不同的客户端通信了。服务端每次受到一次客户端的消息就会随机回复一句话给客户端。当客户端断开连接时,服务端这边也会相应的关闭对应Socket并通信线程。这里是通过输入流为空,则断定客户端退出。
直接上代码吧 客户端的代码
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener {
private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
private static final int MESSAGE_SOCKET_CONNECTED = 2;
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_RECEIVE_NEW_MSG: {
mMessageTextView.setText(mMessageTextView.getText()
+ (String) msg.obj);
break;
}
case MESSAGE_SOCKET_CONNECTED: {
mSendButton.setEnabled(true);
break;
}
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
mMessageTextView = (TextView) findViewById(R.id.msg_container);
mSendButton = (Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText = (EditText) findViewById(R.id.msg);
//启动服务,在服务端里创建TCP
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPServer();//建立TCP连接
}
}.start();
}
@Override
protected void onDestroy() {
if (mClientSocket != null) {
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
//点击按钮向服务端发送信息
@Override
public void onClick(View v) {
if (v == mSendButton) {
final String msg = mMessageEditText.getText().toString();
if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "self " + time + ":" + msg + "\n";
mMessageTextView.setText(mMessageTextView.getText() + showedMsg);
}
}
}
@SuppressLint("SimpleDateFormat")
private String formatDateTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
private void connectTCPServer() {
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true); mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
System.out.println("connect tcp server failed, retry...");
}
}
try {
// 接收服务器端的消息
BufferedReader br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msg = br.readLine();
System.out.println("receive :" + msg);
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "server " + time + ":" + msg
+ "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg)
.sendToTarget();
}
}
System.out.println("quit...");
mPrintWriter.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这是服务端的代码
public class TCPServerService extends Service {
private boolean mIsServiceDestoryed = false;
private String[] mDefinedMessages = new String[] {
"你好啊,哈哈",
"请问你叫什么名字呀?",
"今天北京天气不错啊,shy",
"你知道吗?我可是可以和多个人同时聊天的哦",
"给你讲个笑话吧:据说爱笑的人运气不会太差,不知道真假。"
};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestoryed = true;
super.onDestroy();
}
private class TcpServer implements Runnable {
@SuppressWarnings("resource")
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);//创建TCP通道
} catch (IOException e) {
System.err.println("establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestoryed) {
try {
// 获取客户端请求
final Socket client = serverSocket.accept();
System.out.println("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 (!mIsServiceDestoryed) {
String str = in.readLine();
System.out.println("msg from client:" + str);
if (str == null) {
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg);
System.out.println("send :" + msg);
}
System.out.println("client quit.");
// 关闭流
out.close();
in.close();
client.close();
}
}
5 Binder 连接池
首先我们来回顾一下AIDL的使用方式
首先创建Service和AIDL接口,在Service里创建个类去继承AIDL的Stub类变实现AIDL的接口,在Service的onBind方法去返回这个类的对象,在客服端去绑带Service,然后就建立了远程访问服务器。
这是一个典型的AIDL使用方法,但是如果当有10个业务模块需要AIDL,那我们是不是要建立10个对于的Service?
针对上述问题,我们需要减少Service的使用将所有的AIDL都放在一个Service里面,这样每个业务模块都可以创建接口,并且每个业务模块之间是不能有耦合的,客户端则需提供自己的唯一标识和对于的Binder对象;服务端则只需要一个Service即可,去提供一个queryBinder接口,这个就看根据业务模块去返回相应的Binder,然后不同的业务模块拿到想要的Binder去进行远程操控就可以的。
大致就是,先去创建AIDL接口和实现该接口的类(Stub),然后再去写一个queryBinder的AIDL接口和类,在queryBinder的AIDL接口的类里去做处理,这里的处理就是通过接受Activity传来的标识去放回其需要的Binder,然后再Activity中就可以就收到想要的Binder类,从而去调用接口。
先来其中的一个业务类
//ICompute.aidl
interface ICompute {
int add(int a, int b);
}
//ComputeImpl.java
public class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}//这样一个业务模块的AIDL接口就写好了
下面就贴上关键代码 BinderPool
//IBinderPool .aidl 主要是用来返回客户输入相应的binderCode返回对于的AIDL接口
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
//BinderPool .java
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_NONE = -1;
public static final int BINDER_COMPUTE = 0;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;//这个参数就是为了同步线程,目的就是为了客户端在执行其他操作的时候,已经绑定好了服务器,不然还没绑带就去调用会出事情的
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();//改方法就是为了绑定线程,和绑定服务
}
public static BinderPool getInsance(Context context) {//这就是一种单例模式,通过传来的context去构造函数
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext, BinderPoolService.class);
mContext.bindService(service, mBinderPoolConnection,
Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* query binder by binderCode from binder pool
*
* @param binderCode
* the unique token of binder
* @return binder who's token is binderCode<br>
* return null when not found or BinderPoolService died.
*/
public IBinder query(int binderCode) {//根据binderCode去会回掉相应的binder对象
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
//大家都知道ServiceConnection 是在绑带成功是回掉该函数
@Override
public void onServiceDisconnected(ComponentName name) {
// ignored.
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try { mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);//去注册死亡通知
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
};
//IBinder死亡代理
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(TAG, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();//当Binder 异常中断的时候再开启Service
}
};
public static class BinderPoolImpl extends IBinderPool.Stub {
//去调用AIDL接口
public BinderPoolImpl() {
super();
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
default:
break;
}
return binder;
}
}
}
总的来说BinderPool 帮我们做了2件事
- 绑带BinderPoolService服务,我们要知道我们需要通过Service回传的IBinder对象进程跨进程
- IBinderPool的queryBinder方法,去回掉对应的AIDL接口
接下来我们来看看客户端和服务端的代码
服务端的代码
public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
private Binder mBinderPool = new BinderPool.BinderPoolImpl();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return mBinderPool;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
客户端的代码
ublic class BinderPoolActivity extends Activity {
private static final String TAG = "BinderPoolActivity";
private ISecurityCenter mSecurityCenter;
private ICompute mCompute;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder_pool);
new Thread(new Runnable() {
@Override
public void run() {
doWork();
}
}).start();
}
private void doWork() {
BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity.this);
IBinder securityBinder = binderPool
.query(BinderPool.BINDER_SECURITY_CENTER);
mSecurityCenter = (ISecurityCenter) SecurityCenterImpl
.asInterface(securityBinder);
Log.d(TAG, "visit ISecurityCenter");
String msg = "helloworld-安卓";
System.out.println("content:" + msg);
try {
String password = mSecurityCenter.encrypt(msg);
System.out.println("encrypt:" + password);
System.out.println("decrypt:" + mSecurityCenter.decrypt(password));
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG, "visit ICompute");
IBinder computeBinder = binderPool
.query(BinderPool.BINDER_COMPUTE);
mCompute = ComputeImpl.asInterface(computeBinder);
try {
System.out.println("3+5=" + mCompute.add(3, 5));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
以上代码就实现了Binder 连接池