Android Socket连接(模拟心跳包,断线重连,发送数据等)

2019-02-25 10:05:41

参考链接 Android Socket连接(模拟心跳包,断线重连,发送数据等)


这两天做了一个项目是app通过socket连接自动炒菜机,给炒菜机发指令,炒菜机接收到指令会执行相应的操作。(程序虽然做的差不多了,然而我连炒菜机长什么样都没见过)


其实作为一个会做饭的程序猿,我坚信还是自己动手做的饭菜比较好吃,毕竟做饭还是很有趣的。




闲话不多说,因为是通过socket去连接炒菜机的,并且要求每两秒要给炒菜机发送一个指令,点击按钮的话也要发送相应的指令。

所以要考虑一些问题,比如断线重连,数据发送失败了重连,要保持全局只有一个连接等等。


因为是要保证全局只能有一个连接,而且我们还需要在不同的Activity中发指令,因此肯定不能在需要发指令的界面中都去连接socket,这样一来不好管理,性能也不好,重复代码也会比较多,所以想了一下还是把socket放到service中比较好,发指令功能都放在service中即可。


记得要先给网络权限


    <uses-permission android:name="android.permission.INTERNET" />

1

下面我们来看看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();

    }

}



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

好了,连接的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);


    }

}


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85



ok,大功告成

下面是demo,我用的是android studio3.0预览版,可能gradle版本会高一些。

socketDemo


另外一种方式是采用Netty+Kotlin+RxJava方式实现的。使用起来更加简单,详情请看

Android Netty版本

--------------------- 

作者:朽木_不折 

来源:CSDN 

原文:https://blog.csdn.net/yuzhiqiang_1993/article/details/78094909 

版权声明:本文为博主原创文章,转载请附上博文链接!


  • 2019-06-04 16:40:30

    为了美观当网页图片不存在时不显示叉叉图片

    当在页面显示的时候,万一图片被移动了位置或者丢失的话,将会在页面显示一个带X的图片,很是影响用户的体验。即使使用alt属性给出了”图片XX”的提示信息,也起不了多大作用。

  • 2019-06-04 17:38:44

    PHP时间转换今天昨天前天几天前

    实际情况应该是,昨天任何时间都算一天前,前天任意时间都算2天前,所以自己琢磨了一番,去动态更新时间与今天23:59:59相差的时间秒数与86400(24 x 3600)相除后,向下取整,这样就得到了相差的天数,比如昨天00:00~昨天23:59:59的任何时间与今天的23:59:59,都相差 86400~(86400 x 2) 天,也就是2天。

  • 2019-06-04 17:44:14

    【VUE】图片预览放大缩小插件

    在看项目时,突然看到预览图片的弹窗,感觉好僵硬,不能放大,不能切换,于是便在网上找下关于图片预览的插件,有找到三个插件,具体的优劣势的看自己的使用吧,我目前只是在电脑查看效果,分别是viewerjs插件、基于photoswipe的vue-photo-preview插件以及vue-picture-preview插件 作者:北极星丶超帅的 链接:https://www.jianshu.com/p/e3350aa1b0d0 来源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 2019-06-04 21:32:05

    Android 全局Dialog的简单实现

    这里所说的全局Dialog是指无论当前应用是处于哪一个页面上,都能够及时弹出Dialog来提示用户一些信息,用户体验不会很好,一般应用于优先级非常高的通知。

  • 2019-06-06 16:14:50

    intent-filter的action,category,data匹配规则

    我们知道有两种方式来启动Activity,显示调用和隐式调用。当使用隐式调用时,又会涉及到IntentFilter的匹配规则。我确信大多数开发者很少关注隐式调用,因为平时开发中用到大多数是显示调用。例如:用Intent直接打开一个Activity,或者用Intent通过包名等其他信息打开另外一个应用等。而隐式调用则使用的比较少,当然也不是完全不使用。例如:当我们需要打开浏览器访问某个链接时,手机上可能存在多个浏览器,我们也无法拿到某一个浏览器的包名,那么一般情况下我们会写如下代码:

  • 2019-06-06 17:57:57

    input【type="checkbox"】标签与字体对齐

    今天分享一个比较实用的技巧,在实际项目中我们会经常遇到表单的input标签多选和单选的问题,但是往往由于标签自身的样式和我们项目的风格很不搭调,就不能实现了,今天就来告诉大家怎么去实现吧。

  • 2019-06-10 11:54:52

    html.div禁用点击事件

    今天做项目中偶然误把元素加上了pointer-events属性,结果导致后来在js中给该元素加点击事件不能用,检查了半天才发现是这个属性的问题。之前没有好好研究,于是决定仔细研究一下。