MaWenge's Blog


  • 首页

  • 归档

  • 标签
MaWenge's Blog

自己搭梯子

  |   377 字   |   1 min 发表于 2018-02-28 |

给大家介绍一个简单的翻墙的方法

现状

由于为了网络安全,大量提供VPN服务的公司被关掉,以前那种虽然网速不快的付费翻墙服务也没法用了。以前同事还在自己的服务器上搭建了一个VPN服务,但是接连收到阿里云的消息,吓得赶紧关掉。顶多使用一些镜像Google来查询问题,但是也会出现不能用的时候,十分不方便。

解决方案

条件:一台服务器 + xshell + chrome + SwitchySharp扩展程序。
1 首先使用xshell通过ssh连接到远程服务器上。

2连接成功之后打开隧道窗格显示(查看-> 隧道窗格),此时xshell底部会出现如下窗格

3 选中转移规则,在下面的空白处右键添加,类型选择Dynamic(SOCKS4/5),侦听端口自定义,默认1080。创建成功之后底部就会显示。


这样xshell中配置完成,相当只要本机把数据发生到刚才指定的端口,数据就会传输到我们的远程服务器上。
4在Chrome中添加SwitchySharp插件。
5在SwitchySharp中添加一个情景模式

6大功告成。每次想要爬梯子的时候只要打开xshell连接到远程服务器(保证隧道连接建立),然后再SwitchySharp选中对应模式即可。

最后

希望大家好好利用工具,为社会主义建设作出贡献!

共15.7k字 阅读时长:1分
MaWenge's Blog

一次线上内存泄漏排查

  |   1,233 字   |   4 min 发表于 2018-02-28 |

问题描述

2017春节前夕,车辆服务每隔三四天会出现宕机,周期也比较稳定,基本就是维持在三四天左右。宕机的原因是堆内存溢出。
初步判断造成内存溢出的原因是发生了内存泄漏,大量堆内的无用对象无法被回收,导致新创建的对象无法申请到足够的空间。

问题排查

由于是过年期间,技术部所有人都放假回家,服务负责人在家中进行问题排查,也没有发现什么头绪。由于马上春节,用户量会逐渐减少到0,业务量也不是很大。所以就暂时的应对方案是每隔两天手动重启一次服务,这种临时性的解决方案帮助我们度过了假期期间的业务。

年后回来,正式开始排查问题。2018/02/26上午,服务刚刚被重启,在阿里云后台监测ecs发现运行正常。8g的内存监控后台显示使用了2g,看似正常,并且所有请求处理也正常。连到服务后台进行查看。

查看虚拟机启动参数

可以看出最大堆内存为 2051014656/(1024 * 1024) = 1956M。
查看进程内存cpu占用情况

可以看到20814进程总共占用内存1.414g。
现在看起来都一切正常

再看一下gc的情况,每隔10秒采一次样

可以看出,到目前为止,每次新生代回收一次需要大概22ms,fullgc总共发生了11次,查询进程启动时间为31.5小时

fullgc相当于每2.86小时发生一次,每次fullgc使用的时间为5350/11=486ms,还算正常。

到目前为止没有发现异常,主要是服务启动时间也不长,但是如果等到服务不行的时候再次查看数据,这无疑拉长了问题解决周期,并且春节过后业务量逐渐恢复正常,问题出现的周期很有可能缩短。

所以,需要查看当前堆中内存分布情况。
整体思路有两个:
1 使用jdk自带的visualvm工具分析,最终放弃,原因主要有:
1)服务器不好直接运行visualvm这种可视化工具,visualvm可以支持连接远程服务器,但是貌似不好用,
2)visualvm对堆内对象分析功能不够强大,有些类型信息不能完整显示,而且没法显示引用关系。
这些都不能帮助我有效分析当前内存快照的情况。

2 还有就是使用jmap + mat组合。把进程当前内存快照保存成文件然后传到本地使用mat工具进行分析

这里一定要在登录用户为创建进程的用户的时候执行相关命令,否则会出现process not found的情况。
然后把生成的文件传到本地,使用mat分析。mat就是Memory Analyzer (MAT),它是eclipse的一个插件。用来分析二进制堆文件。

由上图可以知道,进程启动时间是 2018/02/26 08:58:29

在 2018/02/26 16:44:00我做了一次内存快照如下图


总内存138.6MB,其中54.6MB疑似内存泄漏,泄漏对象类型是com.taobao.txc.common.config.ConsoleConfig。

在 2018/02/27 09:48:00,再次做内存快照


此时可以发现,意思泄漏内存增长到175MB,泄漏对象类型是com.taobao.txc.common.config.ConsoleConfig。

在 2018/02/28 10:02:00,再次做内存快照



此时疑似泄漏内存增加到两处,一处是349.3MB,泄漏对象类型是com.taobao.txc.common.config.ConsoleConfig,
还有一处是51.6MB,泄漏对象类型是java.lang.ref.Finalizer。

问题分析

从上面三次做的内存快照可以看出,主要泄漏的对象就是com.taobao.txc.common.config.ConsoleConfig,可以看出是在ConcurentHashMap大量保存了该对象,查看该对象引用关系如下

这个类是我们项目中引用分布式事务组件当中的类,年前有一次升级,现在想起来就是升级之后才出现了这个问题。我们马上联系了分布式事务gts的同学,把这个问题反馈给了他们,他们发现后也很重视,马上就开始排查,到了下午就已经出了一个临时的紧急修复jar包。我们也马上更换并且重新部署所有相关服务。

结论

本次的泄漏问题还算比较温和,没有立刻使服务挂掉,基本上是三四天逐渐加剧,最终导致内存占满,服务奔溃。这就给了我们比较充足的时间来排查分析问题。从这次也验证了jmap + mat分析线上服务的能力非常强大。

共15.7k字 阅读时长:4分
MaWenge's Blog

筋斗云架构演进

  |   5,893 字   |   20 min 发表于 2017-12-09 |

前言

筋斗云出行 主营共享电瓶车租赁。和我们看到的市面上绝大多数做共享出行的公司一样,业务上有很大的相似性,但是有些地方也完全不一样,随着运营时间的增长,产品形态和需求也在逐渐发生着变化,我想每家公司都会总结出自己的一套管理运营心得。这个过程中做过一些根本没怎么用的功能,但是目前留下来的功能可以说大多数都是最需要的。

软件应用服务于业务,业务是核心,当接到一个需求的时候一定要理解需求的本质,然后再规划实现(在小公司尤其要这样,提需求的人也许根本无法准确描述自己的需求,磨刀不误砍柴工,问清楚了,多沟通,确认好,然后想好了再开始动手)。我们对共享出行的认识也是一个逐步深入的过程,所以我们的架构也是在这个基础上不断进化的。

目前公司整个工程可以分为服务端后台、Android和iOS移动端、网页管理后台以及官网,就是服务端+移动端+web前端。其中移动端又包括用户端和运维端。

业务分析

做了这么久的共享租车,成功完成了数百万的租车订单,对整个业务应该也是有了基本的了解,我觉得可以分成如下几个方面:

  • 智能硬件
    主要负责车辆控制、定位以及与服务器通信,包括硬件设计以及硬件上软件编写
  • 智能设备连接服务
    主要负责管理所有车辆与服务端的连接(TCP连接),包括连接管理、数据解析、接收数据、发送指令
  • 车辆管理服务
    车辆信息的持久化以及缓存服务,包括车子位置、电量等各种状态信息的存储以及读取相关服务
  • 订单服务
    租车订单的相关服务
  • 充值服务
    负责用户通过支付宝和微信向账户充值的相关服务
  • 用户服务
    包括普通用户以及管理人员服务
  • 校区服务
    校区电子栅栏管理以及校区特殊配置等服务
  • 工单服务
    车辆维护任务管理,协助运维人员管理维护车辆的服务
  • 用户端服务
    用户端租车等所有相关服务,依赖其他服务
  • 运维端服务
    运维端管理校区车辆的相关服务,也依赖其他服务
  • 分析服务
    对车辆、订单等相关数据的统计分析服务

以上是服务端的主要业务。

第一代筋斗云服务端架构

第一代系统是典型的spring mvc架构。spring 开发web项目可以说效率非常高,其ioc管理bean以及切面技术为开发带来了极大的方便。其切面技术的实现核心原理可以参考前面的一篇文章动态代理

部署

服务是单进程单机服务,也就是说只要这台机器上的服务挂了,那么我们的业务也就挂了。可以水平扩容吗?理论上是可以的。因为我们凡是状态相关的内容(例如token 、session、业务临时缓存数据等)都是保存在redis中,所以即使同一个业务多次访问到不同的机器上也是没有问题的。但是,如果部署多台机器,定时任务必需管理好不要重复执行,可以通过分布式锁来达到要求,也不是什么问题。但是我们在第一代系统运行期间没有进行过多任务部署,原因主要是业务量没有那么多,完全没有必要部署那么多。

那么如果服务重启怎么才能做到用户无感知呢?
我们是这样实现的,买了一台负载均衡,买了两台ecs,平常用的时候只有一个机器上跑着服务。所有的流量也都是指向那一台机器上。如果进行服务重启或升级,就在另一台机器上把服务起起来,此时流量会同时指向这两台机器上,然后迅速把前面那个服务停掉,这时候所有的流量就全部指向新部署的这台机器上了。下一次部署也是同样的方法。也就是说每次服务重启的时候会有短暂的几秒是双机运行。那么这样子能够做到平滑升级吗?不完全可以,因为当你停用旧服务的时候,里面肯定会有正在进行的请求,直接停掉肯定会对客户端造成短暂的请求失败,不过这种情况只要重新请求依然可以得到正确的数据。其实可以调整负载均衡流量,把所有的流量指向新起来的服务之后再把旧服务停掉的,这样基本上可以做到用户无感知,就是稍微麻烦了一点。

物联网服务

在这一代系统中,我们的智能硬件是向一家专做智能中控的服务商采购的。在这些智能硬件当中,又分为两批,前面一批是硬件直连硬件服务商的服务器,然后我们这边的服务器与硬件服务商的服务器进行通信,后面一批是硬件直连我们这边的服务器,由我们这边直接与硬件通信。

对于连接硬件服务商的硬件,我们这边不需要了解具体的连接细节,只需要跟硬件服务商的服务器通信即可。
硬件服务商服务器负责数据的中转,不负责持久化数据,顶多缓存每个设备的最新信息。所有数据持久化全部在我们这边进行。
对于直连硬件服务商的硬件,我们不需要了解与硬件连接以及通信的细节,只需要与硬件服务商的服务器通信即可。对于主动型的指令,例如控制指令以及更新车辆状态信息等指令,我们是通过HTTP请求与硬件服务商服务器通信,对于周期性的车辆位置以及电量等信息更新,是通过硬件服务商发送消息队列来更新。对于车辆上报的警报信息,是我们这边提供HTTP接口供硬件服务商的服务器调用。使用消息队列更新周期性的信息后来在我们每个月的支出里面占了不小的份额。

对于直连我们服务器的硬件,操作起来也就没有那么麻烦了,不需要经过第三方转发。具体就是netty管理设备的tcp连接,然后就是发送与接收数据。

数据库

业务持久化数据库我们使用的是阿里的mysql数据库,对应使用的orm框架是hibernate。其他的数据库以前也没怎么用过,mysql对于一般的业务场景可以说完全够用了。而且对于前期的业务量也基本没有什么压力,只有在第二代系统快要上的时候才会时不时出现一些CPU报警,但是这里面也有很大的一部分是因为sql语句优化不够导致的。
hibernate+jpa框架开发效率也很高,开发起来也是比较方便的。但是对于一些多表联合查询以及一些复杂对象的映射,我认为hibernate支持的不是很好,当然条件查询hibernate做的也是比较到位的。所以后来我也就切换到了mybatis来操作数据库,当然还有同事接着用hibernate。

还有一部分数据是存储在阿里云的tablestore中,这是一种nosql数据库。据说可以支持超大体量的数据查询。我们主要存储的是车辆的历史轨迹信息,就是一些经纬度坐标。因为每个设备快则几秒钟上报一个位置,慢则五分钟上报一个位置,如果所有的这些数据数据全都存储在mysql中,很快mysql就会达到性能瓶颈。而且这些数据有一个特点,查询非常少,只有需要的时候才会去查,一般也不需要查询。所以tablestore是一个不错的选择。

redis主要用来做一些缓存,或者状态变量,例如token,session,发送短信记录等等。

终端应用

终端应用主要分为三大块。用户端移动app(Android+iOS),运维端移动app(Android)以及网页端管理后台。用户端主要负责用户租车服务,运维端主要负责校区运维人员管理当前管辖范围内的车辆。网页端主要用来查看数据以及配置数据。由于前期开发人员较少,运维端和网页端主要由我负责。

移动端app主要就是获取数据以及地图相关显示。我们的地图使用的是高德地图,高德地图的sdk用了之后感觉还是比较友好,基本上完全满足了我们的需求。

网页端使用了一个管理后台框架AdminLTE。这是一个基于bootstrap和jQuery编写的一个框架,里面封装的各种组件用起来可以说是非常的方便,并且也支持响应式布局。管理后台其实是就是查询数据,提交一些表单数据等。最初我们使用的是theamleaf模板引擎在服务端生成填充好数据的HTML界面,浏览器端渲染,jQuery在浏览器端发送ajax请求。但是后来随着后台各种界面列表开始展示查询,每次提交一个查询条件都得所有数据重新从服务端拉回,并且还要恢复上次请求的状态数据,使用jQuery+模板引擎实现这一套功能实在是太麻烦。所以当时又把大部分的常用查询列表的界面全部换成vue实现的了。Vue的数据绑定渲染给我们的开发效率带来了很大的提高,并且大致实现了前后端分离。由于管理后台是不需要SEO优化,所以使用Vue也不会带来任何负面的影响。

以上就是第一代筋斗云架构情况,虽说是单体应用,但是总体下来还是很可靠的,期间出过几次问题,但都不是架构问题导致的性能瓶颈,主要是业务逻辑方面的问题。这个应用一共跑了五个月,产生了二百多万条数据,可以说成功的支撑了公司的初期业务。

第二代筋斗云服务端架构

单体应用虽然没有什么技术含量,但是运行稳定,业务也逐渐成熟,为什么要升级架构呢?
首先,由于是第一次做共享租车行业,初期的功能设计都是模仿膜拜单车 ofo的模式设计的,但是随着运营的开始,逐渐发现我们自己的模式跟别人还是有很大的区别,也衍生出很多没有想到的需求。当然在老系统上逐渐迭代维护也是可以完成的,但是如果重构实现这些将会更加优雅。

其次,第一代系统当中,难免会因为没有经验写出一些效率低,不优雅的代码,这样的代码散落在整个工程的各个角落,必须要花时间把这样的代码逐渐优化。

还有,随着业务量的增大,开发维护整个项目不再是我们几个人可以完成的了,如果继续在这个单体应用上,也是可以的,就是管理起来不是很方便,任务划分也不是非常明确,业务耦合比较严重。

最后,追求技术的码农总是想尝试新的技术,这不最近微服务非常火,所以我们也就开始了微服务的调研。

推荐两篇我认为写的比较好的关于微服务的文章 重新理解微服务、 中小型互联网公司微服务实践-经验和教训。在做项目之前与做完项目之后读文章,感受也不同。可以说经过自己的实践再次读感触会更深。期间,我还参加了沪江举办的一次关于微服务的技术沙龙,当时没有什么感觉,如果现在再去听,我想感触会更加深刻。
技术调研之后,我们一致觉得可行,事实证明确实可行。

服务划分

服务划分粒度不能太大,也不能太小,强联系的业务放在一个服务当中。那么这又会出现一个问题,强联系业务放在一个服务当中,这个服务有可能不知不觉就会变大。我总结了几个服务划分心得:

  • 每一张数据表只能由一个服务访问
  • 一个服务开发周期大致在两人两周的工作量
  • 尽量避免出现使用分布式事务的场景
  • 按层次划分服务(分层设计)

新一代筋斗云服务划分如下:

  • 基础服务
    包括短信服务,缓存服务,消息服务,跟业务没有任何关系,相当于工具包。事实上我们没有所谓的基础服务层,因为这些基础服务我们使用的也是第三方服务,以阿里提供的服务为主。所以我们没有单独部署服务提供这些基础服务,而是把相关服务封装到一个个module当中,上层的服务直接在pom里面引入直接调用接口服务即可。后期如果业务量起来,需要自己实现这些基础服务,可以直接自己实现,然后在调用的地方把本地调用直接替换即可。由于前期设计的时候也都是接口调用,切换成远程服务调用也是非常方便的。
  • 共享服务
    这里面的服务有一个共同的特点:以提供服务为主,相互之间基本没有调用。这个也是我们服务划分要达到的目的。
    智能设备连接服务:负责服务端管理与硬件的tcp连接服务,向智能硬件发送指令,接收信息,以及相关解码。
    车辆管理服务:调用智能设备连接服务,管理车辆相关所有功能。
    订单服务:所有租车订单相关的服务。
    充值服务:所有用户进行微信,支付宝相关的充值,退款等服务。
    用户服务:所有用户相关的管理,包括外部用户和公司内部管理人员。
    校区服务:所有校区相关管理,例如电子栅栏,校区优惠活动,校区结算等等。
    工单服务:车辆维护的服务。
  • 业务服务:
    这里面的服务的共同点是:调用共享服务和基础服务,除了分析服务提供服务,其他三个都不提供服务,直接对外暴露业务。那么为什么把分析服务放在业务服务层呢?因为它也是强依赖各种共享服务和基础服务,属于业务服务内的低一层。
    用户租车服务:处理用户端app的所有业务流程。
    后台管理服务:处理浏览器端的管理平台业务流程。
    运维管理服务:处理移动运维管理平台的业务流程。与后台管理服务有部分业务重合,侧重点不同。

    服务实现

    既然决定使用微服务架构,而且服务也已经划分好,接着就确定微服务框架了。我们主要考虑了两个微服务框架:spring cloud和阿里云edas。
    最终我们选择了阿里云的edas。主要基于以下几个原因:
  • 使用简单,结合spring,服务提供方配置发布服务,服务消费方直接注入接口类即可,无需其他配置。其实RMI hessian等用起来也非常简单。
  • 大公司出品,文档比较全,应该不会有问题。毕竟是付费服务,毕竟是阿里出品,事实证明开发起来虽然遇到一些问题,大多数自己都解决了。
  • 省事,很多工作edas都包了,基本上每个人只要集中在开发自己的业务上面。
    首先,部署非常省事,使用edas控制台,部署只要上传应用,剩下的选项配置一下马上就发布,而且可以分批发布,回滚也是一键回滚,可以回滚到以前的任意版本。
    其次,服务调用安全性不用管,已经做好了,只需要在edas下添加ecs即可,然后只要在上面部署服务就行,剩下的不需要考虑。
    服务监控也可以不用做,有现成的,各种性能都有监控。
    rpc通信使用的是hsf,据说是dubbo的升级版,基于tcp,效率更高,具体不得知。
    edas原理有一篇文章讲的非常好。

服务九月底上线,目前趋于稳定,没有因为edas出现过宕机,可以说,稳定性没有问题。这里没有打广告,真心觉得。

数据库

数据库运行到后来出现的问题是有两个表逐渐变大,订单表和充值表。基本上订单表是充值表数据的五倍。其实历史订单数据查询需求比较少,比较热的数据都是最近几天生成的,可是随着表格数据的不断增大,插入和修改数据变得越来越慢。解决方法想到两个:1把旧数据存到其他地方,始终保持主业务使用的表格数据量控制在一个较小的范围内。2分库分表。两个方法都是可行的,第一个方法比较麻烦,最终选择了第二个方案。
最终使用阿里云的一个中间件–drds。这相当于在应用和mysql服务器中加了一层。拦截sql请求,根据sql请求的具体内容,如果有分库分表键,则把请求发送到指定的数据库实例上,然后把返回内容发送给应用。或者有些sql指令无法确定数据库实例或者请求需要在多个数据库实例上执行,那么就需要把请求发送到多个数据库上,然后再把请求回来的数据组装,返回给应用。总之,这些就是drds做的工作,而服务调用方,只需要在创建表格的时候指定好分库分表键,请求的时候带上分库分表键就行了。还有一点就是有些mysql自带的函数没法用了,这个也很好理解,因为你的请求有可能发送到好几个mysql上,有些参数必须保持一致,如果函数在多个mysql上执行,得到的就有可能不准确了。
所以,在使用了drds之后,数据库性能问题也解决了。

应用中的orm框架不再是单独使用hibernate,其中有很大一部分切换成了mybatis,这个也是根据开发人员的习惯选择的。这也是微服务的另一个好处,技术选型不用是规定哪个就是哪个,不同的服务可以选择不同的技术,只要给其他服务提供标准的接口即可。

再说说数据迁移方案,在迁移的时候,数据量总共有220多万条数据,我们的迁移方案是把老库上的数据表原封不动的复制到新库上,然后在新库上面建好新表,直接在控制台里面用sql语句把旧表当中的数据复制到新表里面。当时也想过用脚本迁移,但是速度太慢,利用sql迁移,整个过程控制在了一分钟之内。

终端应用

终端应用还是分为三大块,这个没有变化。值得一说的是我们的管理后台在一位大神的带领下进行了重构。新的框架是基于vue+element实现的,完全没有使用模板引擎,真正实现了前后端分离,并且前端代码部署在整个服务外的一台机器上作为静态资源访问。最重要的是前端也是完全面向对象的编程模式,可以说非常先进,不说了,还没消化完。

部署


每个服务启动后都会向注册中心注册服务,服务调用不会经过注册中心,服务之间直接调用。这也是去中心化的思想。
每个服务基本上部署两份,edas会自动把请求分配到两个服务中,而且部署的时候可以分批部署,这样就可以做到平滑部署。

总结

以上就是筋斗云最初的单体应用进化成微服务架构的整个过程。由于历史包袱比较小,整个系统升级也是非常顺利。服务器也从最初的两台扩展到现在的19台。以前如果要改个什么小东西整个应用都会受到影响,现在可以单独部署,局部部署,还可以分批部署,基本没有什么感知。而且整个应用也逐渐趋向稳定,横向扩容也就是增加节点部署应用即可。如果想要增加新的功能,不再向以前一样思前想后,可以在现有的应用上增加就在现有的应用上增加,如果不行,可以直接增加一个服务,非常方便。当然,微服务也会带来其他的一些影响,例如如果某个应用发生了变化,影响到其他服务,比如一个枚举类型新增了一项,如果跟其相关的服务没有部署,那么马上就会报错,客服电话就会马上反应出来。而且部署的顺序有时候也要注意。这些都是非常需要注意的地方。
微服务的理念可以说现在也有了一定的认识,我觉得系统变大,业务变多,服务拆分是未来的趋势,大型互联网在这方面走的也是比较前的,这也是值得我们未来探究的一个重要的方向。

共15.7k字 阅读时长:20分
MaWenge's Blog

软件分层设计思想

  |   2,361 字   |   8 min 发表于 2017-11-14 |

什么是分层

我所理解的分层,是对 实现某个功能的过程 进行分层。实现功能的过程是由 很多服务有序组合完成,把相似的服务放在一层当中,把不相似的服务分开,简而言之,就是 对服务进行分层。

分层的应用

分层的思想在各个领域可以说是随处可以看到

房屋建设

我认为可以分为这几层

这里的实现的 功能 就是 建好房子并且卖出去。 我把中间涉及到的服务简单的分成了三层。最上层是地产商经营服务,主要负责房屋销售、融资筹钱等服务;第二层是建筑承包服务,主要负责房屋的建设服务,它接受地产商的指示,最终提供给地产商符合要求的产品;第三层是原料供应商,它负责接受建筑承包商的指示,提供给建筑承包商符合要求的产品。
都是上层向下层索要特定需求的产品,下层根据需求提供给上层符合条件的产品,双方都不需要知道对方的实现细节。

网络传输模型

数据在一个网络中的两台机器之间传输,可以说数据就是在这些层当中不断的流动,最终达到了传输数据的目的。

首先,每一层实现的功能不同
应用层:OSI参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。
表示层:应用层数据的编码和转换功能。用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。
会话层:会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。
传输层:传输层建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。
网络层:本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP层。这一层就是我们经常说的IP协议层。IP协议是Internet的基础。
数据链路层:将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。
物理层:实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。

其次,实现每一层功能的对象是不同的

其中应用层(会话层、表示层、应用层)大多通过软件实现其功能。可以说每一层都有专业的对象负责实现本层的功能。

再其次,每一层遵守的协议也不同

每一层的工作都很明确,从上层接收数据(对于应用层直接传递给下层),根据协议处理或者传输数据,再把数据传递给下一层(对于物理层从上层接收数据传递后继续传递给上层)。每一层只负责依据协议读取数据,处理数据,传递数据。不需要了解上层或者下层的实现细节。如此只要每一层都做好自己的事情,最终就能达到数据传输的目的。
这里实现的功能就是完成数据传输,每一层都提供了服务,最终合起来就共同实现了功能。

安卓平台架构

安卓操作系统其实也是典型的分层架构

Linux内核:Android 平台的基础是 Linux 内核。例如,Android Runtime (ART) 依靠 Linux 内核来执行底层功能,例如线程和低层内存管理。
使用 Linux 内核可让 Android 利用主要安全功能,并且允许设备制造商为著名的内核开发硬件驱动程序。
硬件抽象层:硬件抽象层 (HAL) 提供标准界面,向更高级别的 Java API 框架显示设备硬件功能。HAL 包含多个库模块,其中每个模块都为特定类型的硬件组件实现一个界面,例如相机或蓝牙模块。当框架 API 要求访问设备硬件时,Android 系统将为该硬件组件加载库模块。
Android Runtime:对于运行 Android 5.0(API 级别 21)或更高版本的设备,每个应用都在其自己的进程中运行,并且有其自己的 Android Runtime (ART) 实例。ART 编写
原生 C/C++ 库:许多核心 Android 系统组件和服务(例如 ART 和 HAL)构建自原生代码,需要以 C 和 C++ 编写的原生库。Android 平台提供 Java 框架 API 以向应用显示其中部分原生库的功能。例如,您可以通过 Android 框架的 Java OpenGL API 访问 OpenGL ES,以支持在应用中绘制和操作 2D 和 3D 图形。
Java API 框架:您可通过以 Java 语言编写的 API 使用 Android OS 的整个功能集。这些 API 形成创建 Android 应用所需的构建块,它们可简化核心模块化系统组件和服务的重复使用。
系统应用:Android 随附一套用于电子邮件、短信、日历、互联网浏览和联系人等的核心应用。平台随附的应用与用户可以选择安装的应用一样,没有特殊状态。因此第三方应用可成为用户的默认网络浏览器、短信 Messenger 甚至默认键盘(有一些例外,例如系统的“设置”应用)。虽说官方把系统应用单独设置成一层,但是我们使用的各种app实际上应该是跟这些系统应用在同一层的位置,主要调用下一层的Java api框架。

其实其他操作系统(windows Linux)也是有明确的分层,绝大多数开发者编写的代码都是明确的在各自的层中运行,调用其它层或者给其它层调用

后端服务架构

这里介绍一下目前我自己后端服务的分层架构,也是参考了网络上一些通用的架构方案,没有什么技术含量。

database layer:数据库服务,用的是阿里云的rds,基本上就是配置一下就行了。
persistence layer:持久层,用于与数据库直接交互,主要就是增删改查,我用的是mybatis,其实也叫dao层,主要包括定义好的一些mapper的xml文件和接口文件。这里可以把功能抽象在接口里面,用以适配不同的数据库服务。
service layer:服务层,这部分应该是对持久层做了一些原子性更大一些的组合。我个人认为这一部分的思想可以跟关系型数据库的存储过程很相似。
business layer:业务层,这里涉及到的就是具体的业务了。比如现在的项目是共享电瓶车租赁。相关的业务就会有租还车,车辆管理等等。业务层可以直接调用持久层,也可以调用服务层。
presentation layer:展示层,负责处理所有的界面展示以及交互逻辑。目前的业务主要就是对移动端请求的处理,现在主要就是各种controller,后台静态页面部署在另一台机器上。

其他一些权限过滤、异常处理等这里暂时先不考虑。

spring框架下的这种架构实现起来也非常容易,代码看起来也很清爽,维护起来也没有什么很大的问题。

总结

不难发现,分层的思想在生活当中随处可见,分层真的就是一种正确实用的方案吗?这个也是要根据需求以及实际情况衡量。这里我们只讨论分层特点。

1 分层易于实现分工,每一层人员负责的不同,只需要专注自己的业务即可。
2 分层易于实现代码复用,这也是分层出现的一个直接原因。
3 分层易于测试。由于实现了分层,功能粒度理论上是可以做到很细,单元测试用例编写也会非常灵活。

参考
软件架构模式之分层架构
针对架构设计的几个痛点,我总结出的架构原则和模式
OSI七层模型与TCP/IP五层模型

共15.7k字 阅读时长:8分
MaWenge's Blog

设计模式----代理模式

  |   2,419 字   |   9 min 发表于 2017-11-07 |

代理模式

即Proxy Pattern,什么是代理模式呢?

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

来自 百度百科

所谓代理,就是调用对象的方法的时候没有直接调用该对象,而是生成一个代理对象,实际上是代理对象通过真实对象执行方法。

为什么要这样做呢?我们接着往下看

组成

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

抽象角色声明方法,不提供具体实现;真实角色实现或者继承了抽象角色,实现了里面的方法。代理角色代理真实对象(当然代理角色也是可以直接代理抽象角色的,这个后续具体分析)

静态代理

抽象角色

public interface User{
    void hello();
}

真实角色

public class UserImpl implements User{

    @Override
    public void hello() {
        System.out.println("大家好,我是小明");
    }
}

代理角色

public class UserProxy implements User{

    private User user;

    public UserProxy(User user) {
        this.user = user;
    }

    @Override
    public void hello() {
        long start = System.currentTimeMillis();
        System.out.println("start   :   " + start);
        user.hello();
        long end = System.currentTimeMillis();
        System.out.println("end   :   " + end);
    }
}

生成静态代理工厂类

public class UserFactory {
    public static User getInstance(){
        return new UserProxy(new UserImpl());
    }
}

使用

@Test
public void staticProxyTest(){
    User instance = UserFactory.getInstance();
    instance.hello();
}

结果



静态代理的优缺点:
优点:业务类只需要关注业务逻辑本身,保证了业务逻辑的重用性。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果接口里面的方法很多,就要在每一个方法里面进行代理。
2)如果接口里面新增了方法,那么所有的代理类和实现类都要跟着改动。

举例:代理可以对实现类进行统一管理,比如我们要查看一个方法的执行时间,就可以在代理类的实现方法中记录时间,而实现类的代码不需要做修改,这样就避免了修改具体的实现类。但是,如果每一个实现类都要实现这个功能的话,就需要添加多个代理类,以及代理类中凡是需要记录时间的方法都要实现统计时间的功能。
即 静态代理只能为特定接口服务,如果想要为多个接口服务则需要建立多个代理类

动态代理

根据上面的说法,静态代理的每一个代理类只能代理一个接口,如果被代理的类实现多个接口,那么,势必会产生多个代理类,这也是不可取的?动态代理的出现解决了这个问题

动态代理类的源码实在程序运行期间由jvm根据反射等机制动态生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的

Java动态代理有两种实现方式:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

jdk动态代理

//接口1
public interface Dog {
    void bark();
}

//接口2
public interface Fish {
    void swim();
}

//实现类
public class RealObject implements Dog, Fish {

    @Override
    public void bark() {
        System.out.println("汪汪汪");
    }

    @Override
    public void swim() {
        System.out.println("~~~~~~~~~~~~~~~  swim  ~~~~~~~~~~~~~~~");
    }
}

//InvocationHandler实现类
public class InvocationHandlerImpl implements InvocationHandler {
    private Object object;//被代理的真实类

    //构造方法
    public InvocationHandlerImpl(Object object) {
        this.object = object;
    }

    /**
     * @param proxy  被代理的对象
     * @param method 要调用的方法
     * @param args   方法调用时所传入的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始代理。。。。。。"); //方法被执行之前可以进行自定义操作
        Object invoke = method.invoke(object, args);//执行被代理类的方法(这一步如果没有必要的话是可以省略的,比如retrofit的做法)
        System.out.println("代理完了。。。。。。");//方法被执行之后也可以进行自定义操作
        return invoke;
    }
}

@Test
public void test1() {
    //真实对象
    Object realObject = new RealObject();
    //代理对象
    Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), realObject.getClass().getInterfaces(), new InvocationHandlerImpl(realObject));
    //代理对象执行方法
    //这里的代理对象只能强转成接口对象,而且只能强转成单个接口对象执行接口中所声明的方法
    ((Dog) proxy).bark();
    System.out.println();
    ((Fish) proxy).swim();
}

输出

接口实现类如果没有必要的话是可以不用实现的,InvocationHandler 当中也不用传入被代理的对象,invoke方法中也不用调用Object invoke = method.invoke(object, args);这句话,只需要在invoke中做其他事情即可,Android当红网络框架Retrofit就是这样操作的,后续会有文章讲解。

jdk动态代理说起来就是这几下,Proxy.newProxyInstance 生成代理对象,在invoke做操作,其他的其实也没有了。

cglib 动态代理

Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:

CGLIB的核心类:
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);
net.sf.cglib.proxy.MethodInterceptor接口是最通用的回调(callback)类型,它经常被基于代理的AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法
public Object intercept(Object object, java.lang.reflect.Method method,
Object[] args, MethodProxy proxy) throws Throwable;

第一个参数是代理对像,第二和第三个参数分别是拦截的方法和方法的参数。原来的方法可能通过使用java.lang.reflect.Method对象的一般反射调用,或者使用 net.sf.cglib.proxy.MethodProxy对象调用。net.sf.cglib.proxy.MethodProxy通常被首选使用,因为它更快。

//真实类
public static class Dog {
    public void bark() {
        System.out.println("汪汪汪~~~~~~~~~~~~~~~~~~~~~~");
    }
}

public static class  CglibProxy implements MethodInterceptor {
    private Object o;
    public CglibProxy(Object o) {
        this.o = o;
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before      " + methodProxy.getSuperName());
        Object invoke = methodProxy.invoke(this.o, objects);//代理类调用真实对象执行方法
        System.out.println("after      " + methodProxy.getSuperName());
        return invoke;
    }
}

public static void main(String[] args){
    Dog target = new Dog();
    CglibProxy proxy = new CglibProxy(target);
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(target.getClass());
    enhancer.setCallback(proxy);
    //生成代理类
    Dog dog = (Dog) enhancer.create();
    dog.bark();
}

执行结果

cglib实现了对实体类的动态代理,弥补了jdk动态代理只能对接口代理的缺陷
cglib的研究还是很不透彻,后续会有更深入的研究(Superclass has no null constructors but no arguments were given),也遇到了问题,后续研究spring aop的时候会做深入研究。
通过CGLIB实现AOP的浅析(顺便简单对比了一下JDK的动态代理)

动态代理小结

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务,实际中可以类似Spring AOP那样配置外围业务。 还可以根据方法的不同做不同的操作。

总结

代理的目的就是通过代理在核心主要任务执行的前后做一些其他操作,不影响核心业务代码,保证核心业务代码的纯净性。也有助于功能模块化,实现功能插拔式的操作,也就实现了解耦。例如访问Google,可以在访问之前做一个翻墙操作,然后再进行实质的网络请求。如果有一天不需要翻墙了,那么从项目中剥离翻墙代码也是很容易的。

参考
java静态代理和动态代理
JAVA学习篇–静态代理VS动态代理
Java动态代理的两种实现方法

共15.7k字 阅读时长:9分
MaWenge's Blog

新建文章标题

  |   0 字   |   1 min 发表于 2017-10-29 |
共15.7k字 阅读时长:1分
MaWenge's Blog

设计模式----单一职责原则

  |   1,460 字   |   5 min 发表于 2017-02-03 |

设计原则

面向对象的编程有很多的设计模式,例如单例模式,观察者模式,装饰者模式等等,这些模式适用于不同的场景,但是这些设计模式又遵循几个相同的原则,可以说这些原则是所有设计模式的基础

  1. 单一职责原则
  2. 里氏替换原则
  3. 依赖倒置原则
  4. 接口隔离原则
  5. 迪米特法则
  6. 开闭原则

如果能够很好地理解这些原则,对以后学习具体的设计模式将会有很大的帮助,所以这里我也结合所看的书籍并结合自己的理解深入剖析每种原则的含义。如果发现有不准确的地方,欢迎留言交流,共同进步。

单一职责原则

单一职责的英文名称是Single Responsibility Principle,简称SRP。SRP原话解释如下:

There should never be more than one reason for a class to change
应该有且只有一个原因引起类的变更

其实单一职责这句话本身就已经说得很清楚了,就是一个类只负责一件事,不负责其他的事情。而能够引起这个类变化的原因也是只有这个类负责的事情。

这里举一个例子,也是书上的例子:
打电话需要四个过程,拨号、通话、回应、挂机,现在写一个接口如下

这三个方法都是电话的功能,把他们全都放在IPhone的接口中也是非常合理的。而且它就负责一件事,电话的事情,但只有一个原因引起变化吗?好像不是
IPhone这个接口其实有两个职责:一是协议管理,二是数据传送。dial() hangup()实现的是协议管理,分别负责拨号接通和挂机;chat()实现的是数据的传送,把模拟信号转换成数字信号等等。协议的变化和数据传送的变化都能够引起接口或实现类的变化。那么协议控制和数据传送之间相互依赖吗?电话拨号,我们只要接通就成,甭管是电信还是网通的协议;电话接通后我们也不关心传递的是什么数据。通过这样的分析我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不会相互影响,那就考虑拆分成两个接口

那么这么做到底能有什么好处呢,请看下面:

  1. 我们使用的是面向接口的编程,所以暴露给使用者的是两个接口类型IConnectionManager和IDataTransfer。使用者只能调用接口中声明的方法。面向接口编程实现了方法的隔离,使得使用者只需要使用方法即可,不需要知道具体实现,并且只能使用该接口的方法。所以在业务层面与协议有关的就是IConnectionManager,而与数据传输相关的就是IDataTransfer,到时候如果修改了接口里面的内容,业务调用层需要修改的内容范围也会减小(例如如果修改了数据传输IDataTransfer,那么业务层只需要修改IDataTransfer有关的内容,IConnectionManager相关的内容则完全不用修改,找起来也方便一点,如果没有拆分成两个接口,那么就要找到所有IPhone接口使用的地方一一检查)

  2. 这里的实现类Phone仍然实现了打电话有关的所有功能,只不过对于不同的使用情况通过接口类型暴露不同的方法给调用者。那么有人肯定要问为什么不定义两个实现类分别实现IConnectionManager和IDataManager,然后分别把两个实现类的对象传递给接口对象。这里IConnectionManager和IDataManager的实现方法如果要共享数据的话,那么让Phone类同时实现IConnectionManager和IDataManager接口,这样就可以很方便的在类内部维护共享数据以及状态,而通过接口对外暴露功能方法,类的结构也很清晰。还有一种就是Phone类组合IConnectionManager和IDataManager的实现类,但是组合是一种强耦合关系,而且还增加了类的复杂性,多了两个类,还不如直接让Phone类实现IConnectionManager和IDataManager接口。

单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。

上面的例子从功能上来说,定义一个IPhone接口来说也是没有错的,实现了电话的功能,而且设计还很简单,仅仅定义一个接口和一个实现类。但是确实是有两个可以变化的原因放到了一个接口里面,这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话,我们提供的接口IPhone是不是要修改了?接口修改对其他的Invoker类是不是有很大的影响?

单一职责适用于接口、类,同时也适用于方法。一个方法尽量做一件事。我相信大多数人编程的时候突然发现接下来要写的一段代码以前好像写过,然后回去把那段代码又提取出来变成一个单独的方法。这就时前面设计方法的时候做了太多事情,粒度太大了,相信大多数人多方法的单一职责还是比较容易理解的。

共15.7k字 阅读时长:5分
MaWenge's Blog

android ptp 编程(1)

  |   1,923 字   |   8 min 发表于 2017-01-07 |

最近公司用到一个项目,android手机通过OTG与单反相机实现连接,实现相机中有新增图片的时候自动保存到手机当中,然后手机自动上传到自己的服务器上。后面一步是比较常规的操作,一般都可以实现,主要是前一步。这个系列将介绍我如何一步步实现这些功能以及踩到的一些坑(文中很多链接需要自备梯子)

以下介绍相关概念

PTP协议(picture transfer protocol)

Picture Transfer Protocol (PTP) is a protocol developed by the International Imaging Industry Association to allow the transfer of images from digital cameras to computers and other peripheral devices without the need of additional device drivers. The protocol has been standardised as ISO 15740.

It is further standardized for USB by the USB Implementers Forum as the still image capture device class. USB is the default network transport media for PTP devices. USB PTP is a common alternative to the USB mass-storage device class (USB MSC), as a digital camera connection protocol. Some cameras support both modes.

这是维基百科对ptp的描述。可见主要是数码相机与外部设备通信的一种协议。

PTP是最早由柯达与微软协商制定的一种标准,符合这种标准的图像设备在接入Windows XP系统之后可以更好地被系统和应用程序所共享,尤其在网络传输方面,系统可以直接访问这些设备用于建立网络相册时图片的上传、网上聊天时图片的传送等。

MTP媒体传输协议(Media Transfer Protocol,MTP)

媒体传输协议(Media Transfer Protocol,MTP)是一个基于图片传输协议(Picture Transfer Protocol,PTP)的自定义扩展协议。该协议允许用户在移动设备上线性访问媒体文件。PTP只是被设计用于从数码相机下载照片,而MTP可以支持数字音频播放器上的音乐文件和便携式媒体播放器上的媒体文件,以及个人数字助理的个人信息的传输。MTP是WMDRM10-PD的一个关键部分,而WMDRM10-PD是Windows Media的一项数字版权管理(DRM)服务。

媒体传输协议(即通常所说的MTP)是“Windows Media”框架的一部分,从而与Windows Media Player密切相关。Windows系统从Windows XP SP2开始支持MTP。Windows XP需要安装Windows Media Player 10或更高版本来获得对MTP的支持。在这之后的系统则原生支持MTP。微软同时向Windows98之后的旧有操作系统提供MTP驱动包。OS X 和 Linux 各自拥有可支持MTP的升级软件包. (维基百科)

mtp明显是ptp的升级版,从仅传输图片文件到传输媒体文件,包括音频视频文件。对于ptp协议和mtp协议的具体文件我也是都有看过一遍,mtp当中很多指令等都于ptp完全相同,在我看来就是新增了一些东西,新增的部分我也没有多看,项目进度原因,也没有深究。

PIMA15740:2000

文档下载
那么这个pima 15740又是什么呢

The technical content of this PIMA standard is closely related to ISO 15740, which is currently
in the working draft stage while work on multiple transports is being completed. The main difference is that PIMA 15740 ncludes an informative annex describing a USB implementation of ISO 15740. This information is not included in ISO 15740, hich instead references the USB still device class document developed by the Device Working Group of the USB Implementers Forum. The USB annex in PIMA 15740 provides the same technical approach as the USB still device class specification. The eason for developing PIMA 15740 is to immediately provide a complete, fully documented, stable, publicly available pecification or USB implementations of the Picture Transfer Protocol defined in ISO 15740. This will enable hardware and software nufacturers to immediately produce product implementations, without waiting for the ISO and USB documents to complete the approval process. This PIMA 15740 standard may be withdrawn once the ISO 15740 and USB still device class documents have een approved and are publicly available.

上面这段话清楚地写出,pima15740 是usb实现的pima15740,就是为了提供一个完整的,稳定的,文档化的基于usb通信协议的iso15740,也就是基于usb的ptp协议。看到这里大家应该差不多明白了,我的需求基本上就是这个了。

实现分析

android.hardware.usb

首先查看安卓官方文档会发现有一个android.hardware.usb这样一个包,安卓本身提供了这样一套api用于获取连接在android手机上的usb设备,并且进行相应的通信。连接usb设备一共有两种模式,一种Host模式,一种Accessary模式,前者相当于android手机是老大,连接的设备是奴隶,手机可以进行任何操作;后一种是相当于连接的设备是老大,android手机是奴隶。具体可以看看这篇文章,百度也可以搜出来一大堆文章,基本上学习这个结合官方文档一般人都不会有问题,只要静下心来都不会有问题。

android.hardware.usb
不管如何跟相机通信,必须首先拿到连接到手机上的UsbDevice对象,才能进行下一步的操作。

android.mtp

接着会发现还有一个android.mtp的包,这个也是android自身实现的一个跟mtp设备通信的api。用法也十分简单,文档上面介绍的也是非常的详细,这里我简单介绍一下:
android.hardware.usb

1、获取UsbDevice设备 和UsbDeviceConnection,获取看上一小节的链接
2、获取MtpDevice对象然后建立连接

1
2
MtpDevice mtpDevice = new MtpDevice(mUsbDevice);
mtpDevice.open(mDeviceConnection)

3、然后就可以用MtpDevice对象获取各种有用的信息,MtpDevice的方法也不多,直接点进去就可以看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
close()
deleteObject(int objectHandle)
getDeviceId()
getDeviceInfo()
getDeviceName()
getObject(int objectHandle, int objectSize)
getObjectHandles(int storageId, int format, int objectHandle)
getObjectInfo(int objectHandle)
getParent(int objectHandle)
getStorageId(int objectHandle)
getStorageIds()
getStorageInfo(int)
getStorageInfo(int storageId)
getThumbnail(int objectHandle)
importFile(int objectHandle, String destPath)
importFile(int objectHandle, ParcelFileDescriptor descriptor)
open(UsbDeviceConnection connection)
readEvent(CancellationSignal signal)
sendObject(int objectHandle, long size, ParcelFileDescriptor descriptor)
sendObjectInfo(MtpObjectInfo info)
.
.
.

是不是很方便,看方法的字面意思基本上就知道用法了,用起来真的没难度,so easy,而且这里面的好多方法都是我需要的,你以为到这里就能解决我的问题了吗,一开始我也是这样人为的,后来才发现还是太年轻。。。

mtp这个包其实就是google自身基于mtp和usb通信协议实现的,具体实现还是调用native方法实现的,所以我还看不到每个方法的具体实现细节。我用这一套api确实可以读取到相机中的所有文件,并且可以手动复制相机中的图片到手机中(注意是手动,和我的需求还有一些不同)。我需要的是自动复制,大家肯定看到了还有一个

1
readEvent(CancellationSignal signal)

这个方法可以接收相机的事件,当有新的图片的时候就会发送一个事件编号位ObjectAdded的事件,然后我就可以主动拉取这个照片了,是不是很完美。可是我用的时候又发现只有api24才能用。。。所以这个方案也不行了。除了这一点,还有一个致命的问题,那就是我有些手机通过这种方式连接到相机之后相机被锁定了,不能继续拍照了,关于这个问题我还和阿拉神农,做了简单的交流,虽然没有解决问题,但是还是感谢他。这个问题后来我大致明白了,应该是不同手机在连接相机的时候对相机的mode进行了设置,所以会导致相机不能工作。

结语

通过这些坑,我明白了使用android现成的mtp包里面的接口暂时是无法实现我的需求了,一切有都得从长计议了。

共15.7k字 阅读时长:8分
MaWenge's Blog

写给一阳的

  |   19 字   |   1 min 发表于 2016-12-26 |

我们是在2016.12.12在我科上海校友会上认识的

共15.7k字 阅读时长:1分
Turbo

Turbo

记录我的想写下的东西

9 日志
3 标签
GitHub
© 2018 Turbo
由 Hexo 强力驱动
主题 - NexT.Mist