如何 GET 架构设计的新技能
在这一个多月里,我工作在一个采用插件化的原生 Android 应用项目上。随着新技术的引入,及编写原生 Android 代码的技能不断提升,我开始思索如何去解锁移动应用新架构。对,我就是在说 Growth 5.0。
两星期前,我尝试使用了 Kotlin + React Native + Dore + WebView 搭建了一个简单的 Android 移动应用模板。为了尝试解决 Growth 3.0+ 出现的一系列问题:启动速度慢、架构复杂等等的问题。
PS:作为 Architecture 练习计划的一部分,我将采用规范一些的叙述方式来展开。
即下图:
技术是为了解决业务的问题而产生的。
脱离了业务,技术就没有了存在的前提。脱离了业务的架构不叫 “架构”,而叫刷流氓,又或者是画大饼。业务由于其本身拥有其特定的技术场景,往往是对技术决策影响最大的部分。
因此,开始之前让我们先了解一些业务,这里以 Growth 为例。
Growth 的价值定位是:带你成为顶尖开发者。
复杂一点的说明就是:Growth 提供 编程学习服务 使用 Web开发路线 帮助 新手 Web 程序员 解决 Web 学习路径问题。
让我们来看一下,更复杂一些的说明(电梯演讲):
在原有的业务架构下,我们拥有 Growth、探索、社区、练习四个核心业务,以及用户中心的功能。
这就是业务上的主要架构,接下来让我们看看技术上的事务。
远景,即想象中未来的远大景象。技术远景,即想象中未来的技术方面的远大景象。
在上一节中,我们介绍的是项目的业务远景。而作为一个技术人员,在一个项目里,我们也已经创建自己的技术远景。一来,我们可以创建出可持续演进的架构;二来,可以满足个人的技能需求。
以 Growth 为例,我的最基本的技术需求是:提升自身的能力。然后才是一个跨平台的技术设施——减少构建时间。
从 Growth 1.0、Growth 2.0 采用的 Ionic,到 Growth 3.0 采用的 React Native,它都优先采用新的技术来帮助自己成长,并使用了跨平台的移动应用开发框架。而这几个不同的版本里,也拥有其对应的不同技术问题
index.android.bundle
文件有 3.1M,这个体积相当的大,以至于即使在高通的骁龙 835 处理器上,也需要 4~5 秒的打开时间。而在 Growth 5.0 的设计构架里,考虑到 React Native 本身的不加密,其对于应用来说,存在一些安全的风险。我决定引入 Native 的计划,来从架构上说明,这个系统在某种程度上也是可以加密的。
因此,对于我而言,从技术上的期望就是:
对于普通的应用来说,其需要从不同的方案中选择一个合适的方案。其选择的核心,取决于项目所依赖的关键点。如在 Growth 有两个关键点:代码复用程度、应用性能。
这个时候就需要罗列出不同系统的优缺点,并从中选择合适自己的一部分。
如下数据(纯属个人使用体验总结,没有任何的数据基础):
PS:NativeScript 在安全性上比 React Native 好一点点的原因是,使用 NativeScript 的人相对少一点,所以技术成本就高一些。毕竟,macOS 和 Android 手机上也是有病毒的。
考虑到我打算结合不同的几个框架,所以这里就不需要选择了。
在定下了基本的技术方案后,就差不多是时候进行架构设计了。
现今的很多应用里,也是采用多种技术栈结合的架构,如淘宝的 Android 原生 + Weex + WebView,或者支付宝(不确定有没有 Weex)。但是,可以肯定的是几乎每个大型应用,都会在应用里嵌入 WebView。WebView 毕竟是可以轻松地进行远程动态更新,也需要原生代码那样的后台更新策略。
在 Growth 3.0 里,我们选择了使用 React Native + WebView 的构建方式,其原因主要是 WebView 的生态圈比较成熟,有相当多的功能已经用 WebView 实现了。而在新版本的设计中,则系统变得稍微复杂一些:
从设计上来说,它拥有更好的扩展性,毕竟在安全上也更容易操作。然而,从技术栈上来说,它变得更加复杂。
原生部分
系统在底层将采用原生的代码作为基础框架,而不再是 React Native 作为基础。再考虑到项目上正在实施的 Android 插件化方案,我打算在 Android 的 Native 部分使用 RePlugin 来引入一些更灵活的地特性。因此,从架构上来说,能满足个人的成长需求了。
毕竟原生 Android 有些架构还是相当有意思的:
React Native
React Native 从代码上的变化比较大,架构设计上从代码上切分出几个不同的页面。它可能可以在某种程度上 Bundle 文件过大,带来的加载速度慢的问题。因而,在某种程度上,可能带来更快的启动速度。
WebView
总体上来说,WebView 变化不会太大。除了,可能从 React Native 的 WebView 迁移到原生部分的 WebView 之外。
之前我们提到持续集成的时候,多数是指持续集成的实施。而今天我们谈到持续集成的时候,则是在讨论如何去设计。
首先,就是代码策略,即代码管理策略。代码管理,指的就是决定采用哪种 git 工作流。会影响到代码管理的因素有:
对于 Growth 而言,则仍然是 master 分支,采用多个 GitHub 项目的集成方式。
作为一个有经验的程序员,我们应该在设计的初期考虑到我们所需要的工具:
这些也仍是我们在设计架构的过程中,需要考虑的一些因素。
一般情况下,我们要会采用测试金字塔:
在这里,引用《全栈应用开发:精益实践》对于测试金字塔的分析:
从图中我们可以发现:单元测试应该要是最多的,也是最底层的。其次才是服务测试,最后才是 UI 测试。大量的单元测试可以保证我们的基础函数是正常、正确工作的。而服务测试则是一门很有学问的测试,不仅仅只在测试我们自己提供的服务,也会测试我们依赖第三方提供的服务。在测试第三方提供的服务时,这就会变成一件有意思的事了。除此还有对功能和 UI 的测试,写这些测试可以减轻测试人员的工作量——毕竟这些工作量转向了开发人员来完成。
而如果是架构混搭的应用来说,其的测试成本相当的大。因为要测试的部分是 3 + 1,即:
react-test-renderer
测试渲染、jest
和 chai
测试业务逻辑appium
就是一个不错的选择。最后,让我们来看看我在两个星期前,搭的一个架子,用于作技术验证功能。一共由三部分组件:
接下来看两个简单的代码示例:
如下是一个使用 React Native 编写的 Fragement 示例,它可以直接在原生的 Activity 上使用:
class ArcheReactFragment : ReactFragment() {
override val mainComponentName: String
get() = "RNArche"
private var mReactRootView: ReactRootView? = null
private var mReactInstanceManager: ReactInstanceManager? = null
@Nullable
override fun onCreateView(inflater: LayoutInflater?, group: ViewGroup?, savedInstanceState: Bundle?): ReactRootView? {
mReactRootView = ReactRootView(activity)
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(activity.application)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build()
mReactRootView!!.startReactApplication(mReactInstanceManager, "RNArche", null)
return mReactRootView
}
}
除了将 React Native 切分成不同的几个子模块。对于一个 React Native 应用来说,它可以注册多个 Component
AppRegistry.registerComponent('RNArche', () => App);
AppRegistry.registerComponent('RNArche2', () => App2);
这样一来说,可以在一个 React Native 应用里被原生部分多次调用不同的组件。
对于那些不需要原生组件的组件来说,可以直接由原生应用来对 WebView 处理。从逻辑上来说,这样的性能会更好一些:
@SuppressLint("SetJavaScriptEnabled")
@Nullable
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater?.inflate(R.layout.fragment_webview, container, false)
mWebView = view?.findViewById(R.id.webview)
mWebView!!.loadUrl("file:///android_asset/www/index.html")
val webSettings = mWebView!!.settings
webSettings.javaScriptEnabled = true
mWebView!!.webViewClient = WebViewClient()
return view
}
对,就是这么简单。
So,尝试去做这样的设计吧。
网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。
添加我为好友,拉您入交流群!
请使用微信扫一扫!