Android APP开发实战——从规划到上线全程详解

978-7-115-47230-4
作者: 强增
译者:
编辑: 赵轩

图书目录:

详情

本书从实战出发,以从零开始规划一款APP至APP上线这个过程为主线,尽量贴合实战,介绍整个过程中所涉及的各类知识,而不局限于某一方面,使得读者对APP开发相关知识有一个全面的了解,帮助他们开阔眼界,且能够以更专业的方式完成APP的开发。 本书的许多内容是在官方文档之上,从多个实际项目中获得的经验总结,可以使读者在开发APP的过程中,少犯低级错误,减少可能遇到的各种坑和提高开发效率。

图书摘要

版权信息

书名:Android APP开发实战——从规划到上线全程详解

ISBN:978-7-115-47230-4

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    强 增

责任编辑 赵 轩

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


本书以Android APP开发为主,介绍了从零开始规划一款APP至APP上线整个过程中所涉及的产品规划和原型设计、效果图设计、切图、APP与服务器端的协作、字符编码、网络、多媒体、数据加密、设计模式、APP架构设计、APP的功能开发、APP的性能优化、开发工具的使用、APP测试、项目管理和代码管理工具的使用等知识。通过阅读本书,读者将对APP开发的相关知识有了一个全面的了解,且能够以更专业和高效的方式完成APP的开发。

本书的目标读者是想从事APP开发或正在从事 APP开发的初中级读者,包括产品经理、美工设计人员、APP开发人员、服务器端接口开发人员、测试人员和项目管理人员等。


随着移动互联网的兴起,各行各业对移动应用的需求越来越大,从事APP开发的人也越来越多,APP开发行业可以说是方兴未艾。APP开发是比较复杂的事情,涉及产品、美工设计、服务器端开发、Android/iOS开发、软件测试和项目管理等各方面。这些方面都是相互关联的,如果要做好一个APP,需要对上述各方面都有所了解。

目前介绍APP开发的书籍很多,大都局限于某一方面,内容不够全面,并且许多书籍偏于理论,与实际联系不紧密。国内外市面上大多数的Android开发类图书,基本上可以分为两类:一类是从系统内核和源代码入手,书的内容重在分析Android各个模块的运行机制,深入理解系统肯定对应用开发者有好处,但很多时候并不是那么实用;另一类是标准教程,基本内容与Android官方文档类似,围绕API的用法就事论事地讲解,这类书在写法、教学思路和实例上虽然各有千秋,但在实际工作中就会发现还远远不够。

在实际工作中,笔者接触了许多从别的行业转行从事APP开发或从事APP开发一两年的人员,发现许多人对APP开发的基础知识不甚了解,需要关注的地方都没有考虑到,导致在开发APP的过程中犯了许多低级错误,而这些本来是可以避免的;而在APP开发行业中,从事APP开发一两年的人员又是占了绝大多数。在许多创业型的小公司里,也没有专职的产品、美工和测试人员,往往要求Android/iOS开发人员做到一专多能、身兼数职。

有感于此,笔者想写一本面向初级读者且全面介绍APP开发的书。与上述两类书都不同,本书完全从实战出发,以从零开始规划一款APP至APP上线这个过程为主线,尽量贴合实战,介绍整个过程中所涉及的知识,而不局限于某一方面,使得读者对APP开发的相关知识有一个全面的了解,帮助他们开阔眼界,进而以更专业的方式完成APP的开发。

本书是从多个实际项目中获得的经验总结,可以使读者在开发APP的过程中少犯低级错误,减少可能遇到的各种问题,从而提高开发效率。

本书的章节编排贴合实际项目,讲述了一款APP从开发前准备至上线的整个过程,具体内容如下所述。

本书相关代码,请从异步社区(www.epubit.com.cn)本书页面中下载。


开发APP前,除了竞品分析和项目组搭建外,还有以下事项需要确定。

大多数用户主要是将PAD用于娱乐、教育或企业办公。对于游戏、视频播放、图形图像处理、阅读、教育或企业办公等类型的APP,建议适配手机和PAD。电商或理财等类型的APP建议只适配手机。

随着硬件性能和网速的提高,使用PAD的用户越来倾向于直接打开相关的网站,而不愿去下载应用,以免频繁升级应用。因此在开发APP的时候,为节约资源,加快开发进度,可以先开发网站和手机版本的APP,最后再开发PAD版本的APP。

目前大部分PAD的尺寸都是大于等于7.9英寸,对于PAD版本的应用可以只考虑开发横屏界面,不用考虑竖屏界面。还有一部分PAD的尺寸在7英寸左右,对于这类PAD,可以考虑直接让用户使用手机版本APP(如果一个APP在6英寸的手机上使用,没有界面问题,那在7英寸的PAD上通常也可以正常使用)。

对于手机版本的应用:游戏、视频播放、图形图像处理、阅读、教育或企业办公等类型的APP建议支持横屏和竖屏;电商或理财等类型的APP可以只支持竖屏。

iOS APP建议适配iOS 8.0以上版本,Android APP建议适配Android 4.2以上版本,可以根据Apple和Google提供的各版本系统占有率的统计数据做实时调整。使用Android Studio新建工程时,在图1-1所示的界面,单击“Help me choose”链接,显示如图1-2所示的界面,列出了Android系统各版本的市场占有率。

图1-1

图1-2

对于大多数公司来说,毕竟开发和测试资源有限,对于占有率低于5%的系统版本建议不必考虑支持。

Apple产品的尺寸不像Android产品那么多,每种尺寸的用户量巨大,除了iPhone 4系列和之前系列的手机外,其余型号的手机和PAD都要适配。

Android产品的尺寸千差万别,建议适配的手机屏幕基准尺寸为5英寸、5.5英寸和6英寸,PAD屏幕基准尺寸为7英寸、8英寸和10英寸;屏幕像素密度支持MDPI、HDPI、XHDPI和XXHDPI这4种。

Apple的机型比较少,除了iPhone 4系列的手机外,其余每个机型都可以考虑购买样机。

Android的机型比较多,在选择机型的时候,应根据以下几项综合判断。

友盟和极光等厂商也会发布一些关于不同设备的市场占有率等的数据统计报告,在选择开发样机的时候,可以做参考。

Apple 的产品硬件配置高、屏幕好,所以iOS APP通常内置两套图片,即@2x和@3x分辨率的图片。

Android的产品中,屏幕像素密度为XXHDPI的比较少,且大多数产品的配置不高,为了节约存储空间和减少APP的大小,Android APP通常只内置一套完整的像素密度为XHDPI的图片,以及部分MDPI、HDPI和XXHDPI的图片。

如果APP定位高端用户,建议还是要内置一套完整的像素密度为XXHDPI的图片。


产品经理通常需要提供产品原型、流程图和功能说明文档等给设计、开发和测试人员。设计人员需要根据产品经理提供的文档,制作效果图和图片资源等给开发和测试人员。设计人员的文档应先经过产品经理审核后,再提供给开发和测试人员,且产品经理和设计人员提供的文档内容需要保持一致,否则开发和测试人员会困惑以哪个为准。

其他行业的软件开发通常一个产品是一个项目组做,但APP产品往往是Android和iOS两个项目组做同一个产品,所以无论是产品文档还是设计文档,一定要非常详细,不能有遗漏和产生歧义的地方,否则很可能导致Android APP和iOS APP单独使用都没有问题,但一对比就会发现许多不同的地方,结果还得花费大量时间统一两个APP的UI和功能。

编写产品文档时,需要注意以下事项。

(1)产品文档应该完整体现各种处理流程,尤其是在异常状况下的各种处理流程,如无法从服务器获取到数据和用户输入错误等。

(2)需要明确进入每个界面,显示的数据未加载完时,当前界面怎样显示。例如从服务器获取数据时,当前界面整体显示为空白界面,用户只能看到当前界面之上的加载提示框,数据加载完成后,加载提示框消失,并显示整个界面布局;当顶部有标题栏,或底部有TAB栏时,这两部分显示的数据通常不需要从服务器获取,可以一进入这个界面就显示出来。

(3)需要明确哪些界面支持用户手动更新当前界面数据和上拉加载更多数据的功能,及对应的动画效果。

对于更新当前界面数据功能,还需明确采用以下哪种方式。

(4)许多APP在首页的底部有个TAB栏,该栏上有几个按钮,单击这几个按钮显示的界面属于一级界面,左上角是不需要有返回图标或按钮的。除了一级界面外,其余每个界面都应该明确从当前界面可以返回到哪个界面。

(5)无论APP的界面显示模式是竖屏还是横屏,其对应的屏幕宽度都有限,水平方向尽量少放置内容;而大多数用户已经知晓通过向上滑动手指,在屏幕的垂直方向可以翻页显示更多内容,所以可在垂直方向多放置内容,即屏幕的高度方向对显示的内容没有限制。

当然也有特例的情况,如在竖屏模式下,当前界面有多个TAB页面,通常会支持左右滑动,以显示不同TAB页面的内容;或有多张图片需要显示的时候,也支持左右滑动,以显示不同图片的内容。此时最好在界面显示提示信息,提示用户左右滑动显示更多内容,如图2-1所示。

图2-1

在图2-1所示的界面中显示了3种商品,如果种类超过3个,在用户第一次看到这个界面的时候,在此界面上方可再显示一个界面,提示用户“用手指左右滑动页面,显示更多内容”。用户点击屏幕,则提示界面消失。

如图2-2所示,如果显示的图片不只一张,在用户第一次看到这个界面的时候,在此界面上方可再显示一个界面,提示用户“用手指左右滑动页面,显示更多图片”,用户点击屏幕,提示界面消失。

图2-2

(6)如果当前显示的内容超过了一屏,用户通过向上滑动手指,翻到了第N页(N>1),此时在屏幕的右下角要显示一个图标,用户点击此图标直接显示第1页内容,如图2-3所示。

图2-3

如果页数比较多,需要设计用户可以选择查看其中任一页内容的图标,如提供页码列表或让用户手动输入想查看的页码数字。

如图2-4所示,商品列表数据共有10页,当前显示的是第2页的内容,左右两边4个箭头对应的功能是跳到第一页、跳到上一页、跳到下一页和跳到最后一页。在编辑框中输入5,然后单击 按钮,则会直接跳到第5页。

图2-4

(7)在使用APP的时候,常遇到在某个界面显示的内容比较多,一屏显示不下,而在屏幕底部的控件又需要一直显示,此时就需要考虑悬浮设计,就是在滑动当前界面显示的内容时,始终显示底部的控件,相当于底部控件悬浮在内容的上方。

如图2-5所示的是电商APP里必有的购物车界面,购物车里的商品可能一屏显示不下,需要用户滑动商品列表显示更多商品内容,但在界面底部的商品总价和 按钮需要一直显示,方便用户随时进入结算界面下单,这就需要使用悬浮设计。

图2-5

(8)在电商APP的购物车、结算页面和订单页面,通常页面顶部需要显示商品优惠政策或客户联系方式等信息,中间显示商品列表,底部是功能按钮区域。

如果商品数量比较多,就需要上拉显示更多的商品,为了扩大商品列表的显示区域,有以下两种方案。

如图2-6所示的订单详情界面,上部区域显示收件人的联系方式和收件地址,底部显示 按钮,只有中间区域用于显示商品列表。当用户向上滑动手指,则显示更多的商品,此时用户并不关注收件人的联系方式和收件地址信息,就可让这部分区域随着商品列表一起向上滑动,或者把收件人联系方式和收件地址信息隐藏起来,将此区域用于显示商品列表数据;当商品列表停止滑动时,可再显示收件人的联系方式和收件地址信息。

图2-6

(9)由于手机屏幕空间有限,像标题栏这样的区域在有些界面中就不必显示了,从而更有效地利用空间。如商品详情界面,这样可以扩大图片展示区域。

如图2-7所示的商品详情界面,将标题栏区域去掉,整个上部区域都用于显示商品图片,中间用于显示商品规格等数据,底部用于显示功能按钮。

图2-7

(10)文本输入区域最好能设计在屏幕的上半部分,这样不容易被输入法的键盘遮住,对应的功能按钮最好紧贴在输入区域的下方,许多APP的登录、注册和修改密码界面就是这样设计的。

如图2-8所示,文本输入框和 按钮都在屏幕的上半部分,以避免输入法键盘被遮住。

图2-8

如果文本输入区域只能设计在屏幕的下半部分,那么在用户点击文本输入区域时,设计弹出一个新的界面,或是在弹出输入法键盘时将当前界面整体往上移动,这样文本输入区域也不容易被输入法键盘遮住。

如果不能把界面整体往上移动,可以适当缩小文本输入区域上方的内容所占的空间。如把文本输入区域上方的图片显示区域缩小,这样此区域下方的内容就自动向上移动了,也就是部分上移。如图2-9所示为没有显示输入法键盘时的界面。

图2-9

图2-10所示为显示输入法键盘时的界面。

图2-10

(11) 如果某个界面不需要用户输入,只能看,不能编辑,那么这种界面的功能按钮可以设计在界面的最下方,方便用户用大拇指点击。

在如图2-11所示的设置界面中,通常会有许多功能菜单供用户点击,但不会让用户在此界面进行输入操作,也就是在此界面不会弹出输入法键盘,这样就可把 按钮放在界面的最下方,方便用户单手操作,用大拇指点击按钮。

图2-11

(12)在设计文本输入区域时,应显示提示信息,提示用户输入的字符类型和字符个数。输入区域的最右边要设计有删除输入字符的图标,用户单击即可删除输入区域中的所有字符。

单击如图2-12所示的界面中的“×”图标,会删除密码框中的所有字符。

图2-12

(13)密码输入框的右边应设计有切换明文或密文显示的图标。

单击如图2-13所示的界面中的“ ”图标,会切换密码是明文显示或密文显示。

图2-13

(14)如需用户输入手机号码,需要明确号码的显示方式是否按3-4-4格式显示,如131 1234 5678。

(15)如需用户输入银行卡,需明确卡号的显示格式,如按此格式显示:1111 2222 3333 4444。

(16)遇到字符串长度超过显示区域的情况,通常有以下几种处理方式。

在提供产品文档时,需要明确采用哪种处理方式。

(17)在登录和注册界面,建议设计用户手动输入验证码,这样可以防止恶意攻击。

(18)产品文档中还应包括各种提示框和提示文字的设计。如操作成功和操作失败的提示框,提示用户等待的提示框,以及什么时候使用Toast提示框,什么时候使用Alert提示框。

建议在操作成功的时候用Toast提示框(显示时间不超过2秒),操作失败的时候用Alert提示框。Toast提示框显示后会自动消失,导致用户可能看不到出错提示;Alert提示框不会自动消失,只有用户单击其上的按钮才会消失,这样保证了用户可以看到出错提示。(Android系统自带Toast提示框,iOS系统需要导入第三方库实现Toast提示框)

(19)在APP中若要用到轮播图和动画,轮播图的切换时间和动画的显示时间最好也要在产品文档中明确说明。

(20)产品文档中需要明确哪些界面用网页实现,哪些用原生代码实现。通常内容多变的界面,如广告和促销界面,或者需要可以被链接索引传播的文字内容等,可以用嵌入网页的形式实现。

(21)产品文档中需要明确APP是否支持长登录,如果支持长登录,登录时间维持多久。

(22)产品文档中需要明确是否支持一个账号在多个设备上同时登录,以及哪些数据需要在各设备间进行同步。如电商APP和电商网站的购物车、收藏夹、浏览历史和搜索历史等是否需要实时同步。

(23)需要考虑哪些界面要保存用户的输入信息。如登录界面通常需要保存用户账号在本地,这样当用户再次登录的时候,不需再次输入账号。

还有像用户个人资料、地址和文本编辑等界面,需要用户输入的数据比较多,用户在这些界面中点击 按钮或 键退出时,最好显示提示框,提醒用户是否保存当前界面的数据。

(24)如用到PUSH消息功能,需要明确以下内容。

(25)对于订单、收藏夹和浏览历史之类的数据,也需要考虑在服务器端或APP本地保存的时间段和数据数量。

(26)产品文档中需要考虑到一些应用市场的要求,如iOS APP,要发布到App Store,注册页面必须包含一个用户许可协议的链接,否则可能通不过Apple的审核。

(27)设计评论功能时,分数是否支持小数、星级的划分标准、评论者的昵称或名称的显示方式也都要考虑到。

(28)如有显示或需用户输入数字的地方,需明确数字的默认值和最大值。如有小数,需明确小数位数,像商品的数量和价格等,这也涉及界面布局区域的宽度设置。

(29)在电商APP的结算页面,建议设计买家留言功能,改善用户体验。

(30)搜索功能需要明确是APP本地搜索,还是APP向服务器发送请求,在服务器端进行搜索,并返回结果给APP。

两者具体的实现方式如下所述。

(用实时搜索方式,如需要输入5个字符,在极端情况下可能APP向服务器发送5次请求,在交互5次之后才能搜索到结果,这极大地浪费了流量和时间)

(31)搜索功能会涉及多个界面,各界面间的跳转流程需要明确。

(32)大多数APP都会展示许多图片,对网速要求高,但用户可能会在网速不好的情况下(如在2G网络状况下)使用APP。需要考虑在此情况下,是否显示分辨率较低的图片或不显示图片,如电商APP中的商品列表转换为文本模式,以降低对网络性能的要求。

(33)需要明确APP的升级功能流程和相关界面,且要注意强制升级和非强制升级的不同。

非强制升级是用户即使选择不升级当前版本,也能正常使用APP。强制升级是用户必须升级后,才能使用APP。

如图2-14所示的是非强制升级的界面。当用户启动APP时,APP从服务器获取到升级信息,并在APP启动界面显示提示框,内有 两个功能按钮,供用户选择。

图2-14

如图2-15所示的是强制升级的界面。在APP的启动界面显示的提示框只有 按钮,用户只能点击 按钮升级APP版本。

图2-15

(34)在用户进行删除操作的时候,一定要显示提示框请用户确认,以防止用户误操作,如图2-16所示。

图2-16

(35)大多数APP中的许多功能需要在登录状态才能正常使用。目前许多APP都把注销登录的按钮设计在层次比较深的界面,让用户一直保持登录状态。如果用户很容易看见注销按钮,那退出登录状态的概率也就变大了。

(36)Apple产品没有 键,但Android 产品通常有 键,用户可以直接按 键退出,需要明确采用以下哪种退出方案。

如图2-17所示的提示框,需要用户在点击 键后,把手指从手机的返回键区域移动到中间区域,点击提示框上的按钮。

图2-17

如图2-18所示的Toast提示框,用户手指一直放在返回键区域就可完成退出操作,不需移动手指,用户体验更好。

图2-18

(37)对于展示列表数据的界面,需要明确数据按哪种方式排序。

(38)如果提供下拉列表,让用户选择下拉列表里的数据时,需要考虑是否当用户在文本框中输入文字时,程序自动搜索相关的数据。如图2-19所示,当销售员数量较多时,可节省用户查看列表数据的时间,改善用户体验。

图2-19

(1)设计师在设计效果图的时候,最好按APP适配的最小尺寸设计布局,且在文字显示区域放的字符个数就是实际要显示的最大字符个数,这样很容易发现控件显示区域不足的问题。

如在电商APP中,最长的商品名称可能有20个字符,那么在效果图上就要放20个字符。

(2)各种元素区域的尺寸要符合Apple和Google的设计规范要求。如果最小点击区域太小,会导致用户无法正常操作。

对于iOS APP,就是在iPhone 3手机上,也就是@1x分辨率下,最小点击区域不能小于44px×44px;对于Android APP,就是在屏幕像素密度为MDPI时,最小点击区域不能小于44px×44px。在@2x分辨率和像素密度为XHDPI时,最小点击区域不能小于的逻辑像素为88px×88px。

(3)在用户第一次使用APP时,许多界面没有数据显示,其内容为空。如用户第一次使用电商APP时,购物车和订单界面内容为空,就需要设计这些界面内容为空时的效果图。

(4)除了正常显示数据的界面外,还需设计从服务器或本地获取数据时的提示界面和无法正常获取数据时的界面。

(5)iPhone手机基本都使用系统自带的输入法,在用户点击搜索区域,弹出输入法软键盘时,软键盘上会显示 按钮;Android手机上使用的输入法各式各样,输入法软键盘上不一定会有 按钮。

在设计搜索界面时,iOS APP的右上角通常不需要有 按钮,但Android APP的右上角最好要设计有 按钮。同时要设计搜索不到数据时的界面。

(6)在设计搜索界面时,需要设计有搜索历史和无搜索历史的两种界面,同时明确显示搜索历史的个数。

(7)如果在一块区域中,只有一个元素需要用户点击,那可适当放大这个元素的长度和高度,或设计整个区域都响应用户点击,以方便用户操作。

如图2-20所示的界面,主要是让用户点击右边的箭头图标,可以把箭头图标设计得大点,或把这一整行区域都设计能够响应用户点击,方便用户操作。

图2-20

(8)在许多APP,尤其是电商APP中,会在购物车、订单或消息图标的右上角设计一个数字角标,显示购物车中的商品数量、订单个数或消息个数。

通常会把角标区域设计成圆形,如果数量不到100时,可以正常显示;超过99,变成3位或4位数时,往往就显示不下了,此时有以下两种解决方案。

如图2-21所示,左边的购物车里只有10个商品;中间购物车里的商品个数超过了99,就用99+表示;右边购物车里的商品个数超过了100,达到了1000,把圆形角标改成了椭圆形角标,以便完整显示1000这个数字。

图2-21

(9)在设计图片显示区域的时候,不同界面图片显示区域的长宽比最好一致。如电商APP在商品列表和商品详情界面都需要显示图片,商品详情界面的图片显示区域设计为300px×300px,商品列表中的图片显示区域可设计为100px×100px,这样在服务器端可以只放一张300px×300px的图片,在APP端的商品列表界面显示图片时,服务器端可把300px×300px图片的宽度和高度直接都除以3,再发给APP,而不用担心在商品列表界面图片会变形。

(10)按最新的Google文档要求,Android APP中各控件的尺寸数值及控件间的间距数值最好是8的倍数。

(11)用户使用APP的时候使用得最多的操作是点击操作,要想设计使用滑动操作,如在消息列表中通过滑动删除某条消息,最好给用户某种提示,否则用户通常不会使用滑动操作。

最理想的情况是设计人员给iOS APP和Android APP设计两套图片,为了节约资源,许多设计人员只按iOS APP的要求提供设计切图和标注尺寸。

部分iPhone设备的屏幕尺寸和像素密度见表2-1。

表2-1

设备 屏幕尺寸 图片尺寸倍数 分辨率(px×px) 像素密度值
iPhone 3GS 3.5寸 @1x 320×480 163
iPhone 4/4s 3.5寸 @2x 640×960 330
iPhone 5/5s/5c 4.0寸 @2x 640×1136 326
iPhone 6 4.7寸 @2x 750×1334 326
iPhone6 Plus 5.5寸 @3x 1242×2208 401

iPhone手机的最小分辨率是320px×480px,把这个尺寸定为基准界面尺寸(baseline),基准尺寸所对应的图片称为1倍图(@1x),其余机型使用的图片按像素密度值与基准尺寸的像素密度值的倍数定为2倍图和3倍图。

屏幕像素密度是指每英寸上的像素点数,单位是DPI(Dot Per Inch);PPI(Pixel Per Inch)是每英寸像素数。针对显示器的设计,DPI=PPI。计算方法是长宽的像素各自平方之和开方再除以对角线长度(单位英寸),如iPhone 5的DPI为 / 4 = 326。

Android系统将屏幕大小分为以下4个级别。

屏幕像素密度与屏幕尺寸和屏幕分辨率有关,即屏幕尺寸越小,分辨率越高,像素密度越大,反之越小。

Android设备的部分屏幕尺寸和像素密度见表2-2。

表2-2

 

低密度(120), ldpi

中密度(160), mdpi

高密度(240), hdpi

超高密度(320), xhdpi

小屏幕

QVGA (240×320)

 

480×640

 

正常屏幕

WQVGA400 (240×400)

HVGA (320×480)

WVGA800 (480×800)

640×960

WQVGA432 (240×432)

WVGA854 (480×854)

600×1024

大屏幕

WVGA800(480×800)

WVGA800(480×800)

 

720×1280

WVGA854(480×854)

WVGA854(480×854)

600×1024

超大屏幕

1024×600

WXGA (1280×800)

1536×1152

2048×1536

1024×768

1920×1152

2560×1536

1280×768

1920×1200

2560×1600

注意:其中的xhdpi按屏幕尺寸为4.5寸计算,DPI为 / 4.5 = 326,约为320;XXHDPI的DPI值为480。

在iOS APP中通常内置两套图片:@2x和@3x,而Android APP中通常只内置一套XHDPI的图片。XHDPI对应的分辨率和iPhone 5系列的分辨率最接近(像素密度一样),所以设计师可以按iPhone 5系列的分辨率做一套@2x的切图,在Android APP中把@2x的图片放在drawable-xhdpi文件夹中就可以了(在4.5寸720px×1280px的手机上适配效果最好,像素密度一样)。

在提供APP动态加载的图片时,如电商APP中的商品图片,同样需要考虑不同分辨率的情况。如在@1x和mdpi的情况下,图片显示区域的大小为10px×10px;在@3x和xxhdpi的情况下,图片显示区域的大小变为30px×30px;对于10px×10px的图片,此时要放大显示,就会变得模糊,因此在提供图片的时候,就需要按@3x和xxhdpi的情况,提供最高分辨率的图片,保证在各种分辨率下都能正常显示(高分辨率的图片缩小成低分辨率的图片,图片内容不会变模糊)。

在Android APP开发中,屏幕尺寸的多样性导致了界面适配的复杂性,很多APP内置的图片在不同尺寸屏幕的设备上被放大拉伸后,图像会模糊或失真;如果针对不同的分辨率内置多套图片,又增大了APP安装包的大小,这让开发人员非常头疼。因此Google专门开发了一种.9.png格式图片来解决这个问题。

这种格式的图片能按照设定来拉伸特定区域,而不是整体放大,从而保证了图片在各个分辨率的屏幕上都可以完美展示。与普通的PNG格式图片相比,点9图的四边,即上、下、左和右各有一条黑色实线,各代表了不同的含义:左侧和顶部的线用于确定图片的可拉伸区域,右侧和底部的线用于确定图片中的内容显示区域。点9图一般用于纯色且需要拉伸的地方,如字符串标签、文本编辑框、按钮和箭头等。在前期设计人员切图的时候,开发人员需要与设计人员协商确定哪些地方使用点9图,以免后期改动,导致设计人员重复切图。

在Android Studio 3.x版本中,集成了制作点9图的工具。

在Android Studio工程中,选中图片,点击鼠标右键,在弹出的选项菜单中,选择“Create 9-Patch file...”,可将选中的图片转成点9格式图片,如图2-22所示。

图2-22

(1).9.png对不同尺寸屏幕的适配,只是针对图片拉伸而言的,包括单独的横向拉伸、单独的纵向拉伸以及同时横向和纵向拉伸,对于图片压缩没有效果。

(2)文件的后缀名必须是.9.png。

目前许多人使用Axure设计原型,最好在原型中增加一页,说明每次的修改内容、修改时间、修改人员和版本号等。对于rp文件,建议使用版本管理工具(如SVN)管理。

建议设计人员也使用版本管理工具(如SVN)给APP开发人员提供效果图和图片,每次把文档提交到服务器时,都填写修改说明,方便APP开发人员了解做了哪些修改。在放图片的时候,最好按功能模块或界面分类存放,且文件名都起中文,以方便查找。

使用版本管理工具管理文档,除了可以方便查看修改记录外,如果想使用旧版文档,也可以很容易地从版本管理工具的版本库中获取。


在接口设计中要注意以下事项。

(1)首先需要确定APP和服务器间用什么格式传输数据,常用的有两种:XML和JSON。下面使用了XML格式和JSON格式表示同样的信息进行比较:

<?xml version="1.0" encoding="utf-8" ?>
<country>
  <name>中国</name>
  <province>
    <name>广东</name>
    <citys>
      <city>广州</city>
      <city>深圳</city>
    </citys>    
  </province>
  <province>
    <name>广西</name>
    <citys>
      <city>南宁</city>
      <city>桂林</city>
    </citys>   
  </province>
</country>

以上是XML格式文件的数据。

{
  "name": "中国",
  "quantity": 2,
  "provinces": [
    {
      "name": "广东",
      "quantity": 2,
      "citys": {
        "city": [
          "广州",
          "深圳"
        ]
      }
    },
    {
      "name": "广西",
      "quantity": 2,
      "citys": {
        "city": [
          "南宁",
          "桂林"
        ]
      }
    }
  ]
}

以上是JSON格式文件的数据。

从上述示例可看出,XML文件中存在大量的描述信息,大大增加了网络传输的数据量;同样的内容用JSON格式传输的数据量比较少,相应的网络传输速度和数据解析速度也都快,所以首选JSON格式。

JSON格式的字段类型值常用的有以下几种。

上述三种属于基本类型。

上述两种属于复合类型,其中可以包含各基本类型字段。

示例如下:

{//对象
  "name": "中国", //字符串
  "quantity": 2,//整数
  "isAsia": true,//布尔类型
  "provinces": [//数组
    { 
      "name": "广东",
      "quantity": 2,
      "citys": {
        "city": [
          "广州",
          "深圳"
        ]
      }
    },
    {
      "name": "广西",
      "quantity": 2,
      "citys": {
        "city": [
          "南宁",
          "桂林"
        ]
      }
    }
  ]
}

(2)需要设计JSON数据的具体格式。

APP发送请求(有非数组格式的具体参数),示例如下:

{
     "params":{
         "username":"aaa",
         "password":"123456"
     }
}

APP发送请求(有数组格式的具体参数),示例如下:

{
  "params": {
    "products": [
      {
        "name": "可乐",
        "quantity ": 1
      },
      {
        "name": "雪碧",
        "quantity ": 2
      }
    ]
  }
}

APP发送请求(无具体参数),示例如下:

{
  "params": {
  }
}

服务器端处理成功后,返回给APP的数据(只返回操作状态,不返回数据),示例如下:

{
  "code": 800
}

服务器端处理成功后,返回给APP的数据(返回操作状态和非数组数据),示例如下:

{
    "code": 800
    "result":{
         "message":"订单提交成功"
    }
}

服务器端处理失败后,返回给APP的数据(只返回操作状态和出错提示) ,示例如下:

{
    "code": 800
    "result":{
      "products": [
        {
          "name": "可乐",
          "quantity ": 1
        },
        {
          "name": "雪碧",
          "quantity ": 2
        }
      ]
    }
}

操作失败(只返回操作状态和出错提示) ,示例如下:

{
    "code": 801
    "result":{
         "message":"密码错误,请重新输入"
    }
}

在定义JSON中的字段名称时,要尽量短小,以减少网络传输的数据量。

(3)服务器端采用的语言有Java这样的强类型语言,也有PHP这样的弱类型语言,弱类型语言对变量类型没有强类型语言那么严格,但Android和iOS开发使用的语言都是强类型的,导致APP端常会遇到变量类型出错的问题。如需要整型数据,结果服务器传的数字有小数;需要非字符串类型的数据,结果服务器传的数据是字符串等。

为解决这类问题,在和服务器端定义字段的数据类型时,建议使用以下方案。

这样APP和服务器端交互,只使用了两种基本数据类型,大大减少了由于各种数据类型不兼容导致APP端数据解析出错的问题。

(4)APP从服务器读取数据的时候,会遇到数据为空的情况,此时服务器端返回给APP的数据类型应该和数据不为空时的类型一致。

如下所示:

{
    "code": 800
    "result":{
         "nikeName":""
    }
}

nikeName字段的类型是字符串,当其值为空时,应返回空字符串"",而不应返回null。

{
    "code": 800
    "result":{
         "products":[]
    }
}

products字段的类型是数组,当其值为空时,应返回空数组[],而不应返回null或其他类型的数据。

(5)因为服务器端的接口代码可能会发生变化,所以在APP向服务器端发送请求时,最好把接口的版本号也带上,如下所示:

{
    "version":1.0,
    "params":{
         "username":"aaa",
         "password":"123456"
    }
}

以上JSON数据中,version字段的值表示当前使用接口的版本号为1.0。

如果已经上线的旧APP中使用的接口版本是1.0,在上线后接口更新到1.1版本,而且不兼容1.0版本,用户有可能不更新APP,还是使用旧版本APP。服务器端接收到请求后,发现APP使用的接口版本是1.0,就可以调用旧接口处理APP请求。如果请求中不带版本号,遇到这种状况,就很难处理了。

(6)APP常需要从服务器获取图片,但服务器存储的图片尺寸往往不完全符合APP需要,需要将图片放大或缩小,因为服务器的性能比手机高,所以最好是在服务器端按APP的需求处理图片,然后把处理过的图片发给APP。APP在发送获取图片的请求时,把所需图片的宽度和高度发给服务器,如采用GET方法,可以按以下方式。

http://www.hello.com/getimage/2/width/100/hight/100

服务器收到请求,就可先按APP要求的尺寸处理图片,再发给APP。

当然也可以用POST方法实现,用JSON格式传递参数,示例如下:

{
    "version":1.0,
    "params":{
         "imageId":2,
         "width":100,
         "hight":100
    }
}

(7)大多数APP和服务器交互时用HTTP协议,每向服务器发送一个请求都要先建立连接,传输数据后再断开连接。即使服务器端有连接池设计,连接池中容纳的连接个数也是有限制的。

在设计接口时,APP每执行一个动作尽量做到只向服务器发送一次请求,减少APP发送请求的次数,从而减少APP和服务器建立连接和断开连接消耗的时间及资源,提高程序响应速度。

(8)对于向APP返回数组数据的接口应设计支持分页操作,并提供参数以方便APP设置获取元素的起始位置和获取的个数。

例如,数组中有100个元素,APP端第一次从第1个元素开始只获取10个元素,第二次从第11个元素开始只获取5个元素。在电商APP中读取商品列表和订单列表可以这样设计。

获取商品列表的JSON数据如下所示:

{
    "version":1.0,
    "params":{
         "categoryId":1,
         "offset":0,
         "limit":10
    }
}

其中categoryId表示读取哪一类别的商品列表,offset表示从商品列表中的第一个商品开始读取商品数据,limit表示读取10个商品数据。limit的数值也可以在服务器端设置,此时以服务器端的数值为准,APP传递的数值不起作用。

(9)对于可能会变动的功能逻辑,尽量放在服务器端实现,而不是APP本地实现,这样后续的功能变更时修改服务器端的代码就可以了,不需要用户升级APP。例如,电商APP中商品的默认排序功能,在服务器端可以把商品按价格或销量排序后,再把数据传递给APP,APP端只负责显示就可以了。

(10)APP端在使用服务器接口的时候,常会遇到从服务器传来的JSON数据类型和约定的不一致,导致APP解析出错的问题。APP遇到此类问题时往往会Crash,需要对此问题做特别处理,如下所示:

try {
     // parseJson为解析从服务器返回的JSON数据的方法
     T model = parseJson(jsonData);
     onSuccess(model);
}catch (Exception e){
     message = "数据解析出错";
     onError(message);
}

开发Android APP时,利用try…catch…机制可以有效防止APP Crash,并提示用户出现了什么问题。

在开发阶段,APP应明确提示“数据解析出错”,这样有利于发现和解决问题。上线后,用户在使用APP的时候遇到这类问题,用户不一定理解具体含义,可换种方式提示。

如图3-1所示,明确告知用户服务器端出现问题了,需要联系客服解决。

图3-1

开发阶段通常使用Debug版本,而线上版本是Release版本,利用编译选项可以实现不同版本显示不同的提示信息。

(11)服务器端设计接口的时候,需要考虑到APP重复提交数据的情况。例如,APP和服务器的响应超时时间是10秒,服务器收到APP的请求后,在11秒内完成了处理,但此时APP会提示用户连接超时,用户往往会再次操作,APP就向服务器发起重复请求。

APP和服务器的交互通常使用HTTP协议,常用的方法是GET和POST。GET方法的参数暴露在发送给服务器的URL里,且通常服务器端对URL的长度有限制;POST方法的参数在HTTP请求的BODY体里,比GET方法安全且数据长度没有限制。从安全角度考虑,只要是带参数的请求,都应该使用POST方法。

APP向服务器发送的URL请求通常是如下格式。

http://+域名+/+模块名+/+方法名

例如,http://www.test.com/customer/login 中包含的方法名为login,对应的JSON 数据如下所示。

{
"version":1.0,
"params":{
"username":"aaa",
"password":"123456"
}
}

params 中包含此方法自身需要的参数。

还有一种方案是把方法名也作为参数,传给服务器,对应的URL如下所示。

http://www.test.com/customer(此网址仅举例用,并非一个可以真正访问的网址。)

对应的JSON 数据如下所示。

{
"method":"login",
"version":1.0,
"params":{
"username":"aaa",
"password":"123456"
}
}

如果黑客截获了APP向服务器发送的请求,就可以向服务器反复发送某个请求,对服务器实施攻击,导致服务器瘫痪。解决的方法是在APP向服务器发送的请求参数中增加时间戳参数,如服务器发现这个时间戳与服务器当前时间的间隔比较久,可判定此请求失效,不予处理,避免被恶意攻击。

例如,APP从服务器获取商品列表时,发送如下数据。

{
    "version":1.0,
    "timeStamp":1425065977,
    "authCode":"607a0aa16db850d06682d7711588ae46",
    "params":{
         "categoryId":1,
         "offset":0,
         "limit":10
    }
}

timeStamp是APP发起此次请求的服务器当前时间,authCode是根据timeStamp的数值按MD5算法或其他算法生成的验证码。timeStamp和authCode是一一对应的关系,如果timeStamp的值变了,则authCode的值也随之变动。

服务器收到APP发的请求后,首先比较timeStamp的数值与服务器收到该请求的时间,若两者相差比较大(如相隔了60秒),则可能是黑客发起的非正常请求,服务器对此请求不予处理;如两者相差在允许范围内,则验证用timeStamp的数值按APP同样的算法生成的字符串内容是否和APP发送的authCode内容一致。

使用此方法,需要APP和服务器的时间保持同步。APP在启动时,通过接口获取到服务器的时间并与APP的时间比较,如果不一致,APP在计算timeStamp的数值时,需要把两者的差值也计算在内。

数据加密有如下几种方式。

(1)使用HTTPS对APP和服务器的交互数据加密。

(2)使用HTTP协议,自行设计用对称加密或非对称加密方式加密。对称加密是加密、解密用同样的“钥匙”,非对称加密的加密、解密用不同的“钥匙”,建议采用更安全的非对称加密方式。

(3)使用HTTP协议,自行设计用MD5加密。许多APP的密码就是用MD5加密后传给服务器的。

APP向服务器发送密码的时候,应先对密码进行加密,然后再发送。之前已经提过需要对传输过程加密,这样相当于对密码进行了两次加密。

存储在用户终端设备或服务器中的密码也都是要加密后再存储,不能存储明文(最好不要在用户终端设备中存储密码,以防被盗取和破解)。

一些比较重要的数据,如支付宝和微信支付都会用到密钥文件,这类文件最好存储在服务器中,不要存储在用户终端设备中。

账号+密码的登录方式又分为以下两种。

(1)密码由人工设置,用户需要记住密码,且密码一旦确定,用户不会经常变更,此种方式容易被破解和利用。

(2)每次登录时,服务器端动态生成密码,然后用短信发给用户。此密码只有在限定的时间内使用才有效,有效期一过,密码自动失效。用户无需记住密码,且密码在每次登录时随机变更,此种方式不容易被破解和利用。

用户只有使用合法的手机号,才能登录使用APP,从而有效地鉴定用户身份。

账号+密码+验证码的方式又分为以下两种。

(1)由APP或服务器生成验证码,验证码直接在APP的登录界面显示出来。

如果由APP生成验证码,则在APP端可以判断用户输入的验证码是否有效,APP不必把验证码发给服务器。

如果由服务器生成验证码,APP必须把验证码与账号、密码一起发给服务器,由服务器判断验证码是否有效。如果验证码有效,再判断账号和密码是否有效。

(2)用户每次登录时,服务器利用短信发送验证码到用户手机,此验证码只有在限定的时间内使用才有效,有效期一过,验证码自动失效。

APP必须把验证码与账号、密码一起发给服务器,由服务器判断验证码是否有效;如果验证码有效,再判断账号和密码是否有效。

用户只有使用合法的手机号,才能登录使用APP,也可以有效地鉴定用户身份。

目前APP大都支持长登录,就是用户登录一次后,如果用户没有主动注销、清除APP缓存数据或卸载APP,就可以在一段时间内一直保持登录状态。

APP登录成功后,服务器以某种方式,如随机生成N位的字符串作为Token,同时设置一个有效期,存储到服务器中,并返回Token给APP,APP把此Token的值保存在本地。

后续APP在发送请求时,都要带上该Token。每次服务器端收到请求时,都要验证Token和有效期,Token数值正确且在有效期内,服务器返回所需要的结果,否则返回错误信息,提示用户重新登录。

登录成功后,服务器返回数据给APP,如下所示。

{
    "code": 800 //表示登录成功
    "result":{
         "token":"abcd1234"
    }
}

APP再次发送请求时,把接收到的Token值也发送给服务器,如下所示。

{
    "version":1.0,
    "token":"abcd1234",
    "params":{
         "categoryId":1,
         "offset":0,
         "limit":10
    }
}

APP登录成功后,服务器创建一个包含SessionId和Expires两个属性值的Cookie,存储在服务器中,并发送给APP。

后续APP发送请求时,都要带上一个包含此SessionId的Cookie。服务器每次收到请求时,都要验证SessionId和有效期,SessionId数值正确且在有效期内,服务器返回所需要的结果,否则返回错误信息,提示用户重新登录。这种方式类似浏览器的认证方式。

当APP登录成功后,服务器端返回Cookie给APP,如图3-2所示。

图3-2

APP后续再发起请求时,把之前获取的Cookie信息发送给服务器,如图3-3所示。

图3-3

APP登录成功后,每次发送请求时都把账号和密码也发送给服务器,服务器每次收到请求都要验证账号和密码。如果用户没有登录或已注销,发送请求时就不会把账号和密码发送给服务器。

例如,账号是aaaa,密码是123abc,登录成功后,APP后续发送请求时,可按如下形式:

{
    "version":1.0,
    "authCode":"070cb3abda10fa1d50e4b0c2b71ac561", // "aaaa+123abc"的MD5数值
    "params":{
         "categoryId":1,
         "offset":0,
         "limit":10
    }
}

服务器记录处于登录状态的账号和密码的MD5数值,与APP端上传的数值进行比较,判断账号和密码是否有效。

通常图片都需要在APP端做缓存处理,所以从服务器返回图片链接的时候,一定要同时返回图片最新修改的时间戳。APP将本地存储图片的时间戳和从服务器获取的时间戳进行对比,判断是否需要更新本地缓存的图片。

服务器返回一个“modification_time”字段,用于表示图片的修改时间,如下所示。

{
    "image":{
        "modification_time":1525065977
        "image_url":"http://www.test.com/image/test.jpg"
    }
}

对于其余类型的数据文件,可以用时间戳,也可以用版本号作为是否更新的依据,而且最好把版本号或时间戳保存在数据文件里。

在保存地址数据的JSON格式文件里,使用version字段表示版本号,如图3-4所示。

图3-4

当前文件的版本号为1.0。

用户在使用APP时,如果遇到和这个数据文件相关的问题,打开文件后,根据版本号就很容易知道与最新的数据文件有什么不同,便于解决问题。

对于一些二进制文件,不方便在文件数据里增加版本号或时间戳,就只能像图片文件一样,服务器返回给APP特定的字段表示版本号或修改时间。APP端除了保存文件外,还需要保存这个字段的数值。

由于中国行政区域地址数据比较大,做成JSON格式文件有400多K,压缩后也有30多K,用户使用APP编辑地址数据时,如每次都从服务器下载这些数据,比较耗费数据流量、数据下载时间和解析时间,会导致客户体验不好,有以下4种解决方案。

(1)在服务器和APP中都内置一个包含地址数据的文件,在地址编辑界面会先获取服务器端的地址数据文件版本号或时间戳,并和本地数据文件的版本号或时间戳进行比较,如果一致就启用本地的文件,如果不一致就从服务器下载新的文件并覆盖本地的文件。

毕竟地址数据不会经常变更,这样就大大减少了从服务器获取数据的次数,从而改善用户体验。

为了进一步减少传输的数据量,还可以采用增量更新机制。服务器每次只把有变动的地址数据发给APP,并在每个有变动的地址数据中增加一个字段,用于区别数据变动属于增、删和改中的哪种情况。APP根据数据变动的类型,处理存放在本地的数据。

(2)如果APP支持定位功能或在首页让用户选择当前所在的地区,如自动定位到上海,或用户选择了上海,则在地址编辑界面从服务器只获取上海市的二、三级地址数据,这样从服务器获取的数据量就很小了,而且可以始终获取到最新数据。

(3)可以根据用户当前设备的IP地址判断用户所在的地区。在地址编辑界面,从服务器只获取用户所在地区的二、三级地址数据,那么从服务器获取的数据量就很小了,还可以始终获取到最新数据。

(4)因为直辖市、省和自治区这些一级地址区域的名称基本不会改变,所以可以把这些数据内置在APP中。在地址编辑界面,首先让用户选择一级地址区域,然后再从服务器获取所选区域的二、三级地址数据,那么从服务器获取的数据量就很小了,还可以始终获取到最新数据。

(1)用数据变更的时间戳判断是否更新APP本地的数据。

例如,电商APP与电商网站之间的购物车和收藏夹等数据的同步(用户可能会随时改变数据),就可以采用时间戳作为判断依据,具体方案如下所述。

(2)用数据失效的时间戳判断是否更新APP本地的数据,就是用数据变更的时间戳加上数据有效期来进行判断。

电商APP从服务器获取的商品数据有一定的时效性,为了减少和服务器的交互,就可以采用此方式判断是否需要从服务器更新数据。

例如,服务器向APP返回商品详情数据时,除了商品属性外,还要加上数据失效的时间戳,如下所示:

{
    "product":{
         "expiry_time":1625065977
         "name":"铅笔"
         "price":1.00
         "image_url":"http://www.test.com/image/test.jpg"
    }
}

APP每次进入商品详情界面时,通过比较当前时间和本地存储的数据失效时间戳,来判断是从服务器获取数据,还是用本地缓存的数据。

以上两种方式都需要保证APP和服务器的时间同步。

(3)用PUSH机制推送实现。

电商APP的首页通常有视频或图片广告,这些广告的数据量都比较大,如每次进入首页都要从服务器获取,比较浪费流量。每次服务器端变更了数据,APP端又需要及时更新,这时就可以用PUSH机制推送,即服务器端变更数据时发送PUSH消息给APP,APP就从服务器获取数据。若APP没有收到PUSH消息,则使用本地存储的数据。

有些数据,像用户账号,通常在服务器端和APP端都会保存。如果在APP端修改这样的数据,最好是先向服务器发送修改请求,等接收到服务器成功修改数据的消息后,再修改本地存储的数据;如果服务器修改数据失败,就不修改本地存储的数据,这样可以避免APP端修改了,服务器端没有修改的情况发生,保持两者的数据同步。

目前用户使用的手机硬件性能与服务器相差甚远,尤其是Android手机,千元机以下的低端机占据很大份额,所以对数据的处理和业务逻辑等能在服务器端实现的,尽量在服务器端实现,APP端只负责显示和处理用户交互。

这样可以减少APP对系统资源的消耗,改善用户体验;且当业务逻辑发生变化时,只需更新服务器的代码,不需要用户升级APP,有利于整个系统的运营、维护和升级。

存储在APP自身文件夹里的数据,在用户清除缓存或卸载APP时会被清除,所以在把数据存储在本地的同时,最好也要同步存储在服务器端,或只把数据存储在服务器端,用户查看时从服务器下载,如购物车和收藏夹等数据。

验证安全的功能都放在服务器端实现,如对密码的校验,不在APP内做任何验证。如果要做验证,势必会在APP端存放一些敏感数据,APP一旦被破解,后果很严重。

APP在向服务器发送请求时,在HTTP的请求头中应添加要求支持gzip的key-value,设置Accept-Encoding的类型为gzip,服务器应把数据使用gzip压缩后再返回给APP,以减少数据流量,加快APP响应速度。

若支持一个账号在多个设备上同时登录,用户在某个设备上修改账号和密码后,服务器应自动注销其余设备上此账号的登录状态。

接口文档通常由服务器端负责接口开发的同事维护,对于每个接口的描述,应包括以下内容。

可以使用版本管理工具,如SVN管理接口文档。每次把文档提交到服务器时,都要填写修改说明,方便APP开发人员和测试人员了解接口的变更。也可以使用在线API接口文档管理工具管理接口文档。


相关图书

Android App开发入门与实战
Android App开发入门与实战
Kotlin入门与实战
Kotlin入门与实战
Android 并发开发
Android 并发开发
Android应用案例开发大全( 第4版)
Android应用案例开发大全( 第4版)
深入理解Android内核设计思想(第2版)(上下册)
深入理解Android内核设计思想(第2版)(上下册)
Kotlin程序开发入门精要
Kotlin程序开发入门精要

相关文章

相关课程