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

本贴最后更新于 2331 天前,其中的信息可能已经水流花落

这两天做了一个项目是 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

  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    336 引用 • 324 回帖
  • Socket
    21 引用 • 23 回帖
  • TCP
    32 引用 • 38 回帖 • 2 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    89 引用 • 122 回帖 • 620 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 181 关注
  • 自由行
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    83 引用 • 37 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 384 回帖 • 10 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 637 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    21 引用 • 31 回帖
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 13 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    24 引用 • 241 回帖 • 1 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖 • 1 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 544 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 91 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    174 引用 • 529 回帖
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    286 引用 • 248 回帖
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 434 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 647 关注
  • Anytype
    3 引用 • 31 回帖 • 16 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖 • 1 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 634 关注
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 2 关注
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    188 引用 • 319 回帖 • 251 关注