前言
这是我自己做的一个仿滴滴打车的Android出行项目,主要针对滴滴等出行平台一直饱受质疑的“人车不符”问题,以及当前越发火热的国际化和出海战略,给出行项目增加了下面几个功能:
1. RFID识别验证功能:在司机证件或者车内识别硬件里嵌入RFID识别芯片,乘客使用手机读取到芯片信息,并且通过网络(okhttp3)发送到出行平台数据库进行验证(我用NDK加了一个C语言的MD5加密算法对识别到的信息进行了加密)。如果不是合规的“人”或“车”,则不能完成订单并向平台或监管单位汇报当前位置。(为了方便读者测试,可以使用手机读取任何一个加密或非加密RFID芯片,比如银行卡、公交卡等,我在代码中的验证前阶段把芯片信息都换成我自己的司机信息,确保读者测试时可以收到服务器的回复)
2. 海外版功能:点击切换当前语言。
3. 司机证件号码识别功能:读取司机证件上的证件号码,也可以用来与出行平台数据库的信息进行核实比对。
项目源码地址:https://github.com/18601949127
项目代码都是一行一行自己敲的,在多部手机上调试过确保各项功能能够顺畅运行。GitHub的源码中保留了所有的手机CPU指令集架构,保证在所有手机上能够运行成功。觉得包太大的同学可以自己把不需要的 .so 指令集删掉,主要是做识别的 OpenCV4Android.so 包比较大,其次是百度地图的包。
地图我使用的是百度地图LBS 版本5.3,海外的话考虑到信息数据多少、性能、包大小、数据源等多方面因素推荐使用mapbox。 感兴趣的读者可以看Trinea 的这篇文章:
https://blog.csdn.net/weixin_37734988/article/details/92852349
文章发布以后得到《滴滴国际化项目 Android 端演进》作者滴滴公司技术专家Trinea @trinea 的提点, 告诉我要特别根据海外版的应用场景认真分析国外几款 Map 服务的各项优劣,比较Google地图、 mapbox 、Nutiteq 等,非常感谢。我后面会单独写几篇关于 mapbox使用的文章并且分享出来
读者如果想到滴滴出行或者其他平台比较实用的功能可以留言或者微信给我(微信:18601949127),我会抽时间把好的 idea 或者功能继续添加到项目里。
开发环境
1. Android端: Android Studio 版本3.4, 百度地图LBS 版本5.3 , OpenCV4Android 版本3.2
2. 服务器端: Apache + PHP + MySQL 用的是我自己租的腾讯云主机做服务器,我会一直开放出这个项目的接口,接受并处理读者发来的测试请求。
主界面概览
界面最上面TitleBar 的位置是主要的功能区,除了中间的醒目logo,两侧分布主要功能选项,最左边的SlidingMenu提供侧滑菜单,给乘客个人信息和软件设置提供入口,右边的证件标志按钮用于导向司机证件号码识别功能,再右边的英语标志按钮是国际化语言切换,最右边的无线标志是RFID识别认证功能的入口。
主界面的中间部分是地图区域,可以在上边选择不同交通工具,用于展示乘客所在位置,附近车辆或者POI热点,以及路径规划。
主界面的下方可以提供上划菜单,主要用于上车和目的地地址关键字输入,以及安全提示信息或者广告的入口。
项目文件结构
首先介绍一下项目文件结构,方便读者阅读代码:
包名:com.tantuo.didicar
-
Activity 文件夹:有的Activity 相对独立,并不属于某个功能模块,可以放到这个文件夹。
-
adapter 文件夹:相对复杂一点的adapter会从类文件中取出单独保存到 adapter文件夹,比如左侧侧滑菜单中 recycler view的adapter。简单一点的adapter还是会保存在调用的类中。
-
Bean 文件夹: 存放Entity 实体类,比如司机的相关信息会包装成一个DriverBean,每个司机都是一个类对象,使用Gson 传递很方便,用的时候get,set 就可以。
-
DriverLicenseNFC 文件夹:RFID识别验证模块,乘客使用这个功能模块验证司机身份或者车辆信息。
-
DriverLicenseRecognition 文件夹:司机证件号码OCR识别的功能模块。
-
splash 文件夹:app 初始化和引导界面。
-
TabFragment:主界面上方的滑动主题条用来切换交通工具或者服务项目(Tab),不同的交通工具或者服务项目代码都保存在TabFragment 文件夹里。
-
utils 文件夹: 用来保存项目用到的各种工具类,比如DriverRouteOverlay 用来在地图上渲染规划出来的驾车路线,MD5JniUtils 用NDK调用MD5加密算法,保护RFID芯片信息,NfcUtils 用来管理手机的NFC功能,POIOverlay 用来在地图上渲染周围兴趣点(POI)。
把工具类从Activity 或者 Fragment 中extract 出来放到统一的utils 文件夹,会让你的代码更清晰,可读性更强。
引导界面
先看下真机上的效果:
引导界面最初的logo动画是用我自己用SVG矢量动画做的,路径规划描述在 drawable 的splash_logo.xml 文件里:
还需要资源文件里的animator文件夹下的didi_logo_animator.xml 对路径进行动画描述:
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:propertyName="trimPathEnd"
android:repeatCount="0"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"/>
这几秒的时间里可以在下图的位置添加一些初始化代码,比如网络请求,得到后续Activity的素材,地理位置等等。
出行界面
通过滑动地图界面上方的主题可以切换不同的项目界面。
滑动主题条是一个VIewPager的 Indicator,每一个主题对应一个下面的服务项目,放在各自独立的VIewPager里。每个服务项目有各自独立的上划菜单,作为此服务对应的地址关键字输入或者相关信息入口。
出行界面的UI结构:
注意:乘客的位置信息、当前经纬度、当前街道名字、楼宇名字都是在MainAcitivity做为静态成员变量定义的,原因是在别的Acitivity或者类中,这些变量需要经常使用,直接调用 MainActivity.CurrentLocation就可以了,后面用到的所有当前位置,都是在MainActivity中 MyLocationListener 类得到的。
上车地址和目的地址的路线规划
不同交通工具(快车,出租车,单车,公交车等等)对应的服务项目都嵌在TItleBar下边的 VIewpager里,一个服务项目对应一个独立的Fragmen文件,由其顶部的的VIewpagerIndicator滑动切换。
服务项目的主要代码在com.tantuo.didicar 包下 TabFragment 文件夹里。
底部上滑动菜单
buttonsheet是在布局文件中加入android.support.v4.widget.NestedScrollView类的 app:layout_behavior="@string/bottom_sheet_behavior"
左侧侧滑菜单
左侧侧滑菜单可以作为个人信息、安全提示、设置信息的入口
司机证件的号码OCR识别功能
证件号码识别功能的主要代码在com.tantuo.didicar 包下的 DriverLicenseRecognition 模块。
还是先看下真机效果:
点击进入司机证件号码识别功能以后,可以选择对证件拍照,为了方便演示,这里是从手机相册选择刚刚拍的照片。同时为了方便读者测试这个功能,我把照片保存在了开发包的asset文件夹里面,这样读者下载我保存在GIthub上https://github.com/18601949127 的版本,点击选择司机证件以后调用的是我保存在assets 文件夹里的司机证件照片,也就是下面图片里的 getDriverLicenseFromMySample() 方法,可以立刻进行测试。想继续从手机相册读取的读者可以执行LicenseMainActivity 下的 LicenseMainActivity 方法。
注意:在程序中,想要在运行中读取司机证件照片,要把照片保存在assets 文件夹下面,使用AssetManager 类读取,而不能试图调取drawable 文件夹下面的照片,因为 \res 文件夹下的资源文件都会被编译到apk里面去,并同时赋予资源 id。感兴趣的同学可以看下代码里面的 copyFilesFassets()方法。
这里用我以前在国外读书时候的证件作为例子:
- 首先:要从照片中找到司机证件区域,也就是上证件边缘红色的区域
/**
* 找到图像中的证件区域
* 在RGB色彩空间求取驾驶员证件的图像梯度,之后在此图像上做二值化,从而通过轮廓(contour)发现与面积大小过滤得到证件区域
* author:Tantuo 86-1860194917
* @param fileUri
* @return
*/
public Mat findLicenseContour(Uri fileUri) {
//首先使用openCV 的 Imgcodecs类得到相机获取的证件图片
Mat src = Imgcodecs.imread(fileUri.getPath());
if (src.empty()) {
return null;
}
//得到证件照片的x梯度和y梯度
Mat grad_x = new Mat();
Mat grad_y = new Mat();
Mat abs_grad_x = new Mat();
Mat abs_grad_y = new Mat();
//注意求梯度的时候我们使用的是Scharr算法,sofia算法容易收到图像细节的干扰
//所谓梯度运算就是对图像中的像素点进行就导数运算,从而得到相邻两个像素点的差异值 by:Tantuo
Imgproc.Scharr