写给产品经理的技术书:客户端、服务端和交互相关技术
产品经理有三大领域的技术是需要去攻克的,分别是:客户端相关技术、服务端相关技术、交互相关技术
一、客户端相关技术
1.iOS和安卓产品差异
1.1 应用的设备不同:
IOS和安卓最大的区别在于本身所应用的设备不同。IOS系统主要是应用在iPhone、IPad、itouch设备上的操作系统,安卓系统主要是应用在安卓智能手机上的操作系统。
1.2 面向人群不同:
IOS系统面向的是中高层收入的人群,有人称它为“高富帅”系统,而安卓系统则是面试中低层的大众人群,有人称它为“屌丝系统”。
1.3系统的开放性区别:
安卓拥有自己的开源计划AOSP(Android Open Source Project),只要遵循GPL和Apache Licence 2.0开源协议,那么你就可以使用安卓源代码进行二次开发。而安卓由于源代码开放,自然可玩性也比iOS高。此外,安卓比iOS开放了更多的应用接口API,可以很自然地利用安卓实现很多在iOS上不折腾就没法做的功能。在安卓,可以随心随意地更换输入法,随意用任何浏览器打开链接,随意从任何途经安装程序,随意调用第三方程序分享文件——这些在iOS上不越狱都做不到,即使越狱也未必比安卓做得更好。
1.4系统的安全性区别:
IOS系统是一款比较强大的操作系统,在IOS系统运行的程序不管程序多大都不会造成死机,玩起来非常的流程,而且系统的安全性比较高。
安卓系统是属于代码系统,如果所有的应用程序需要下载下来之后才能玩,系统用久之后会经常出现卡机或者是死机的现象,而且安卓系统还存在恶意的插件在系统上自动运行,系统漏洞多,导致个人资料被盗、系统耗电大,流量消耗大等,系统安全性相对来说比较低。
1.5开发难度不同:
苹果提供完整高效xcode,sdk等开发环境,ios系统一脉相承,ios版本之间的软件通用,即开发一款产品苹果所有设备都能运行。其硬件的强大也让开发变的更加容易。
2.Web前端技术-HTML、CSS、JavaScript
- HTML、CSS、JavaScript共同构建了你看到的任何一个网页展示和交互:
- HTML(HyperTextMarkup Language)超文本标记语言
- CSS(Cascading Style Sheets)级联样式表
- JavaScript一种脚本语言,主要用于前端页面的DOM处理
文本的意思,应该大家都明白,就是你随手在桌面上建立一个txt,这就是一个文本文件。
那什么是HTML超文本标记语言呢?超文本就是超越文本的意思呗,超越文本的意思就是它已经不仅仅是简单的文本,比普通的txt要高级一些,那到底高级在哪里呢,是第二个词Markup(标记的意思),就是对一个普通的txt里面的文字进行标记,标记其中的一段为title,标记另一段应该另起一行,标记任意一段为某个意思。最后超越了普通文本的标记,这些记号对普通文本的修饰,就构成了一套规则,这套规则就是html。
CSS中文名叫级联样式表,也是一个超别扭的名字,但是样式大家都应该懂,就是长什么样子,类比到生活中,就是HTML只是你的肉体,你总要穿上衣服,戴上牙套,穿双鞋再出门吧。再举刚才盖房子的例子,你定义好了各个空间,并且房子也盖起来了,你要装修,比如客厅用什么壁纸,卧室的地板用什么样子,CSS就是起装修作用的,必须要和HTML一起配合使用。
JavaScript是一种脚本语言,他在网页中使用的主要场景是控制HTML中的每一个元素,有时候可以把有些元素删除,有时候要添加新元素,你常常遇到过这样的场景,点了一个按钮,这个时候会有一个网页上从没有过的元素出来,其实就是利用JavaScript实现的。你的房子已经装修好,贴上了墙纸,铺上了地板,桌子,板凳,沙发都已经摆好了,一切都完美了,可是一切都是静态的,作为一个有生活情趣的人,你总是要买些新家具,或者想把茶几换个位置,这个时候这种在这个屋子里的所有移动,添加,减少物件就只能靠JavaScript实现。
当前互联网上的任何一个网页,都是由他们三个构建起来的,虽然简单,但你不可不知。
3.实时更新移动客户端技术–React Native
做为一名产品经理,你是否遇到过这样的窘境,“帮我把字体调成16号呗,颜色变成#FFFF00FF,老大说这里最好改一下”,作为一名app的开发只能无奈但心里窃喜的告诉你,“只能等下个版本了,必须要重新发布才能改”,如果你问为什么不能改了就生效啊,那说明你对技术的理解要么真的很差,要么你就是知道这项React-Native新技术所爆发出来的力量。
React Native是Facebook推出的一个用JavaScript语言就能同时编写ios,android,以及后台的一项技术,2015年9月发布了android版本,又在程序员里面掀起了一波小高潮,不断有喜欢尝鲜的程序员投入到这个领域。用大白话说,就是从此一名程序员自己就可以创业了,他只用这一门技术,就可以同时写出androidapp,ios app,以及后台应用程序,并且,请注意这里,它可以做到实时热更新(就像网页一样,改了一个字体,随时可上线),app也能做到随时都能更新了,第一段讲的那个需求可以分分钟秒杀解决,不用新发版本,只需在服务器改动一下代码即可,真的很牛逼。
4.Android应用权限
目前国内Top 100的热门应用,来看看它们最喜欢的申请的权限是什么,以及拿到这些权限后可用做些什么事情:
网络访问权限
互联网产品,当然要联网才行啦,所以每个应用都申请了这个权限
修改或删除外置存储中的内容
往用户的SDCard上随意读写文件的权限。当你的手机用了一段时间后,发现SDCard上面乱糟糟的,什么奇怪的文件名都有,就是因为这个权限,每个应用都想着你手机里留下一些痕迹。其实为了存储数据,系统给了特定的存储空间,这并不是应用必须要用的权限。
读取手机状态和身份
有了这个权限,可以获取到手机的唯一识别码IMEI,很多应用用它来做为单一用户的标识,没什么可怕的。
查看WLAN连接
可以查看用户当前的WiFi接入点信息
控制振动
这个没什么好说,就是要让你手机有动次达次的效果
检索正在运行的应用
可以查看用户当前运行了哪些应用,瞅瞅你平时喜欢用些什么应用,也可以看看竞品的活跃程度:)
防止手机休眠
在锁屏后为了降低功耗系统会进入休眠状态,但是很多应用为了维持后台运行,就会申请这个权限,这也是Android系统比较耗电的原因之一,都是应用希望知道用户的位置(基于网络),O2O这么火的年代,为了提供更个人化的服务,各路应用都希望知道用户的当前位置。
开机启动
要想日日夜夜的陪伴,那就得一开机就启动,也是耗电的罪魁
相机
帮你打开相机,扫一扫二维码,拍一拍片片
在其他应用之上显示内容
桌面上那些飘来飘去的东西,或者你正用着一个应用,其它某个APP又突然蹦了出来盖在上面,都是用的这个权限
精确位置(基于GPS和网络)
三胖想定点轰炸你,就得用这个权限,获取精确的GPS位置
安装快捷方式
很多应用希望用户更方便的启动自己,都喜欢往桌面上发送一个快捷图标,更有丧心病狂的应用,会发送多个图标到桌面。往往新买一个手机,安装10个应用,桌面上会出现20个以上图标的,就是因为它
录音
每个应用都有一个成为微信的梦想
卸载快捷方式
悄悄的将自(友)己(商)的图标删掉:)
读取联系人信息
大家都对这个权限很敏感,应用有了这个权限,就可以读取你的通讯录,不怀好意的应用还会偷偷上传,哪天你收到垃圾短信也不必奇怪,也许是你的某个好基友“出卖”了你
停用屏幕锁定
你得一直看着我,不要让屏幕锁定了
发送短信
有了这个权限,就可以花用户的钱,给自己发条短信。感觉应用都没有什么正当理由来获取这个权限
读取短信
查看用户的短信,感觉这是老大哥干的事,普通应用拿来是够恶心的
5.Android休眠状态
(1)任何一个应用申请了wakelock锁,待机(按:什么是待机?待机与屏幕黑、锁屏、休眠的关系是什么?)时没有释放掉,系统是不会进入待机的,直到所有应用的wakelock锁都释放掉了,才会进入待机。
(2)如果不进行特别的设置,Android会在一定时间后屏幕变暗,在屏幕变暗后一定时间内,CPU也会休眠,大多数的程序都会停止运行,从而节省电量。
(3)Android手机有两个处理器,一个叫Application Processor(AP),一个叫Baseband Processor(BP)。非通话时间,BP的能耗基本上在5mA左右,而AP只要处于非休眠状态,能耗至少在50mA以上,执行图形运算时会更高。一般手机待机时,AP、LCD、WIFI均进入休眠状态,这时Android中应用程序的代码也会停止执行。Android为了确保应用程序中关键代码的正确执行,提供了Wake Lock的API,使得APP可以通过之阻止AP进入休眠。但不一定必要,首先,完全没必要担心AP休眠会导致收不到消息推送。通讯协议栈运行于BP,一旦收到数据包(按:收到TCP数据包才会唤醒AP,UDP包不会唤醒),BP会将AP唤醒,唤醒的时间足够AP执行代码完成对收到的数据包的处理过程。其它的如Connectivity事件触发时AP同样会被唤醒。那么唯一的问题就是程序如何执行向服务器发送心跳包的逻辑。你显然不能靠AP来做心跳计时。Android提供的Alarm Manager就是来解决这个问题的。Alarm应该是BP计时(或其它某个带石英钟的芯片,不太确定,但绝对不是AP),触发时唤醒AP执行程序代码。那么Wake Lock API有啥用呢?比如心跳包从请求到应答,比如断线重连重新登陆这些关键逻辑的执行过程,就需要Wake Lock来保护(按:只在这些关键逻辑时,需要Wake Lock API确保不休眠)。而一旦一个关键逻辑执行成功,就应该立即释放掉Wake Lock了。两次心跳请求间隔5到10分钟,基本不会怎么耗电。除非网络不稳定,频繁断线重连,那种情况办法不多。
(4)Android设置–> WLAN–>点击菜单键选择高级–>休眠状态下保持WLAN连接的下拉列表{始终、仅限充电时、从不(会增加数据流量)},如果设置不为始终,那么我们锁屏休眠后,程序将会处于无网络状态,相应的app用户会一直处于离线模式。
(5)可以设置不同的模式,让其产生不同的休眠,比如让cpu保持运行。
- Flag Value CPU Screen Keyboard
- PARTIAL_WAKE_LOCK On Off Off
- SCREEN_DIM_WAKE_LOCK On Dim Off
- SCREEN_BRIGHT_WAKE_LOCK On Bright Off
- FULL_WAKE_LOCK On Bright Bright
6.app推送原理
传统的app架构里,通常是app主动向服务器请求数据,服务器被动的提供数据。以新闻客户端app为例:app被用户打开的时候,会通过网络(无论3g、4g还是wifi)连接到服务器上,向服务器请求最新的新闻。服务器收到请求,从自己的数据库里查询最新的新闻,返回给app。app收到服务器返回的数据,经过一系列的解析处理操作,最终把最新的新闻呈现给用户。一次通信就完成了。
然而如果此时服务器上又有了新的新闻,无论多么重要,在用户没有主动刷新的情况下,是没有办法让用户看到的。推送就是为了解决这样的困境的,它给了服务器一个展示自我的机会,主动连接上所有的app,告诉他们我有新的新闻了,你们再来请求一次吧,于是收到推送的app(即时此时已经被用户关闭了)又去服务器请求最新的新闻,这样用户就能看到最新的新闻了。
从技术上来讲,实现一个推送系统需要服务器端和终端的配合。一种方法是轮询,也就是不停的向服务器发起请求。这其实很好理解,作为app,我既然不知道什么时候会发生新的新闻,那我一遍一遍的问好了,而且我知道这样一定会成功的。显而易见,这种方法app端费时费力不说,电量流量也扛不住啊,服务器要处理如此量大的请求,必然也是非常头疼的。
另一种方法是服务器和app建立一个长时间连接的通道,通过这个通道,不仅app可以向服务器请求数据,服务器也可以向app发送数据,看起来非常完美,但是如果app被用户关闭的话,通道就断掉了。好在android系统给app提供了一个这样的环境,app可以启动一个后台服务来维持这个通道,即使app被关掉了,服务依然可以运行,通道依然还在工作(ios后面会讲)。回到前面的例子,你在睡觉前关掉了淘宝,但是并没有关闭淘宝的后台服务,淘宝依然可以接收服务器推送来的指令,把自己的唤醒。
那么如何维持这样的一条长时间连接的通道呢?就好比两个人打电话,一开始聊的热情有来有往,后来慢慢沉默下来了,几分钟之后,电话的另一头没有任何动静,如何知道那边的人还在呢?很简单,只需要另一头的人每隔几分钟说一个字就行。同样的道理,app会每隔一段时间向服务器报告自己还活着,就像心跳一样,服务器收到后,就知道这个通道是可以继续使用的了。
然而天下没有免费的午餐,发送心跳是有代价的,一般手机锁屏之后,为了省电CPU是出于休眠状态的,然而发送心跳就会唤醒CPU,必然会增加电量的消耗。这还只是一个长连接通道的情况,如果手机里装了2、30个带有推送的app呢?先别急着抱怨,聪明的android工程师和ios工程师早就想到了这一点,他们分别设计了GCM和APNS来解决多个app有多个长连接通道的问题。
以APNS为例,ios开通了一条系统级别的长连接通道,通道的一端是手机的所有app,另一端是苹果的服务器。app的服务器如果有新的消息需要推送的话,先把消息发送到苹果的服务器上,再利用苹果的服务器通过长连接通道发送到用户手机,然后通知具体的app。这样就做到了即使手机安装了100个app,也只需要向一条通道里发送心跳。
回到Android,系统提供的GCM只能在Android2.2以上才能使用,3.0以下必须要安装Google play并登陆了Google账号才能支持。而国内发行的手机大多是阉割掉了google服务的。因此,对于Android系统来说,各家app只能各显神通,开发自己的专用长连接通道了。然而这时候他们遇到了app的天敌:管家和卫士们。
前文说了,app想要及时收到服务器推送的消息,关键在于自己与服务器的长连接通道不被关闭,也就是自己的后台服务可以一直在后台运行,而管家和卫士们的一键清理功能就是专治这种“毒瘤”的。道高一尺魔高一丈,app在与管家和斗士们的长期斗争中,总结了一系列躲避被清理掉的方法,什么定时自启能力、什么相互唤醒、什么前台进程等等,当然这就是另一个话题了,我们后面会讲到。
总结起来,app和后台的连接方式有两种。一种叫pull,也叫轮询,就是定期的不断向后台请求,缺点是耗电,费流量,不环保。对于一名有追求的程序员,他应该会比较恶心这种方式的,你千万不要对他说,我不管你怎么实现,我就要这种效果这种笨蛋话了,凡事应该找到最优路径。
另一种叫push,app和后台一直维持了一条通信通道,两端不定期的就会偷摸的约会,告诉对方“I‘m Here”,也能顺带把信息互相携带了。缺点是要维持一条长连接通道,这条通道容易被其他程序杀死,要多想复活办法。
7.应用程序、进程和线程
应用程序都是用来“应用”的,也就是我们平时所说的“打开”、“运行”某个应用程序。
在每个平台上,应用程序都会有一个供操作系统使用的“入口”,这个“入口”就是让系统通知应用程序“运行”的关键所在,也就是系统启动应用程序的门户。当我们点击桌面上应用的图标,系统就会收到一条指令:“启动XX应用”,这时系统的应用加载器就会找到应用程序的安装目录,并为应用程序创建一个“进程”,进程创建后,系统就会利用“入口”把应用程序的“逻辑”和“数据”加载起来,并根据应用程序的需要为进程分配资源,如内存、cpu等,这样,应用程序“运行”的条件就满足了。
进程中会包含若干“线程”,这些“线程”共享进程的资源,并且按照应用程序中指定的“逻辑”完成既定的任务,如启动闪屏,播放视频,响应用户的交互操作等。
8.同步和异步
有一天,你找到公司刚来的程序员小 T,跟他说:“我们要加个需求,你放下手里的事情优先支持,我会一直等你做完再离开”。小 T 微笑着答应了,眼角却滑过一丝不易觉察的杀意。
切入正题。世界上的所有事情大致可以分为同步去做和异步去做两种。你打电话去订酒店,电话另一边的工作人员需要查下他们的管理系统才能告诉你有没有房间。这时候你有两种选择,一种是不挂电话一直等待,直到工作人员查到为止(可能几分钟也可能几个小时,取决于他们的办事效率),这就是同步的。另一种是工作人员问了你的联系方式就挂断了电话,等他们查到之后再通知你,这就是异步的,这时候你就可以干点其他事情,比如把机票也定了之类的。
同步和异步的区别就在于,在下达了执行任务的命令后,是等到执行完成之后才能得到结果呢,还是马上就知道了结果(尽管是不确定的答案)。
计算机世界也是如此。我们写的代码是交给CPU去执行的,在这个过程中经常面临是让CPU同步执行还是异步执行的选择。比如我写了一个APP,它可以帮你下载网络上的一个文件。当你输入一个文件的网址,按下下载按钮的一瞬间,CPU就收到了一个下载文件的任务。
我们先想象一下同步执行时什么情况。CPU立刻停掉了手头的事情,包括绘制界面、对用户的点击做出响应等等,倾尽全力去帮你下载文件。但是,这时候你会发现,你的屏幕再也没有响应了,整个系统就像死了一样(废话,CPU都被你的下载任务抢走了)。过几秒钟,如果是Android系统则会弹出一个的提示,用户非常感动,然后无情的卸载了这个APP(尼玛裤子都脱了,你让我看这个)。同样的情况异步执行要好很多。CPU马上告诉你任务已经被受理了,等下载完成我会通知你的。于是呢,屏幕照样刷新,用户点击都能做出处理,就好像没有下载过一样。然而CPU并没有闲着,它开启了一个线程,专门处理这个下载任务(还记得之前讲过的线程的概念吗?不用担心我们下面会详细讲)。过了几个小时下载完了,你会收到一个通知,告诉你任务执行的结果。
一般情况下计算机通过多线程来实现同步,你可以把线程看做是富土康生产iPhone的一条生产线。它给生产一台完整的iPhone提供了所有必须的资源:包括人力,原料,设计图纸等等。生产任务来的时候,如果是同步的,那一条生产线就够了,所有的小伙伴们蜂拥而上,不一会儿就搞定了。如果是异步的,那就必须新建一条生产线(好在CPU创建线程的成本并不高),分一部分资源到新的生产线上,这样可以同时生产两台手机。那么生产线可以无限制增加吗?答案是不行的。一是异步会面临资源竞争的问题。比如说8条生产线都要组装电池,但是电池原料只有4份,那么必然会存在其他4条生产线等待的其情况,如果资源竞争比较频繁,甚至异步的执行效率要低于同步。二是异步会导致状态难以管理。比如车间主任想要统计一共生产了多少iPhone,就必须要询问完所有生产线才能得出结论,而且这个询问过程必须要停掉所有的生产线,同步来做。
讲到这里,回调的概念呼之欲出。上面异步任务的整个过程是首先你要把自己的信息给异步任务执行者,等执行完成的时候,执行者可以通过这些信息找到你,并给你一个通知。把自己信息给别人的过程叫做注册,别人找到你给你通知的过程就叫做回调。上面的例子,你把自己的联系方式给了酒店工作人员叫做注册,工作人员完成任务后联系你叫做回调。但是回调的概念其实非常广,这里可以抽象成先把要做的事情注册给别人,等条件满足的时候别人再回过头来调用你的模型。程序上响应一个按钮点击之后要做的事情也是用回调来做的。程序员先把用户点了按钮要做的事情先写好(比如要下载文件),注册给系统。等用户点击到按钮的时候,系统就会回调你下载文件的代码。
回到前文,了解了同步、异步以及回调之后,你会这样跟小 T 说:” 我们要加个需求,你抽时间支持下,等你做完了记得通知我。“小 T 欣然接受,眼角闪过理解万岁的泪光,回头就把这事儿忘了。
9.渲染
计算机、浏览器、手机app的渲染道理一模一样,你在显示器上看到的一切也都经历了类似的过程,大致分为三步:测量、排版、绘制。拿支付宝手机App举例,我们进入界面之后看到了那么多按钮或TAB,计算机是如何知道哪个按钮该摆在何处,应该多宽多高,以及程序启动的时候应该是呈现出什么样子呢?
计算机里面存储的全部是01组成的串(这些串既有程序代码也有相应的数据),他们静静的躺在你的硬盘或sd卡中,当你点击手机app上的支付宝图标的时候,这个时候存储设备中的代码和数据迅速被载入内存,并加载执行。当程序运行到构造界面的时候,这个时候计算机像画家一样开始测量,每一个按钮的宽高(其中是有一大堆算法或者说规则在默默的计算,比如一个按钮在另一个上方,如何不和其他的按钮重叠等等)。知道了多宽多高之后,计算机开始计算每一个按钮应该摆在屏幕上的什么位置。大小、位置都明确之后,计算机开始绘制,也就是把相应的颜色或者图片资源从CPU输送到显卡,显卡把这些数据发送给显示器的缓冲区,屏幕的下一次刷新将这些新数据更新到显示器上,整个渲染(呈现)过程结束。
说了很多废话,想说清楚的是,渲染是通过一些列计算并呈现的过程,其中包括测量、排版、绘制。你在任何屏幕上看到的任何一个图形,无一例外,都经过了这三个过程。下次和程序界的朋友沟通展示慢,顿问题的时候,你可以很随意的说句,感觉整个渲染过程不是很流畅,保证你们的交流会很得心应手。
10.QQ快的原因
(1)QQ会在用户上传、下载图片等连接服务器操作时,结合其网络情况选择周边最快的服务器;
(2)QQ会对用户每天使用的网络进行记录和分析,预测出用户在哪个时段可能用哪个网络(如3G/4G/WIFI),并在相应时段自动连接相应情况下最优的服务器;
(3)图片下载优化:
- 渐进传输:先传输图片部分数据和像素模糊显示,后续再将剩余数据和像素传输完成从而清晰显示;
- 图片转码:同等图片质量下图片更小的编码技术;
- 图片适配:较慢网络如2G或较低像素终端情况下,下载较低质量但更小的图片,前者为提高速度,后者为节省流量;
- 预加载:为方便用户快速打开而预加载一些大图,可通过银行家算法加以控制,用户看了的话就加载对了,没看的话就说明无效加载了,累计的无效加载到一定阀值就不再进行预加载。
11.图片资源处理
相信广大的产品经理同学肯定希望自己的产品UI能够美美的,让用户们赏心悦目。要做到这一点,主要就靠咱们伟大的视觉设计师出的各种图片资源。小到应用程序的图标或者按钮,大到启动时的闪屏,无不是出自设计师之手。当图片被打包到程序中后,他们就被正式的赋予了为广大用户谋眼福的使命。
然而,图片的大小是固定的,而使用图片的设备分辨率却千变万化。比如一张1280×720分辨率的全屏闪屏图片,可能会被加载到1080P、720P、480P甚至320P的设备上,除了720P的设备外,在其他三款设备上1280×720的图片都会产生“缩放”。我们都知道,图片的内容都是由像素组成的,比如1280×720的图片由921,600个像素构成,720P的显示设备屏幕上也正好是921,600个像素,这样图片的每一个点都可以与屏幕上的点一一对应,进而完美的呈现图片的细节。480P乃至320P的设备,他们屏幕上的像素点个数远远小于921,600(480P设备38万个像素点,320P设备15万个像素点),屏幕上的像素无法做到与1280×720图片像素的一一对应,这种情况下,为了让低像素数的屏幕能够完全呈现高像素数图片的内容,图片的一些细节(像素)就会被丢弃,以480P的设备为例,1280×720的图片就会被显示成800×480个像素,图片看起来被“缩小”了,也就是系统对图片进行了“缩放”。这种“尺寸”(分辨率)从大到小的缩放会丢失一些细节。
当1280×720的图片被加载到1080P(207万像素)时,情况就更糟了。我们期望自己的图片可以占满用户的屏幕,但是即便把图片中的每一个像素都一一对应的填充到屏幕上,还是会有一半的像素没有内容,这种效果大家可以想象。不过好在聪明的软件工程师们早就实现了一系列的方法来让大家避免陷入这种图片被“放大”或“缩小”后图片质量变差的窘境,这些方法被叫做“插值算法”。
顾名思义,“插值算法”就是在原有的像素值基础上,插入或修改一些像素值,并尽最大可能保证原图的特征。当前比较流行的插值算法主要有“邻近插值”和“双线性插值”,具体的算法这里不再冗述,感兴趣的同学可以在网上随处搜到。
通过上面两段的描述,我们可以看到,当图片的内容无法与屏幕上的像素点进行一一对应的时候,就会产生“缩放”,虽然当前有一些手段可以尽量的避免缩放对图片质量造成的影响,但显示效果或多或少都会收到影响,并且缩放的程度越大,效果损失的越严重。所以有的系
统会提供另外的机制尽量避免“缩放”的产生,或者把“缩放”带来的副作用降低到最小。比如安卓系统就为应用程序的图片资源定义了一组文件夹,每个文件夹对应一种屏幕的像素密度/分辨率,在不同像素密度/分辨率的设备上从对应的文件夹中取图片资源,尽量的减少或避免“缩放”,进而最大化的还原设计师们的原始设计。
12.Cookie和广告联盟
相信大家都有相同的经历,在浏览网页的时候,有的广告竟然知道我近期搜索过的关键词,也有一些广告竟然知道我近期要买的东西。那到底是什么东西悄悄的把我们的信息出卖了呢?答案就是本文的主角:Cookie。
之前的文章讲过我们浏览一个网页的时候,浏览器在做什么事情。它不断的向服务器请求数据,服务器不断的回答数据。但是这个过程有个缺点,就是每次请求都是独立的,服务器并不会记下客户端的信息。打个比方说,你每天都去楼下马大姐那里吃烤串儿,但是马大姐记性不好,你一走她就不认识你了。这时候你就想,如果我每次去吃烤串的时候,主动给马大姐提供一些自己的身份信息,说不定还能打个折呢。这个身份信息,在技术上就叫做Cookie。
Cookie是浏览器每次向网站服务器请求数据的时候,携带的一些额外信息,这些信息一般非常少(最多4KB),主要就是为了解决服务器记性不好的问题。当然Cookie究竟需要携带什么信息,其实是由服务器决定的,比如你登录了新浪微博之后,服务器就会要求浏览器把你的账号写到Cookie里,下次你请求你的关注列表,浏览器就会带上这个Cookie,一起发送到服务器,这样服务器就会知道你是谁了。
Cookie每个网站都会有很多,但它们是隔离开的。也就是说,百度只能访问到百度存在浏览器的Cookie,微博只能访问到微博存在浏览器的Cookie,百度是拿不到微博的Cookie的,这一点由浏览器保证。
现在我们来看下开头广告的事情。我们的搜索关键词被百度保存在了浏览器的Cookie里,但是这个广告是出现在一个博客网站上的,按上文的理论,这个博客网站只能访问到它自己存在我们浏览器的Cookie,为什么能访问到百度的Cookie呢?这时我不禁想起程序界有一位祖师爷的教训:你所有的痛苦和困惑,都可以从源代码里找到答案(read the fucking source code)。小弟看了下这个页面源码后,发现广告其实是博客网站的程序员从百度那里拿了一段代码放到自己的页面上,用户在请求广告图片的时候,还是去百度请求的,自然百度也就能拿到带着搜索关键词的Cookie了。拿到Cookie的百度就可以根据关键词匹配他们的广告推荐给你,这种广告因为推送的都是用户感兴趣的内容,杀伤力特别大,被称为精准广告。
成千上万的网站加入了搜索引擎的广告联盟,这样你在浏览其他网站的时候,都会看到带有自己关键词的广告,哪天你搜索了一些不想让人知道的东西,嘿嘿,这些广告就会跳出来出卖你。
13.动画原理
我们先来说几个简单的概念。动画过程中的某一张静止画面叫做一帧(Frame),一个动画每秒钟播放的帧数叫做帧率(单位是FPS),一般来说当帧率达到30帧每秒的时候,人们就会觉得这个动画很连贯了,当帧率达到60帧每秒的时候,这个动画就会非常流畅了。像下面这个点击按钮弹出菜单的动画,要达到每秒钟60帧的帧率流畅运行,每一帧要花多久来展示呢?如果我没算错的话,应该是16毫秒左右。
16毫秒,也就是留给是你的手机渲染一帧的时间。还记得我们之前讲过的渲染的概念吗?在这16毫秒期间,你需要为屏幕上的所有图片、按钮、文字测量好大小,排布好位置,然后交给显卡绘制出来。现在手机配置越来越强大,但是屏幕分辨率也越来越大。分辨率越大意味着每一帧要画的像素越多,CPU和显卡的负担也越重。这时候万一哪个2B程序员插了一段从网络上同步下载苍老师.avi的代码进去,导致每一帧绘制都需要100多毫秒,这时候用户就会看到动画一卡一卡的,这个用户多半是要流失了。
那么从技术上来讲如何实现一个动画呢?这里需要操作系统提供三个东西,一个是刷新屏幕的命令,我们假设叫refresh,我们的程序发出了这个命令后,手机就会刷新一次屏幕。另一个是绘制图形的命令,假设叫drawFrame,这个是一个代表,具体可以是drawCircle(在屏幕上画个圆圈)、drawRect(画个长方形)、drawText(画一段文字)等等。最后一个是定时器,假设叫scheduleNextFrame,它的作用是告诉操作系统下一帧的时间。
假设我们要绘制一个500毫秒的动画,它展示一个圆放大30倍的动画过程。程序员会这样
- 写程序:
- 动画开始:
- 第一帧:drawCircle(1倍)—>refresh—>scheduleNextFrame
- 第二帧: drawCircle(2倍)—>refresh—>scheduleNextFrame
- 第三帧: drawCircle(3倍)—>refresh—>scheduleNextFrame
- …
- 第30帧: drawCircle(30倍)—>refresh
- 动画结束。
这种动画实现起来非常简单,iOS和Android都内置了几种常见动画类型,如缩放、平移、渐变、旋转等等。程序员只需要设置好动画时长(前面的500毫秒),动画中要变化的东西(前面放大多少倍),然后发出start的命令就可以了。
还有一种动画叫有交互的动画。它由用户手指的操作触发刷新屏幕,一个典型的场景是我们滑动朋友圈列表的时候,列表之所以跟着手指动,就是因为手指的移动触发了屏幕的刷新。这个场景延伸出去就是游戏了,游戏的界面刷新也是由用户控制的。从实现成本上来说,程序要要实现一个没有交互的动画很简单的,如果动画不是特别复杂,基本上从设计师那里拿到资源和设计稿,就可以大概做出个雏形。有交互的动画因为要处理用户手指的触摸事件,会稍微麻烦一点,但基本原理都是相通的。
二、服务端相关
1. 302状态码
在互联网世界里面,已经存在数亿量级的网页,如何管理及标识每一个网页以及方便浏览器寻址到此网页并展示呢?其中,每个网页都对应着一个URL(Uniform ResourceLocation)地址,也叫网址,类似于一个真实世界中的门牌地址一样,真实世界中标识了物理地址(如北京市朝阳区某小区张大妈家的门牌号)。同样道理,网址标识了一个web页面所在的互联网里面的真实地址(这个页面处于www.baidu.com/file/1.html,处于baidu服务器file路径下的1这个文件)。
当你用浏览器点击一个页面链接的时候,随即你看到了一个新的网页展示在浏览器内,在这个过程中,浏览器其实是在不断的接收服务器端的应答(这个应答是服务器端的状态,所以返回码叫状态码),从而来决策下一步来做什么(尽管大部分情况下,你毫无感知的就打开了你想要的页面),这个应答即状态码(status code),在http协议里面,以三位数标识,共分为五类:分别为1××,2××,3××,4××,5××。一些常用状态码如下所示:
301和302表示重定向,301表示这个网页已经永久的由服务器的A路径下移动到路径B下,而302表示临时移动到B路径下,对应到Url地址也即http://baidu.com/file/A/1.html到http://baidu.com/file/B/1.html,当浏览器访问前面一个地址的时候,这个时候服务器会告知浏览器,请到B路径下获取这个文件,随后浏览器重新发起网络请求,请求B路径下的页面,经过渲染,呈现给用户,例如淘宝的例子,请求taobao.com,收到302,从而浏览器再次请求www.taobao.com获得页面内容。
2.升级及下载加速原理
升级检测和升级方式
动检测或者用户点击检查更新以后,会像云端检索最新的版本号,md5等等。然后与本地的版本号核对。若一致,则告诉用户”您正在使用的是最新版本“若不一致,就下载最新版本。这儿分两种。
一种是全部下载。换句话说就和你当初安装这个软件出不多,只是下载那步他帮你完成了。数据也得到了保留。
一种是增量升级。其实增量升级的原理很简单,即首先将应用的旧版本Apk与新版本Apk做差分,得到更新的部分的补丁,例如旧版本的APK有5M,新版的有8M,更新的部分则可能只有3M左右(这里需要说明的是,得到的差分包大小并不是简单的相减,因为其实需要包含一些上下文相关的东西),使用差分升级的好处显而易见,那么你不需要下载完整的8M文件,只需要下载更新部分就可以,而更新部分可能只有3、4M,可以很大程度上减少流量的损失。
增量更新原因
增量更新对于版本更新不是很频繁的软件来说还可以,但对于更新较频繁的软件,使用增量更新每更新一次工作量都会很大,因为你需要考虑各个版本升级到最新版本的兼容性问题。比如一个APP有V1.0、V1.1、V1.2、V1.3、V1.4、V1.5几个版本,现在有V2.0需要发布,如果做增量升级的话需要做之前每个版本升级到V2.0的差分包,因为你不能保证用户手中的APP都是V1.5版本,这对于测试验证来说工作量太大了,并且管理起来也很麻烦。
升级离线下载原理
假如,你现在要下载QQ,普通下载,使用普通下载(浏览器),只能从腾讯服务器下载,并且只有一条下载路径。就好比上学的时候缺钱,只能从老爸手上要钱。如果你使用迅雷下载,就有机会同时获得以下几种加速方式:
多线程下载(免费)
依旧只能从腾讯服务器下载,但是能够获得多条下载路径,提升下载速度。
偶然知道生活拮据,姑姑伯伯舅舅开始偷偷塞钱给你,你手中的现金开始富余。
P2S下载(免费)
P2S=Point to Server点对服务器
除了多线程下载之外,迅雷支持从全网的其他有QQ软件的服务器下载,比如金山服务器等等,提升下载速度。
后来你认识了富二代的朋友,他们时不时请你吃饭,给你买单,你几乎不用从老爸(原始地址)那里要钱了。
P2P下载(免费)
p2p=Peer to Peer点对点
有了多线程和P2S加速之后,当其他用户同时在下载QQ时,你也可以直接从对方PC下载,而不用经过服务器。(目前手机暂不支持P2P)
再后来,你有能力了,开始计划创业了,几个天使投资人对你感兴趣,给你投资,你再也不用找家里人要钱。
会员高速CDN下载(迅雷会员)
CDN=Content Delivery Network内容分发网络
通过购买服务器,迅雷在用户下载的同时,把文件快速下载到迅雷服务器(强大的带宽和网速),用户再从距离最近的迅雷服务器进行下载(从迅雷服务器到迅雷客户端的下载速度极快)。
然后你公司慢慢做大做强,几个大型的投资机构,如日本软银、红杉资本又给你注入了资金,你已经向高富帅迈进了!
DCDN加速(迅雷会员)
DCDN=CDN 2.0
用户通过协议之后,迅雷会把相关资源片段存储在用户PC,把每一台PC都当成服务器,其他用户下载QQ时,可以获得极快下载速度。在此过程中,迅雷须向提供存储空间的用户付费,作为对用户的一种补偿。
最后,突然发现巴菲特是你失散多年的干爹!于是,化身高富帅,赢取白富美,你登上了人生巅峰!!!
以上是迅雷加速的几种原理,用户能够获得远远高于原始下载的速度,这也是迅雷下载如此迅速的原因。
3.代理服务器
代理就是代为处理的意思,现实生活中有很多事情我们不想自己亲自动手,就花点钱找个代理摆平,说的就是这个意思。这年头,各种各样的代理都有。你看到别人名片上写着阿迪王北京总代理,就应该知道他是替阿迪王公司卖鞋的;看到有人喜当爹了,就应该知道他是在替别人的孩子当爸爸。他们有个共同特点,就是代理和本人干的是一样的事情,外人很难分辨出来。
切入正题,今天我们要讲的代理服务器,是指在我们上网的过程中访问某个服务器的时候,并不是亲自访问真正的服务器,而是先找了一个代理,由它向真正的服务器发出请求。到这里各位看官应该明白了,代理服务器架在客户端和真正服务器中间,干的是替客户端访问真正服务器的工作。
那么这里有人要说了,不对啊,小学语文老师教过我们两点之间线段最短,为什么你们不直接去真正服务器拿数据,还要到代理服务器里绕个路呢?这里可能有几种情况:一是真正的服务器藏于千里之外,我们连接不上。二是我们访问真正的服务器的速度太慢,比不上我们访问代理服务器+代理服务器访问真正服务器的速度。还有一种情况,就是通过代理服务器访问真正服务器可以隐藏访问者的身份,保护访问者。同理,我们在网络世界中一定要懂得保护自己,一次看似不经意的浏览,背后可能有好多双眼睛在盯着你。他们可以通过各种途径查到你的IP地址,然后上门找到你。所以请记住一句话,代理用的好,不怕查水表。
光说理论太枯燥,我们看几个例子。下面的截图是我们通过百度搜索点开了一个网站,上面提示“原网页已由百度转码,已方便在移动设备上查看”。也就是说,这时候我们访问的并不是这个网站真正的服务器,而是百度提供的代理服务器。这个代理服务器把真正服务器的内容返回给我们的时候,把原网页的内容改成了现在这个样子,“顺便”还插入了自己的广告(下方红框)。
现在很多手机浏览器都有省流加速功能,其实就是通过代理服务器来达到节省流量的目的。假如我要访问的原网页A需要800K的流量,但是我开启了省流加速功能后,浏览器会帮我自动连接上A的代理服务器B,B从A拿到真正的数据后,进行一些数据的压缩操作,那么我再访问代理服务器B的时候,可能只需要100K就可以浏览网页A的内容了。
4.轻量级虚拟机–DOCKER
软件开发中需要面对的一个挑战就是环境管理问题,因为软件并不是独立运行的,它依赖了很多其它的软件,包括操作系统、运行时、依赖库等等,而且对每个依赖软件还有版本要求,有一个依赖关系稍微不对,那就可能造成软件的运行异常。产品同学应该有过这种经历,从开发哥那里要一个最新版的软件来体验功能,结果装在自己的电脑上打开就挂掉,这个时候找开发哥来解决,开发哥一看就会说“哦,你这环境不对,换个Win8吧,这软件只能在Win8以上运行”,或者说“这个软件需要.Net框架,你装个.Net就好了”。
其实解决依赖环境的办法很简单,那就是所有机器都用同一套环境。但是对于一些web服务,它所依赖的软件及关联软件可能有上百个,让你去配一台机器已经要吐血了,如果让你把这个服务发布到100台不同的机器上,那么你就应该会阵亡了。同时,很有可能因为不同的机器已有的环境不同,你安装这些依赖的同时还要保证不能影响其它已有应用。
说了这么多,其实就是三个大问题,如何解决环境依赖?如何解决大规模部署?如何解决应用与应用的互相影响?Docker就是这些问题的一种解决方案,它是一个容器,也可以说是一个软件集装箱,这个箱子里面可以塞入特定版本的操作系统、数据库、服务器程序和web应用,这样一套完整的web服务就集成在这个箱子里面了,当要发布服务的时候,直接将这个集装箱放在我们的服务器船上。如果你想发布到100台机器上,没问题,只需要ctrl-c、ctrl-v,将这些集装箱复制到100台机器上,它不会在乎船的配置高低,只要能放得下就行。
如果你想发布10个不同的服务,还是没问题,你只需将这10个不同的集装箱依次排列在服务器船上,它们之间完全不会互相影响,因为各自被锁在不同的箱子里。
有的同学可能会说了,这不就是虚拟机嘛…是的,Docker算是一种轻量级的虚拟机,它比起传统的虚拟机更快,更节省资源。打个比方,虚拟机就是轮船上的豪华包间,即使它用不了这么多资源,它也霸占着不让别人使用,而Docker容器就是一个简单的集装箱,它只占据它需要的资源。
三、交互相关
1.网页与原生App如何交互
想想平时用的 App,你非常确信在浏览一个网页,然而需要登录时,它却唤起了你手机里的 QQ 或是微信,你不再需要输入帐号和密码就可以让你浏览的网页获取你的登录信息,这一切只发生在你指尖的两次点击。
而在手机上,网页越来越炫酷,你都很难区分你在点击的是一个原生界面(指 Native 应用程序,说人话就是 android app 或 ios 应用)或仅仅是一个 H5 页面。你的操作一直穿梭在网页与原生界面之间,比如一个网页中的电话号码,点击就可以拨打电话,这种网页和 app 交互这一切是如何实现的呢?
这项能力在安卓中叫做Js2Java(ios上也提供类似的技术),很好理解,从Js到Java,从网页到app,他们是双向通信,可互相调用的,市面上大量的App程序,都在利用这项技术,微信更是本质上利用这项技术打造了公众帐号整个体系,使得创业者用一个简简单单的网页就打通了帐号、身份、支付、客服、售后等一系列操作,虽然简单,但是真的将移动互联网的Web生态囊括了更广阔的内容,也是移动互联网较PC互联网更优越、更猛烈的点之一。
以Android系统为例,Android手机上的App是使用Java语言编写的,而网页中则运行着一些Html、Javascript编写的代码。Android的App是通过WebView(请亲理解成一个组件,想象WebView就是一个没有任何操作按钮的浏览器,你输入baidu.com他就打开了百度的页面)来展示一个网页的,同时WebView为网页和原生App建立一个桥梁,让网页和原生App能够看到彼此暴露的一些方法,从而达到互相操作的目的。
当然,这些操作是需要前端页面和终端程序互相协商的。虽然很多App遵守了一些相同的原则,使网页在不同的APP中都能具备相同的能力,但是如果你看到同一个网页在一个App中能够调用一些安卓系统的能力,而在另一个APP中却没有对应的能力也不要觉得奇怪(找对应App的开发勾兑一下就好了)。
一个原生应用为网页开放的能力越多,网页对原生系统的操作能力就越强,就越能做出逼近原生应用的体验。但是,这却是一把双刃剑,因为原生App开放的能力有可能会被恶意的页面利用,对用户造成伤害,如何控制能力的开放,也是需要产品和开发一起思考的问题。
例如微信是一个终端能力的宿主,拥有支付,登录,分享,获取App信息等能力,并以Js接口的形式提供给前端页面使用,前端开发则需要在微信申请对应的Js接口使用权限,才能够在微信中正常使用对应的能力。
最后总结一下,网页塑造界面的优势在于灵活,随时可以更新,而原生APP塑造的界面则能够提供更流畅的用户体验,但是却无法热更新,只能依靠发布版本来提供新功能。通过上面说的这种技术,就可以利用各自的优势,规避各自的劣势来提供更好用户体验,例如在微信中购物的展示是网页形式的,方便运营快速更新,通过Js接口调用起原生的支付界面,给用户更流畅的支付体验,提高支付成功率。
2.应用下载劫持
其实一次网络下载的过程,就像一次“网购”,当我们点击下载按钮时,就跟下载服务器下了一份“订单”,“订购”了一个文件(当然大部分是免费的),服务器确认“订单”后,就会将文件在网络中“快递”(传输)到用户的终端(手机、PC等)。下载劫持一般出现在“下订单”的过程中。
举个栗子,假设我们通过微信官网的链接下载微信安卓版本的客户端。链接地址为:http://dldir1.qq.com/weixin/android/weixin637android660.apk,整个流程大概是这个样子:
当点击了下载按钮后,客户端会通过url中的“域名”“dldir1.qq.com”来向DNS服务器获取确认“订单(下载)服务器”的IP地址,IP地址在互联网中相当于日常生活中“电话号码”,有了它,就可以连接到这台“订单(下载)服务器”,而DNS服务器就像一个存贮着大量“姓名”(域名)和“电话号码”(IP地址)的黄页。当客户端获得了“订单(下载)服务器”的“电话号码”(ip地址)后,就会连接“订单(下载)服务器”,并告知“订单(下载)服务器”客户端需要获取服务器上的“微信安卓版”apk文件,一般情况下,服务器在这个阶段确认了“订单”后,就会向客户端“快递”(传输)对应的apk文件,当客户端将文件下载完毕后,这次“网购”也就完成了。
下面,我们引入运营商(电信、联通等)网关的概念。运营商网关可以类比成日常生活中的“总机”,接入运营商的互联网设备想要能够“上网”,都需要经过“总机”(运营商网关)的转接。也就是说,在上图中的第二步,我们并不能直接通过“订单(下载)服务器”的“电话号码”(IP地址)联系到“订单(下载)服务器”,而是需要先连接到“总机”(网关),并且告诉它,我们要向某某某服务器下“订单”,经过“总机”的转接,我们才能真正连接到“订单(下载)服务器”。整个过程如下图:
可以发现,DNS服务器和网关的决策,确定了客户端“订单”(下载请求)的走向。而“下载劫持”也就发生在这两个关键节点上。
假设客户端获取下载服务器“电话号码”的DNS服务器被篡改,那么客户端可能会通过“dldir1.qq.com”查询到一个“骗子服务器”的“电话号码”(IP地址),当我们联系到这个“骗子服务器”时,我们的“订单”(下载请求)可能会换来一些奇奇怪怪的“商品”。
当我们遇到这种情况时,可以手动修改DNS服务器IP(具体方法请问度娘)来解决。然而当运营商的“总机”(网关)“出了问题”(这些“问题”一般是运营商主动造成的)时,就没那么容易解决了。假设当客户端拿着“订单(下载)服务器”的电话号码要求“总机”(网关)转接到我们指定的“订单(下载)服务器”时,“总机”(网关)对客户端说“哎呀,不要去A家下载微信了,你去我给你介绍的B家下载“XX助手”吧,比微信好用”(这个过程在技术上是被一个叫做302跳转的机制完成的,如果你不知道什么是302,出门左转,查询我们星期一的文章)。客户端是个实在人,就这样被“引诱”到B家的服务器上下载了。
“总机”(网关)和服务器B就这样沆瀣一气,来骗客户端的下载量。
3.前端和后台的数据交互与协议
目前,除了一些特别简单非联网类应用(比如计算器、闹钟等),几乎所有的应用均是联网应用(比如新闻客户端,微信等等),这些app客户端基本都只是负责用户的交互与数据收集与展示,真正的数据和服务均存储在云端。
那移动端究竟如何和后台来交换数据并展示呢?我们打个比喻,其实整个过程跟去烧烤店儿撸串一样一样的。
拿任意一个新闻客户端举例,当用户刷新的那一刻(你萌生了吃烧烤的想法),客户端开始组织数据请求(你开始穿衣洗脸打扮,并思考该去哪一家吃呢),当用户界面开始展示 loading 的时候(这个时候你正走在 “马大姐烧烤店” 的路上),经过几百毫秒的时间,这个时候请求数据已经到了服务器(你已经坐在了马大姐烧烤店的桌子上),服务器开始查看客户端想要请求哪方面的数据,是请求财经频道的,还是请求汽车频道的数据(服务员递来了菜单,问你想吃啥),服务器看懂了客户端的想法开始准备数据(你点了 20 个肉串,10 个大腰子),服务器看到你请求的是汽车频道和财经频道的数据(光着膀子的烤串师傅开始烤这 20 个串和 10 个大腰子),并给回到服务员,服务员一路小跑,将你要的串和腰子递到你的面前,这个时候相当于数据已经传回到了客户端,客户端 loading 消失,你看到了最新的两个频道的数据。
那客户端和服务器之间传输数据的格式是怎么样的呢?
现在流行的做法通常有两种,一种是类似于PB(Protocol Buffer,Google定义的一个数据传输协议,以简洁,省流,易用出名)的二进制数据(二进制数据的意思就是你打开这个文件你只能看到0和1组成的数字串,是没办法和你生活中任何认识的字母联系在一起的)传输,这种格式的好处是包小,重复的字段会被节省。
另一种是JSON(JavaScriptObject Notation),这也是一种轻量级的数据传输格式,就是用一堆中括号把数据组织起来,不像二进制,这种格式是人可读的,并且比较轻巧,所以也有大量的应用场景。下面这段数据就是JSON格式,简单解读一下,就是people对应了三个人,三个人分别是中括号间的三个花括号中的人。
总结起来,十分简单,移动端提出需求,服务器按要求组织好数据发给你,针对不同的格式,移动端自己解析,展示,完活儿。其实,不止移动端,前端网页和后台,后台和后台之间也是这个道理。
#专栏作家#
给产品经理讲技术,微信公众号(pm_teacher),人人都是产品经理专栏作家。资深程序猿,专注客户端开发若干年,对前端、后台技术略懂,热衷于对新的科技领域的探索。
本文原创发布于人人都是产品经理,未经许可,不得转载。
安卓和IOS的区别得辩证看吧,现在iOS哪里还算高富帅啊
必须手动点赞!
除了了解基础技术知识,还让我认识到了语言风格的重要性,感谢!
写的真好,有质有量
分两天读完,有质有量
非常感谢
写得太好了,我尽一口气看了两遍
太感谢了 解释的通俗易懂 谢谢
给力给力
如果产品经理不懂这些会有什么问题?
比如有些需求能不能实现,你应该能自己先过滤一遍,然后再到开发手里,另外产品经理也要关注数据的传输、存储、结构、分析等等
很有意思,感谢 ❗
可以说是非常全面了