常州定制软件开发外包公司

18912312413 在线客服 关于我们 返回顶部
企业新闻 技术分享 行业动态

Kotlin 风险高、RxJava 已过时,Android 原生开发现状分析

2020-11-07分享
    许多Android开发者经常会问我,要学会哪些东西才能成为一个优秀的Android工程师?对于这个问题,他们的描述或多或少都有些差异。但是,总体来说,我们都需要学习一系列的技能,才能成为一个优秀的Android工程师。
 
    Android原生开发的生态系统变化得非常快。至少在过去的五年时间里,我经历过很多Android的变化,并且花费大量的时间参与其中。这几年里,Google每两到三年,就会推出一组新的库和框架作为官方Android原生开发的指导方针。我花了大量的时间,回顾了这几年的变化,希望从中找出好坏。我相信,有很多的Android开发者,也和我一样。
 
    过去的一年,大量的内容被添加、被废弃或被删除,文档被更改,新的官方指导方针被引入等等。即使我以Android原生开发生态系统的的标准来看待这些问题,所发生的这些事情,都是非常疯狂的。当我开始思考这些内容的时候,我已经无法在我的脑海中描绘出一个完整的、详细的Android开发环境。
 
    因此,我决定要花一些时间去整理这些内容,然后再来写这篇文章。本文中,我会试图去总结Android原生开发的生态系统中发的事情,并且对原生开发未来的走向做一些预测。我会将我的想法分成不同的章节去叙述,这些内容没有特定的顺序,但我会把最有争议的内容放在文章最后。
 
    我希望我的这篇文章可以给你带来一些启发和帮助,但是你需要记住,本文不可能包含所有的内容,有可能会漏掉许多重要的观点,并且本文中的内容可能会包含我个人的一些偏见。
 
    AndroidX
 
    这个事情说起来有点儿疯狂,Google官方在一年半前就发布了AndroidX的预览版本。并且在一年前,AndroidX库就已经很稳定了,与此同时,Google官方也宣布不再对遗留的库进行支持与开发。(在我写这句话的时候,我想起我之前在StackOverflow上提的一个问题:为什么要将新的API放在Support库中,而不是SDK中?[1])
 
    用“稳定”来描述AndroidX这个库有点讽刺,现在关于AndroidX的任何东西都是不稳定的。Google不断地在AndroidX下添加新的库和框架,使用androidx作为命名空间。许多“老”的API(目前还不到一年)以非常快的速度发展。
 
    到目前为止,我已经将两个应用程序迁移到了AndroidX上了。一切都很顺利,我已经不记得在这个过程中,带给了我多少的“惊喜“。Google也提供了一个工具,Jetifier可将依赖于支持库的库迁移为依赖于等效的AndroidX软件包,一个非常好用的工具。然而,即使是一个很小的工程,也不能实现“一键迁移”。
 
    我也参与了没有迁移AndroidX的项目(项目并不计划迁移到AndroidX),现在也没有任何问题,所以,不迁移AndroidX,在有些情况下,也是一种可行的方案。
 
    总而言之,在新的Android项目中,建议直接使用AndroidX。并且,针对老项目,我也推荐你们将迁移到AndroidX列到计划中,虽然现在你看不到迁移AndroidX过后,带来的任何收益。无论如何,你都有可能在某个时间点进行AndroidX的迁移,所以最好能够按照自己的进度进行迁移,而不是在6个月后,你需要使用某个新的AndroidX库时,再进行紧急迁移。
 
    Jetpack
 
    在讨论AndroidX过后,还必须要提到Jetpack。在我的印象中,Jetpack开始是作为“架构组件”的一把保护伞推出的。但是到后面,引入了几乎所有关于AndroidX的API。因此,现在来看,我们看不到它与AndroidX之间的任何区别,除了Marketing和PR(即市场和公关)。
 
    当你查看Jetpack主页[2]时,会发现,这个页面并不是一个技术文档页面。这个更像是一个早期的SaaS页面。
 
    看看例子,开发者“赞誉”:
 
    开发者赞誉
 
    或者“信赖应用”列表:
 
    信赖应用
 
    这些在市场公关层面更受关注,如果Jetpack在2020年申请独立IPO,我都不会感到惊讶。
 
    不过,说真的,尝试向自己生态系统内的开发者“销售”API的想法,我觉得存在一些问题,比如说,谁会想看搜索出来第一个就是ViewModel广告呢?
 
    AndroidViewModel
 
    总而言之,Jetpack只是AndroidX库的一个聚合,所以在前面写到的AndroidX的内容,在很大的程度上也适用于Jetpack。在后面的内容中,我将单独讨论其中一些API。
 
    后台作业
 
    在Android应用程序不在前台执行逻辑时,你可以有很多种方式来实现后台运行,这也是Android动态化的用例之一。如果你不引入doze,SyncAdapter,CGMNetworkManager,FirebaseJobDispatcher,JobScheuler和最近的WorkManager,可以使用常规的启动服务(非绑定服务)来实现。这些都是Google提供的API,我可以说出所有的使用方式。当然,还有一些第三方库可以使用,例如:Android-Job。
 
    不过,Google最近宣布,他们将围绕WorkManager来统一后台任务调度[3]。这听起来非常棒,我再也不用学习那么多后台调度的知识了,只是,不知道为什么,我好像以前在哪儿听到过这句话……
 
    我们不管将来是否会统一使用WorkManager,WorkManager都还存在一些问题,比如可靠性。我不想在本文中解释为什么,但是请你一定要记住,如果你想在应用程序中使用WorkManager,实现后台作业,一定要去读一下dontkillmyapp.com上的所有内容,并且要关注一下与WorkManager相关的Google问题列表[4]。
 
    Android的后台作业由于碎片化等原因,导致它们一团糟,而且还很不可靠。
 
    在过去,我一直主张尽可能对数据和其它类型的后台处理进行同步。我或许是SyncAdapter最后的粉丝。今天,考虑到可靠性的问题,我建议,尽可能避免后台作业。如果你的老板一直坚持要使用这个功能,请将上面的链接发给他们,告诉他们,后台作业可能会需要数百小时的工作,才能实现,并且它带来的问题也会比它带来的好处多很多。
 
    很多情况下,后台作业的需求是不可避免的。但是在大多数情况下,你都可以不使用它。虽然有时候会给用户带来一些不便,但是这可能是到目前为止的最佳方案。
 
    数据库
 
    在有关SQLiteORMs的内容里面,没有什么令人吃惊的内容——Room主宰着一切。Room从2.2.0开始,添加增量注解解析支持。不过,请你一定要记住,你应用程序的架构不应该关心你使用的是什么样的ORM框架。说到Room,“架构组件”也只是一个营销术语,并不是一个技术角色。
 
    在AndroidORM框架中,与Room竞争的主要是SQLDelight。这是一个比Room老很多的库。但是据我所知,在过去的一年里,它几乎被全部重写。但新版本的SQLDelight只针对Kotlin。另一方面,SQLDelight也支持KotlinMultiplatform,所以,随着Kotlin使用的增多,我预计,SQLDelight的采用率也会随之增加。
 
    顺便说一下,在AndroidX的命名空间下,也有SQLite的镜像。我不知道这个会有什么用处,但是如果你在应用程序中直接使用SQLite,也可以对这个进行深入的研究。
 
    此外,我们也不要忘记非关系型数据库,比如Realm、Parse、Firebase、ObjectBox等等(其中一些,核心还是使用的SQLite来实现的),如果我没有记错的话,这些非关系型数据库中,大多数(甚至全部)都具有自动数据同步的功能。在之前有段时间里,它们非常地流行。但是现在,据我所知,已经不再流行了。也就是说,在短期内,我会持续关注非关系型数据库。
 
    去年,我写了一个非常复杂的应用程序,对接了一个Parse的服务。这个App具有完全的离线支持,服务端本地化,用户指定的系统和语言设定,复杂的多媒体系统等功能。我在Android端上使用了Parse的SDK。除了一些小的WTF以外,其他体验都非常棒。如果你们公司有很多后台开发人员,或者说你需要实现大量的服务端逻辑,这也许并不是最佳解决方案。但是对于仅仅只需要执行简单的CRUD操作的初创公司,这或许是一个不错的选择。
 
    一个警告:如果你打算采用数据库即服务解决方案(如Firebase),请你一定要关注长期使用的成本和影响。
 
    外部存储
 
    Android外部存储发生了一个“很有意思”的变动。
 
    如果,你在开发App的时候,把TargetAPI调整为29及以上,之前获取SD卡文件的方法,现在都不能使用了,并且不会提示任何异常[5]。现在,你需要使用Android存储访问框架来进行更细粒度的文件访问。不幸的是,Android存储访问框架的工作原理与之前的读取方式完全不同,你可能需要对代码进行大量的重构,来实现新的文件访问和读取。
 
    Google原本希望在Android10上,对所有的应用程序进行文件访问的限制,推广使用Android存储访问框架的方式进行文件访问。但是这个改动引起了社区内的强烈抗议,所以Google决定推迟推出这个功能。因此,现在即使你的应用程序在TargetAPI29及以上,也可以设置为在“Legacy”模式下可读取文件。不过,有可能在Android的下一个版本,不管你设置的TargetAPI为多少,都会限制应用程序使用新的作用域访问模式。
 
    到目前为止,我也还没有更新我应用程序的文件访问方式。但是从互联网上的讨论来看,实现新的文件访问方式是一项很具挑战的任务,虽然你的应用程序在“Legacy”模式下,没有任何异常,但是我建议你最好从现在开始,着手对代码进行重构和测试,以防发生不可控的事情。
 
    SharedPreferences
 
    在几周前,AndroidX家族增加了一个新的库,这条提交信息写道:
 
    新的库用来替换SharedPreferences,新库的名称还没有确定下来,这次提交只是为了评审和设计文档(请自行申请设计文档)。
 
    现在还没有什么值得担心的。不过,从长远来看,SharedPreferences会被废弃掉,取而代之,使用新的库来实现类似的功能。
 
    与SharedPreferences不同的是,这个新的库默认情况下使用的是异步的方式[6]。换句话说,如果你需要取某个值,你需要实现回调,通过这个回调才能拿到值。
 
    如果你对这种异步回调的原理感兴趣,你可以去看看StackOverflow上的这个回答[7]。Reddit的一个用户Tolriq分享了他们的App遇到的一个SharedPreferences的Bug,这个问题影响了万分之一的月活用户。对于一般的应用程序来说,这个问题并没有什么明显的影响。但在一些需要高可靠性的应用上,就显得很不可靠了。举个例子,如果在Android汽车上,应用程序的无响应和崩溃会引起驾驶员的注意力被分散,这将有可能导致出现交通意外。
 
    依赖注入
 
    在依赖注入领域,最大的新闻莫过于Dagger-Android被弃用,有两点需要强调一下,首先我所说的弃用,不是正式的弃用,因为官方并没有发布声明。其次,Dagger-Android并不是指整个Dagger2框架,只是指其中相对比较新的部分。详细细节可以看我的另一篇文章[8]。
 
    在Android领域也存在其他的依赖注入框架,但是我不认为他们会比Dagger更好。值得一提的是,Koin是一个不错的依赖注入框架,但是我依然觉得它也不会引起多大的潮流。它之所以会被采用,无非是这两个原因,一个是,它拥有比Dagger好很多的文档,降低了很大的学习成本。第二个是它基于Kotlin进行编写,因为Kotlin的热度,给它也带来了不少的关注。到目前为止,Kotlin的热潮几乎已经全部过去了,所以,我预测,Koin的关注也将会逐渐减少。
 
    不管这些框架如何发展,手动依赖注入的发展都会很缓慢。
 
    Google声称:随着应用程序的增大,手动依赖注入的成本会出现指数级增长。但是我并不这么认为,我觉得Google既不了解"指数"的含义,也没有实际去“衡量”过任何东西。这个申明的内容是错误的,我希望Google不再使用这种方式误导社区。
 
    事实上,这种手动依赖注入在后端比较常见(尤其是微服务中,你并不想在每个服务中都添加对注入框架的依赖),也可以正常的工作。在后端,反射被经常用到,所以后端的依赖注入框架并不需要解析编译时的代码。
 
    在Android上,解决方案与后端有一些不同,我们几乎不会用反射方案的依赖注入框架,所以就只剩下Dagger可以用了。其实,反射虽然会影响性能,但是在大多数项目,都是可以用的。我的意思并不是建议你们使用反射方案的依赖注入框架,这个选择并非是非黑即白的,你需要按照你的要求来进行选择。
 
    无论如何,Android领域上,Dagger作为依赖注入框架的现行标准,我们所有人都在使用它。尽管Google在宣传上,对Dagger的使用成本使用漂亮的绿色图形进行展示,但是Dagger使用成本在实际上依然会随着时间增长而快速增长。越来越多的代码,在编译构建的时候需要花费更多的时间;你的开发人员越多,代码编译的次数就越多。当然,所有的开发人员都需要学习如何使用Dagger,这本身就是一项很大的成本。
 
    换句话说,虽然Dagger可以减少项目中编写的代码,但是需要花更多的时间去培训新人,在编译上花费更多的时候。所以,对大型项目来说,使用Dagger会更耗时。
 
    在一个大型项目中,编译耗时会逐渐成为生产力的瓶颈。当然,Dagger也提供了很多优秀新的功能来帮助你优化编译时间,但前提是,你需要知道如何使用这些工具。读到这里,我相信你对手动依赖注入会很感兴趣。
 
    数据绑定
 
    作为一个Android开发者,都知道在写布局的时候,会经常调用findViewById这个方法。DataBinding诞生就是为了取代掉这个模板方法。老实说,在使用findViewById的时候,我并没有遇到过任何问题,虽然希望摆脱掉它,但我并不认为使用DataBinding就是一个更合理的方式。有一个好消息,很快我们就可以使用ViewBinding来摆脱findViewById了,也不需要使用DataBinding。
 
    说句实话,我不相信DataBinding。对于它想解决的问题来说,这种方案在过于复杂。在使用DataBinding的时候,需要把代码逻辑放到XML布局中,这听起来很不错,但是经验丰富的开发人员都不会这么做,这个做法也是Databinding的另一个缺点。
 
    早在2016年11月的时候,那个时候Google还在大肆宣传DataBinding。我在StackOverflow的回答中作了如下预测[9]:
 
    我可以非常自信地预测:DataBinding不会成为行业标准。DataBinding可以带来短期收益,但是从长远来看,它将会使代码变得不可维护。一旦Databinding被长期使用,它的缺点就会暴露出来,将来它一定会被废弃掉。
 
    我没有统计过使用DataBinding的项目,但是很明显,它没有成为行业标准。我从来没有在自己的项目中使用过它,也很少看到其他开发者使用。据我猜测,当ViewBinding逐渐成熟,并且被广泛采用,DataBinding将会作为一个“传统”框架,大量地被引用到。
 
    状态保存
 
    自从引入ViewModel架构组件以来,在Android应用程序中,当配置发生更改,保存与恢复状态的逻辑,就变成了一个烂摊子,没有人去管理。虽然这样子说有点过分,但是我觉得,这已经是我最温和的表达方式了。
 
    GaborVaradi(又叫Zhuinden)在Reddit论坛中描述了ViewModel引入带来的问题[10],我不需要再去写一遍了。再次强调,不推荐使用onRetainCustomNonConfigurationInstance,推荐使用ViewModel。
 
    在帖子的末尾,Gabor作了一些预测:
 
    你知道吗?Fragment状态保存的方法已经被弃用了[11]。
 
    在我看来,废弃Fragment状态保存的方法是非常好的主意,众所周知,Fragment的onAttach和onDetach方法就是为了支持状态保存的,现在废弃了状态保存的方法,那这两个方法也可以被废弃掉,并且这样子可以简化Fragment的生命周期。我长期以来都建议不保存Fragments的状态,忽略掉onAttach和onDetach方法,和我之前写的处理Framgent生命周期方法一致[12]。
 
    尽管有很多理由表明,要废弃掉Fragment的状态保存,但是也不可能废弃掉onRetainCustomNonConfigurationInstance,这个可不是我说的,是JakeWharton说的。在上面Gabor的帖子上,他的回复获得了最多的点赞。虽然我不太赞成Jake所说的话,但是我找不到更好的理由去说服自己。这个方法和ViewModel后台使用的原理完全一致,完全没有理由废弃掉它。
 
    那我们应该怎么对待这些废弃的方法呢?Google不管这些方法使用的技术方案和优势,都强制所有的Andorid应用迁移到ViewModel。即使这些方案有可能优于ViewModel本身,他们也愿意放弃。听起来有点像是阴谋论吧。
 
    我确实不喜欢保存非配置的状态,并且废弃掉对我没有任何影响,因为我从来都没有使用过它。事实上,大多数应用程序都不需要这些方法,当然,ViewModel也不需要。我们需要处理状态改变的方法仅仅只有onSaveInstanceState(Bundle)这个方法。这个方法非常简单明了,可以同时处理保存和恢复的逻辑。所以,只要能用这种方式保存状态就可以了,我相信,我不是唯一一个使用这个方法的人。虽然Google对ViewModel进行了大量的营销宣传,但是对于很多经验丰富的开发者来说,ViewModel还是太复杂了,我们有更简单有效的方法来处理状态存储。
 
    如果Google别有用心,想强制所有项目使用ViewModel,那么它还将废弃掉onSaveInstanceState(Bundle)方法。这听起来有点不可思议,如果将来真的这样发展,那说明我的基础理论是正确的。
 
    考虑到Android的内存管理机制,Google不可能在没有稳定的解决方案之前就废弃掉onSaveInstanceState(Bundle)。“幸运的是”,我们已经可以使用ViewModel来完成相关工作了。
 
    我想,在一两年内,就能看到我的理论是否正确。
 
    总而言之,如在本节开头所说,Android状态保存将变成一个烂摊子。两年多前,我曾经写过ViewModel架构组件有害的文章[13]时,我就预测ViewModel会对保存与恢复状态的一点点造成影响。我所预测的都变成了现实,而且现在的情况比我曾经的预测更糟。
 
    并发
 
    在Android并发编程中,一个重要的API就是AsyncTask,不过它现在已经被弃用掉了。我之前已经写过很详细的文章分析过它了[14],在这里,将不再赘述。
 
    下面我要说的内容,有可能会伤害很多读者,但是,请不要“恨”我。
 
    RxJava,是一个Andorid中常见的多线程框架。但是它现在将逐渐退出历史的舞台。从StackOverflow的趋势图可以看出:
 
    RxJava使用趋势
 
    很多开发者对这个说法提出了质疑,他们反驳说这个数据不具有代表性,并且我们可以找到其它的理由来解释图上所发生的事情。他们所说可能是正确的,我个人本身也不是数据科学家。但是从图中我们可以看到,RxJava与AsnycTask有相同的斜率。
 
    如果你没有时间去学习RxJava如何使用,并且你的项目中也没有使用过RxJava,我建议你不要在你的项目中使用RxJava。事实上,我也一直不推荐使用RxJava,现在已经有数据支持我的这个观点了。
 
    如果你的项目中使用了RxJava,你也不用慌张,不需要紧急去重构你的项目。如果你的项目只有你一个人,或者整个项目组成员基本不会变动,保持项目现状就好了。但是你需要记住,以后要招具有RxJava开发经验的人会越来越困难,新招开发人员可能需要学习使用RxJava。广泛使用RxJava的项目,在以后也会被认为"不酷",就像今天还在使用AsyncTask和Loader的项目一样。
 
    我知道,很多RxJava的开发者都是RxJava骨灰级的粉丝,他们花了数周的时间去学习RxJava,付出巨大的努力才说服队友在项目中使用RxJava。现在我却在这里说RxJava已经过时了。我只能说,这不是我的个人意见,我只是对现有的情况进行分析,并根据我所看到的内容做出预测。我也有可能是错的。两军交战,不斩来使,请大家不要“攻击”我。
 
    在Kotlin中,使用协程来实现多并发。最近使用协程实现了一些简单的用例[15],我发现它复杂、不稳定,甚至还有一些Bug。
 
    所有的人都在说,协程可以降低并发的复杂度,使用并发变得简单。我从来都不相信这句话,因为我知道并发从根本上来说就是很复杂的。我动手写过一些测试用例过后,据我的经验,我可以很自信的告诉你,协程不能使并变得简单。我认为,协程会增加复杂性,我建议你们谨慎使用他们。
 
    在Kotlin中,协程将作为处理多线程的默认方式,如果你已经开始使用Kotlin进行开发,那么你应该花点时间,去学习一下,协程的使用。
 
    据我所知,还有一个Flow框架,它是基于协程,添加了流运算符。在几个月之前,已经稳定了。所以现在也没啥好评价的。
 
    Kotlin
 
    现在,让我们来讨论一下Kotlin在Android领域的现状。根据我个人的经验来看,这是一个很敏感的话题,不论我所说的话有多么公正客观,都会有一些粉丝评价我所说的话是“Shit”。但从专业的角度来说,Kotlin的话题是跳不过去的,所以,我要强调的是,这些内容只是我的个人观点,仅供参考。
 
    在Android开发中,使用Kotlin,会大大地增加你的编译时间。
 
    在我的另一篇文章中[16],我统计了使用Kotlin过后,编译时间的增长情况。结果是,全量编译的情况下,会增加18%左右的耗时,如果是增量编译,则会增加8%左右的耗时。
 
    Uber和JetBrains联合发表了他们有关Kotlin对项目编译时间的影响,在文章中,显示的结果非常的悲观。他们表示,如果你不开启IDE中的annotationprocessors,引入Kotlin的项目,编译构建的时间大约会增加四倍,如果开启了annotationprocessors,编译构建的时间也会增加50%~100%。
 
    Uber的结果与OkHttp迁移到Kotlin后得到的结果是一致的,都是编译时长都增加了4倍。
 
    别担心,虽然这个结果让人吃惊,这个也不是你的错,很多人都和你一样,正在使用Kotlin。这个问题,虽然很重要,但是它并没有引起广泛的关注。我觉得,Google也在试图解决这个问题。我曾问过Google相关的开发者,并进行了深度的交流,他们在这个问题给我的回答是:“这是一个很棘手的问题,我宁愿不做”。
 
    Kotlin除了会增加编译时间,直到上周,才支持增量注解处理,而Java,在10个月以前就支持了。
 
    在两年前,我就写了篇文章用于告诫大家,过早使用Kotlin会存在很大的风险。你可以从评论中可以看出,我在很长的一段时间里面,都是“Kotlin的黑粉”。
 
    在你的实际工作中,你会发现,与上面的数据相比,Kotlin的问题远不止于此。在大型的Android项目中,编译构建的时间会严重阻碍项目的发展。即使到了今天,Kotlin已经被正式使用两年了,Kotlin编译效率依然比Java差很多。不管Kotlin能给你带来多少优点,编译耗时的问题,都有可能导致你不在使用它。
 
    不管如何,Google向整个Android的开发生态推出Kotlin作为第一首选语言,现在Kotlin的使用量也越来越大,我们也不得不进行跟进。就我个人而言,我还没有在我的项目中使用Kotlin。因为Kotlin还不够成熟,并且我的客户也不会为我的学习付费,并且我也不希望在Kotlin上浪费时间。但是从现在开始,Kotlin已经逐渐稳定,我也在我拿手的项目上尝试过,在新项目中,我也会考虑使用它进行开发。有一些开发者认为:“必须在新项目中使用Kotlin进行开发“,我不同意这个观点,我觉得这是一个权衡的问题,Kotlin现在已经成为了一个重要的选项,只要适合当前的项目,就是最好的语言。
 
    对于是否要将已有的项目迁移到Kotlin上,我不能给你太好的建议。你需要根据你项目的情况,具体问题具体分析。如果你一旦决定要进行迁移,这篇文章中列举的一些可能存在的问题,可能对你有帮助。
 
    总结
 
    请允许我用Android开发者的背景,描述一下我这两年所经历的事情:
 
    在过去的两年里,我启动了三个项目,我一直争取,至少参与其中一个项目的开发工作。我回过头来看这些已经存在的项目,并分析这些项目前期所做的技术决定对整个项目的影响。我写了这篇文章,也制作了很多Android开发的高级课程,也花了很多时间在互联网上讨论Android相关的主题。
 
    即使这样,我今天依然感觉跟不上Android整个生态系统的变化。可想而知,对于那些经验不足,需要指导的Android开发者而言,是多么地绝望。我现在已经无法想像,现在从头开始学习Android的感觉。当你好不容易学会了某个框架或者工具,觉得它很好用的时候,它或许就要过时了。现在也许是加入Android开发大家庭最坏的时候。Google正为他们的“包容性”沾沾自喜,但这一切,对初学者来说,都是极其痛苦的。
 
    Google在Android框架中所做的事情,会导致大量的时间浪费。我们需要花费数小时的时间才能读完所有更改的内容,更别说在项目中应用它们了。我宁愿花时间来创造价值,而不是舍本逐末。
 
    在本文中,我试图总结Android开发的现状,并对未来作出了一些预测。文章中,可能包含错误和漏掉一些重要信息,请随时在下面的评论中告知我。文章中的内容都是客观内容,虽然我提出了一些有争议观点,但我相信我是对的。
 
    还有,在文章中,我引用了很多之前写的帖子,我并不是为了炫耀。而是让你能够阅读之前的预测与现在的状况进行对比,虽然那些文章在那个时候读起来很疯狂,就像现在你读本文一样,但是我的这些预测都是很准确的。当然,我也想说:“看,我说得对吧”。鉴于我发布的内容具有争议,当得知没有误导读者,我也会感到很欣慰。有时候,我也宁愿我的预测是错的,Google正在为开发者着想。但是到目前为止,情况并非如此。
在线咨询
Copyright © 2013-2020 YongJiaSoft 永佳软件 版权所有 备案号:苏ICP备13061077号