暑假期间自己写了一个 APP ,一直想找个时间来总结下这次经历,正值国庆假期,于是静下心来好好总结下自己的收获!
1.开发前
1.1 模块功能的划分
APP 在开发前,首先要确定 APP 的模块、功能,画出 APP 的功能结构图,然后团队之间划分相应的工作。之后就要开始设计产品原型图,根据原型图进行软件界面的设计。但是由于画原型图,需要利用 Axure 或其它专业工具进行,同时又需要一定的设计功底,最终限于时间的原因,这步只能作废。在后期开发过程中,先用画图工具画出布局的草图,然后再具体设计细节,其中也参考了一些其它 APP 的界面设。
1.2 框架及 SDK 的选择
-
框架的选择
了解到的 Android 开发框架有 MVC 、MVP 等,由于 MVC 框架从 Web 开发中沿承下来的,对于 Android 开发而言,会使得 Activity 过于臃肿,于是便采用了 MVP 框架。由于之前练手的项目一直采用 MVC 框架,当确定了暑假这个项目采用 MVP 框架之后,便在正式开工之前查阅了网上相关的资料,并看了一些小 Demo 的源码,对 MVP 框架整体也算有了大概的了解。
-
地图 SDK 的选择
由于项目中需要使用地图服务,而国内比较好的地图服务商有百度地图、高德地图。在比较了两者的定位精度之后,最终选择了百度地图的,但是在开发过程中才知道百度地图的 API 的坑!不过好在使用的人比较多,遇到问题,可以看看前人的解决方案。
-
云端服务器的选择
目前提供移动应用后端云的公司有国内的 Bmob、LeanCloud 以及国外的 Parse Server,最终选择了 Bmob 的服务。
2. 开发中
2.1 开源框架的使用
在开发过程中,为了避免重复造轮子,同时提高开发效率,在所难免的会使用到一些开源的框架,在这里非常感谢那些为致力于开源项目的同仁。 项目中使用到的几个常用开源项目有:
-
Glide
确实是一款功能强大的图片加载库,利用它加载图片,非常方便,同时可以一定程度避免因显示图片导致的 OOM。
-
XUtils
功能很多,但在此项目中,主要是利用其下载文件
-
Gson
对 Json 格式进行转换
-
Android-PickerView
一个选择的控件,可以用来选择时间、三级联动选择地点,或者可以自定义选择的条目数据。
-
Leakcanary
内存泄露检查的开源框架,主要是为了优化应用的性能。初次使用,检查出了一些内存方面的问题,也让自己对 Android 开发过程中的内存的管理有了一些了解。
-
Slidinguppanel
一个可以向上滑动的组件,为了让 APP 的交互更加友好而采用了此框架。
-
代码家的 Slider
一个用来展示图片的轮播条。本来是自己亲自写了一个,但是由于内存泄露以及样式比较简陋,最终选择了此开源组件。该组件样式、动画比较多,但是也存在内存泄露问题,好在已经有人反馈并给出了解决方案。
2.2 开发时遇到的坑
2.2.1 百度地图
-
地图 Marker 的图标设置问题
由于百度地图的 Marker 的图标不支持设置在线图片(出于网络请求比较耗时方面的考虑),而在此项目中,图标的图片需要从服务器中下载并动态的设定,因此必须想办法让其能够设置在线图片。
-
第一种思路
将图片全部离线保存到本地然后设置,这种方式会将导致 APP 的 size 剧增,且不利于后期的维护。
-
第二种思路
将一张图片从服务器中下载好之后,再进行设置。这种方法可行,于是在百度、谷歌上搜索相关的资料。经过两天的努力,终于找到方法解决了这个问题。解决方案就是利用 Glide 框架下载网络图片,然后当图片下载完成之后在回调的方法中再将下载好的图片设置给百度地图的Marker。当设置完所有的 Marker 的图标之后,通过 Handler 发送消息通知图标设置完成,进行后续的操作。Glide 的用法也是在解决这个问题的过程中才学会和熟悉的。
关键代码如下:
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
Glide.with(mContext) .load(url) .asBitmap() .skipMemoryCache(false) // 设置是否开启内存缓存,默认开启,若关闭,则传入true .error(R.drawable.default_image) // 设置图片加载失败时的图片 .diskCacheStrategy(DiskCacheStrategy.RESULT) // 设置硬盘缓存策略为转换后的图片(默认) .override(50, 50) // 将图片大小设置为50, 50 .into(new SimpleTarget<Bitmap>(50, 50) { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { // 找到view上对应的控件,并设置数据 tv_attraction_name.setText(marker.title); // 设置标题 rv_thumbnail.setImageBitmap(resource); // 设置 Marker 的图标 // 根据 View 创建BitmapDescriptor BitmapDescriptor descriptor = BitmapDescriptorFactory.fromView(view); LatLng latLng = new LatLng(marker.position.getLatitude(), marker.position.getLongitude()); MyMarkerItem markerItem = new MyMarkerItem(latLng, descriptor); // 将MarkerItem添加到集合中 mMarkerItems.add(markerItem); // 判断是否已经下载完所有图片 if (finalI == list.size() - 1) { mHandler.sendEmptyMessage(100); // 下载完所有图片之后,通过handler发送消息通知添加图片 } } });
-
-
大量 Marker 的展示问题
在解决了 Marker 的图标设置在线图片的问题之后,我发现当加载较多的图标时,内存的占用率比较高。我猜测,若后期再继续添加 Marker 时可能会导致 OOM ,为了验证自己的猜测,我便利用循环添加了大量的 Marker 。果然,最终应用因 OOM 而闪退。
遇到这个问题之后,查询了一天的资料之后,才有了一定的思路和方法。这才知道,百度地图在 JavaScript API 中提供了海量点的显示,而 Android 版本中并没有海量点的功能(高德地图 Android SDK 支持,当时就有点后悔之前没有选择高德了)。对于需要展示大量的 Marker 的这种情况,百度地图给出的解决方案是使用点聚合(当然,高德也不甘示弱,也有此功能,看了下双方的源码,貌似高德的代码量少很多)。
在找到了方向之后,于是就开始向前走。最开始,我想到的是先去百度地图的文档中看下点聚合相关的 API,但是没想到的是,根本没有。在网上一查,才知道这部分功能,百度并没有打包到 jar 包中,是直接以 java 文件的形式放在 Demo 中。打开 Demo 项目,直接定位到给出的点聚合功能相关的示例。大概了解了用法之后,便开始动手实践了。
刚开始,我直接在 AS 中将点聚合的代码文件夹拷贝到自己的项目路径下。由于 AS 太过智能了,自己改了包名,导致最后点聚合的代码中出现一片红。最后,我还是直接关闭 AS,在资源管理器中找到项目的路径,将代码复制过去(这种方式比前面的那种速度要更快)。这样做,虽然包名一致了, error 少了很多,但是没想到还有红色的警告,这真是让人不省心!只能再找错误位置修改。
经过慢慢地摸索,掌握了点聚合功能的用法,终于在项目中添加了点聚合的功能。加了点聚合之后,显示的 Marker 数量可以增加,但是当用户把 Marker 都展示时,依然会有 OOM 的风险。原因是展开后图标依然保留在地图上,依然占用着内存。 所以,为了避免这个风险,必须要回收一部分内存,采取的方案是:每次只展示屏幕范围内的 Marker ,一旦用户移动了地图,不在屏幕范围内的 Marker 全部回收。
经过上面的处理后,内存使用率减小很多,基本上避免了 OOM 的风险。但是经过点聚合之后,比较密集的 Marker 会聚合成一个点,其图标样却式改变了(显示的是聚合的 Marker 的数量)。虽然,这样做可以增加展示的 Marker 数量,但是样式对用户而言太不友好了。于是又开始想办法修改聚合的图标样式。在修改图标样式,查阅了网上的相关资料之后,发现可以将点聚合后的图标自定义成自己的图片,但是他们实现的效果依然不符合我的要求。这样做,虽然对图标的样式进行了自定义,但是所有的聚合点的图标全部一样,而我想要的效果是聚合点的图标是从聚合前的图标中选择一张来展示,从而每个聚合点图标都不一样。
为了实现这个效果,不断地查阅资料,同时自己也去查看了百度地图点聚合这部分的代码,最终找到了方法。即在正式点聚合之前,从需要聚合的点中选择一个出来,保存其图标样式,然后在正式进行点聚合时,将此图标样式设置给聚合点,从而聚合点的图标来自于聚合的点。同时为了对用户更加友好,也对点聚合增加了附加的功能,当用户点击聚合点之后自动将聚合的点全部分散并展示到屏幕上。
这是我在使用百度地图整个过程中遇到的最棘手的问题,连续花了几天的时间查阅资料,最终攻克下来了。由于涉及到的代码较多,在这里就不贴代码了,以后有机会,单独写一篇博客记录详细的解决方法。
-
自动提醒问题
在本项目中,需要使用到定时提醒功能,本来想直接使用百度地图封装的 API ,但是发现其功能存在一定的 bug,设置提醒的最短距离与实际不符合。网上也早有人反馈此问题,但是百度貌似并没有正视,最后只能自己动手换一种方式实现提醒的功能。
-
小插曲
在项目快要结束的时候,我偶然逛百度地图的社区论坛,发现 SDK 推出了新版本,于是便在项目中采用了新版本的 SDK 。没想到一采用新版本的 SDK 之后,一旦用户执行了某一操作之后,程序便闪退,向百度反馈了这个 bug 之后,百度那边也承认存在问题,表示尽快修复(但是最终新版的发布时间比他们承诺的时间足足晚了一个星期,不过更加悲催的事情是新版修复了旧 bug ,又引发了新的 bug,论坛里面骂声一片,不过好在我备份了旧版 SDK ,直接换回去即可)。通过这个事情表明,在实际开发过程中,不要急于求新,要等待新产品经过一定的时间检验,趋于稳定后再尝试使用,同时在更新之前要备份好旧版的数据,以防万一。
2.2.2 MVP 导致的内存泄露
在选择了 MVP 框架之后,在开发过程中也确实感受到了它的好处,将 Activity 与业务逻辑分理,Activity 只负责界面及数据的展示,主要的业务逻辑交给 Presenter ,大大简化了 Activity。但是没想到的是,利用 Leakcanary 检查之后,发现 MVP 如果使用不得当的话,会造成内存泄露。
在 MVP 中, View 与 Model 之间分离了, 而 Presenter 成为了两者之间的桥梁。当 View 需要显示数据时,通知 Presenter 让其获得数据,Presenter 在获得 Model 层的数据之后再 返回给 View 显示。在这个 过程中, Presenter 持有了 View 的引用, 如果直接使用 View 的强引用,会导致当 Activity 退出时而不能释放内存。为了解决这个问题,可以将 Presenter 持有的 View 引用更改成 弱引用。
2.3 界面布局及交互的设计
这一部分本应该在开发前通过 Axure 画好产品原型图确定好的,因为这步工作由于时间的原因,在当时就没有做了,只好放在开发的过程中进行。在每创建一个 Activity 时,就要先用画图画好布局,然后再仔细想布局中的细节问题并参考其它的 APP 界面和网上的一些作品的设计。同时为了更加的美观,还要考虑到颜色的搭配,尽量让颜色更加的协调。在这一个过程中也收藏了一些不错的设计相关的网站,如UI 设计师导航 ,配色网,Iconfont。
在产品的交互设计方面,尽量让用户操作起来更加方便、简单、友好。比如当用户在主界面点击返回键时,提醒用户是否真的要退出(避免用户误操作退出程序);当用户删除某项记录时,提醒用户是否确定要删除;当用户进行某项操作失败时,提醒用户。还有一些更加细节的地方,比如说 Toast 的设置,应该优化 Toast消息的提示,若直接用默认的 Toast ,可能会出现不断重复提示一个消息的情况,对用户来说体验并不友好;还有如应用启动时,会出现短暂的白色界面,一些大公司对于这种情况,会进行优化,用自己产品的图片替换掉白色背景,从而在视觉上给用户更好的体验。
一句话,在设计界面与交互时,我们应该站在用户的角度去设计,而不是站在开发者的角度,如果自己设计出来的东西连自己都不太愿意使用、不愿意看,那么还会有用户使用吗?所以说在这方面千万不要偷懒!当然限于自己在设计方面知识的欠缺,只能尽量做好这方面的工作。
3. 开发后
当项目开发完之后,自然就是对项目进行测试。虽然在开发过程中就保持了单元测试的好习惯,但是谁也不敢说当添加了新的功能之后,原有的功能不会出现 bug,因此还需要做最终的测试。
由于在开发过程中,一直只使用了自己的一部机子做测试,在自己的机子上能正常完美的运行,但是并不代表其它机子上也能有同样的效果,于是借来同学的一部旧手机做了测试(当时主要是为了测试在低版本的旧 Android 手机上的运行情况)。这一测试,就立马测出了问题,主要还是一些历史遗留问题,一些新 的控件在旧的 Android 版本上显示的效果与在新版本上的不一样,当然还有一些新版本特性的兼容问题以及屏幕分辨率不一致的问题。
刚开始在开发时为了让一些图片能够在不同分辨率下自适应,采用了 Vector Drawable,但是在低于 Android 5.0 以下的版本使用此种图片会报错,因此不得不想办法去解决这个兼容性的问题。而一些图标由于当时为了节省图片的大小,而使用了一些尺寸比较小的图标,当在高分辨率比较高的手机上显示时会比较模糊,为了给用户提供更好的显示效果,又去下载了不同的尺寸的图标,处理分辨率的兼容性问题。
当把上面的这些问题都处理完之后,想到为了让应用更健壮、性能及兼容性更好,同时又因手头上测试的机子数量有限,于是便去了鹅厂的云测试平台体验了一把,但是却不怎么让人满意。首先这个测试平台测试的机子被人黑了,无法正常开机进行测试,腾讯在对用户的应用正式测试之前并没有对机子的有效性进行检查;其次,当应用需要某项权限时,测试平台没有授权,反而卡在那里,导致测试无法正常进行而直接终止。遇到了这些问题,我就直接与腾讯的相关客服人员进行了反馈,谁叫我们是程序员呢?看到了 bug,就想尽快修复它。体验了一把云测试之后,没有让我感到满意,最后只好自己手动测试,解决应用在逻辑上的 bug 。
经过这次测试,让我深深体会到一个好的应用是经过不断测试优化而得到的。不过好在经过自己的折腾,目前项目中自己的那部分功能已经没有明显的 bug 了。
4. 后记
写了这个项目之后,至少我体验了项目的整个开发流程,虽然有些地方还是与实际开发的规范存在一定的差距,但是也让我接触到了一些实战方面的知识,让我更加重视产品的一些细节问题,让产品给用户带来更好的体验。