这两天做了一个项目是 app 通过 socket 连接自动炒菜机,给炒菜机发指令,炒菜机接收到指令会执行相应的操作。(程序虽然做的差不多了,然而我连炒菜机长什么样都没见过)
其实作为一个会做饭的程序猿,我坚信还是自己动手做的饭菜比较好吃,毕竟做饭还是很有趣的。
闲话不多说,因为是通过 socket 去连接炒菜机的,并且要求每两秒要给炒菜机发送一个指令,点击按钮的话也要发送相应的指令。
所以要考虑一些问题,比如断线重连,数据发送失败了重连,要保持全局只有一个连接等等。
因为是要保证全局只能有一个连接,而且我们还需要在不同的 Activity 中发指令,因此肯定不能在需要发指令的界面中都去连接 socket,这样一来不好管理,性能也不好,重复代码也会比较多,所以想了一下还是把 socket 放到 service 中比较好,发指令功能都放在 service 中即可。
记得要先给网络权限
<uses-permission android:name="android.permission.INTERNET" />
下面我们来看看 Service 中的代码,其中有些细节是需要注意的
1)我们要保证只有一个连接服务运行,所以在启动服务之前先判断一下连接服务是否正在运行,如果正在运行,就不再启动服务了。
2)连接成功之后给出相应的通知,告诉连接者连接成功了,方便进行下一步操作,这里为了省事儿就直接用 EventBus 去通知了。也可以用广播的方式去通知。
3)连接超时之后要注意先释放调之前的资源,然后重新初始化
package com.yzq.socketdemo.service;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.TabHost;
import android.widget.Toast;
import com.yzq.socketdemo.common.Constants;
import com.yzq.socketdemo.common.EventMsg;
import org.greenrobot.eventbus.EventBus;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by yzq on 2017/9/26.
* <p>
* socket连接服务
*/
public class SocketService extends Service {
/*socket*/
private Socket socket;
/*连接线程*/
private Thread connectThread;
private Timer timer = new Timer();
private OutputStream outputStream;
private SocketBinder sockerBinder = new SocketBinder();
private String ip;
private String port;
private TimerTask task;
/*默认重连*/
private boolean isReConnect = true;
private Handler handler = new Handler(Looper.getMainLooper());
@Override
public IBinder onBind(Intent intent) {
return sockerBinder;
}
public class SocketBinder extends Binder {
/*返回SocketService 在需要的地方可以通过ServiceConnection获取到SocketService */
public SocketService getService() {
return SocketService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/*拿到传递过来的ip和端口号*/
ip = intent.getStringExtra(Constants.INTENT_IP);
port = intent.getStringExtra(Constants.INTENT_PORT);
/*初始化socket*/
initSocket();
return super.onStartCommand(intent, flags, startId);
}
/*初始化socket*/
private void initSocket() {
if (socket == null && connectThread == null) {
connectThread = new Thread(new Runnable() {
@Override
public void run() {
socket = new Socket();
try {
/*超时时间为2秒*/
socket.connect(new InetSocketAddress(ip, Integer.valueOf(port)), 2000);
/*连接成功的话 发送心跳包*/
if (socket.isConnected()) {
/*因为Toast是要运行在主线程的 这里是子线程 所以需要到主线程哪里去显示toast*/
toastMsg("socket已连接");
/*发送连接成功的消息*/
EventMsg msg = new EventMsg();
msg.setTag(Constants.CONNET_SUCCESS);
EventBus.getDefault().post(msg);
/*发送心跳数据*/
sendBeatData();
}
} catch (IOException e) {
e.printStackTrace();
if (e instanceof SocketTimeoutException) {
toastMsg("连接超时,正在重连");
releaseSocket();
} else if (e instanceof NoRouteToHostException) {
toastMsg("该地址不存在,请检查");
stopSelf();
} else if (e instanceof ConnectException) {
toastMsg("连接异常或被拒绝,请检查");
stopSelf();
}
}
}
});
/*启动连接线程*/
connectThread.start();
}
}
/*因为Toast是要运行在主线程的 所以需要到主线程哪里去显示toast*/
private void toastMsg(final String msg) {
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
});
}
/*发送数据*/
public void sendOrder(final String order) {
if (socket != null && socket.isConnected()) {
/*发送指令*/
new Thread(new Runnable() {
@Override
public void run() {
try {
outputStream = socket.getOutputStream();
if (outputStream != null) {
outputStream.write((order).getBytes("gbk"));
outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} else {
toastMsg("socket连接错误,请重试");
}
}
/*定时发送数据*/
private void sendBeatData() {
if (timer == null) {
timer = new Timer();
}
if (task == null) {
task = new TimerTask() {
@Override
public void run() {
try {
outputStream = socket.getOutputStream();
/*这里的编码方式根据你的需求去改*/
outputStream.write(("test").getBytes("gbk"));
outputStream.flush();
} catch (Exception e) {
/*发送失败说明socket断开了或者出现了其他错误*/
toastMsg("连接断开,正在重连");
/*重连*/
releaseSocket();
e.printStackTrace();
}
}
};
}
timer.schedule(task, 0, 2000);
}
/*释放资源*/
private void releaseSocket() {
if (task != null) {
task.cancel();
task = null;
}
if (timer != null) {
timer.purge();
timer.cancel();
timer = null;
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
outputStream = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
socket = null;
}
if (connectThread != null) {
connectThread = null;
}
/*重新初始化socket*/
if (isReConnect) {
initSocket();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("SocketService", "onDestroy");
isReConnect = false;
releaseSocket();
}
}
好了,连接的 service 我们基本就做好了,先来看看效果,调试工具使用的是一个网络调试助手,免去我们写服务端的代码。
来看看效果图:
可以看到,断线重连,连接成功自动发送数据,连接成功发消息这些都有了,实际上数据发送失败重连也是有的,不过模拟器上间隔时间很长,不知道怎么回事,真机没有问题。
解决了 service 下面就是 Activity 于 service 通信的问题了。这个就简单了,我们在 service 中提供了一个 binder,我们可以通过 binder 来拿到 service,然后调 service 的 sendOrder()即可
先来看看示例代码:
package com.yzq.socketdemo.activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.EditText;
import com.yzq.socketdemo.R;
import com.yzq.socketdemo.service.SocketService;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Created by yzq on 2017/9/26.
* <p>
* mainActivity
*/
public class MainActivity extends AppCompatActivity {
@BindView(R.id.contentEt)
EditText contentEt;
@BindView(R.id.sendBtn)
Button sendBtn;
private ServiceConnection sc;
public SocketService socketService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindSocketService();
ButterKnife.bind(this);
}
private void bindSocketService() {
/*通过binder拿到service*/
sc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
SocketService.SocketBinder binder = (SocketService.SocketBinder) iBinder;
socketService = binder.getService();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
Intent intent = new Intent(getApplicationContext(), SocketService.class);
bindService(intent, sc, BIND_AUTO_CREATE);
}
@OnClick(R.id.sendBtn)
public void onViewClicked() {
String data = contentEt.getText().toString().trim();
socketService.sendOrder(data);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(sc);
Intent intent = new Intent(getApplicationContext(), SocketService.class);
stopService(intent);
}
}
ok,大功告成
socketDemo
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于