原生动态化探讨与实现

现在随着大前端的流行,Native小程序,RN,等看似打着原生旗号的动态化,但开发过程中都会发现非常非常的像在写前端,各种 margin padding align 等,尤其是各大框架看起来都在反复提一个词 FlexBox ,于是我们深入的聊聊在移动端,动态化与跨平台这两个词的发展。

编译动态化

每一行代码本质上他就是一个字符串,他不具备任何的可运行的能力,之所以能被运行是因为他经过了一系列的编译,把一行行人能读懂的有逻辑有语意的字符串,变成了机器可以读懂的一条条指令集

 

  • 输入:代码字符串

  • 编译: 

    • 编译前端

      • 词法分析 一句话解释:识别字符串中的语言关键字

      • 语法分析 :识别循环,判断,跳转,调用等代码逻辑

      • 生成抽象语法树 AST :将代码字符串转化为机器可理解可遍历可处理的语法树

    • 编译后端

      • 生成中间码 IR :将语法树向可执行结果产物进行转化,先产生个中间产物

      • 生成结果 :将中间码根据个平台的差异,生成不同的运行结果

  • 输出:平台可运行的结果

    • iOS:可执行二进制 Mach-O 文件(汇编代码装载进入内存后即可执行)

    • 安卓:JVM的字节码(在任何有JVM的平台中都可以执行)

编译结果动态化

编译的最终结果想要执行,都得装载到运行环境里去,iOS是直接装载到内存上,而安卓是加载到JVM虚拟机里,而这个加载过程天然是支持“动态”加载的,并且这种动态化的开发模式是非常贴近原生的。

  • 安卓:插件化技术,dexloader 加载

  • iOS:动态链接库技术,dlopen 加载

安卓我不了解就不细说了,但iOS确实值得咬文嚼字一下,dylib 的全程叫动态链接库,我们有时候自己做一些通用组件库的时候都可以选择创建一个“静态链接库”又或者“动态链接库”,差异取决在是否在app启动截断就第一时间加载。但次动态指的是动态“链接(加载)” 而非 “动态更新”。

静态库中的代码都会与主程序编译到同一个可执行文件中,如果多个程序引用了同一个静态库,那么这个静态库是会在多个程序的可执行文件中存在多个副本。而动态库他的本质是希望让多个可执行文件间共用代码段,在链接装载期间,通过把独立于主程序可执行文件之外的 dylib 链接到主程序的虚拟地址上,故名:动态链接

但既然是动态链接,那么在本次加载的时候通过网络下载了最新的 dylib 文件,在下次加载最新文件,自然也能做到 “动态更新”,下面看一个在运行期间用代码,手动链接 dylib 的例子

#define Dylib_PATH  "/System/Library/PrivateFrameworks/xxxxx/xxxxx/xxxxx"- (void)dynamicLibraryLoad{   // dlopen 去动态加载 dylib  void *kit = dlopen(Dylib_PATH,RTLD_LAZY);       // 在运行期动态加载的dylib应该如何调用里面的代码?  OC有运行时与反射    Class pacteraClass = NSClassFromString(@"DynamicOpenMenth");  if (!pacteraClass) {      NSLog(@"Unable to get TestDylib class");      return;  }  NSObject *pacteraObject = [pacteraClass new];  [pacteraObject performSelector:@selector(startWithObject:withBundle:) withObject:self withObject:frameworkBundle];    // 只有OC的代码才能动态调用么?C代码一样有办法 dlsym 通过函数符号获取动态加载后的函数地址,进行调用    NSString *imsi = nil;  int (*CTSIMSupportCopyMobileSubscriberIdentity)() = dlsym(kit, "CTSIMSupportCopyMobileSubscriberIdentity");  imsi = (NSString*)CTSIMSupportCopyMobileSubscriberIdentity(nil);    // 卸载动态库  dlclose(kit);}

只可惜这条看似最佳的动态化的路被苹果堵死了,dlopen 这个函数在 debug 包下运行以上代码,动态加载畅通无阻,但在 release 的包上,就会被苹果加入签名校验逻辑,凡事没经过签名的dylib/framework,都会动态链接失败,所以这条路在 iOS 上是被封死的,但是在安卓上,各种插件化的方案还在继续

编译结果跨平台

本身 Java 就被设计为一种一次编译处处运行的语言,只要对应平台有 JVM 这个虚拟机运行环境,换句话说跨平台的差异被 JVM 给打平了,但很可惜 iOS 平台并没有选择 JVM ,所以也就不跨到了 iOS 上。

那么剩下的跨平台的语言就是 C/C++ 了,安卓与iOS都支持用C与C++的编译产物,在iOS上 OC 与 C 和 C++ 几乎是无缝支持,而在安卓上需要 JNI 来进行桥梁与 java 互通。早年间流行的游戏引擎 cocos2dx 就是用 C++ 进行的逻辑与渲染(lua容后再表),Flutter的渲染平台就是用 C++ 写的 skia 渲染引擎做的,安卓与iOS两个平台其实都存在很多知名的库或者本地算法,底层是用 C++ 实现的。

本质上:编译后台的设计分为,先编译出中间码,再由中间码根据平台差异编译成最终结果,本身就是一个为了跨平台而做的设计“增加一个中间层”,但由于很多语言在发展过程中,不同语言的作者在做开发语言编译器的时候,使用的IR中间码,并不是统一的一套,所以设计很丰满,现实很骨干,IR中间码并没有完全解决跨平台的问题。

Web技术动态化 – “不要写死”

我们在开发的过程中即便是纯native开发,也经常会伴随着一些开发理念“不要写死”

  • 界面的文案?不要写死,服务器下发

  • 界面的文案的样式?不要写死,阴影,加粗,斜体,下划线服务器下发

  • tableView 的Cell?不要写死,七八种cell模板,服务器下发啥渲染啥

  • 程序逻辑?不要写死,根据服务器返回的type开关,看情况跳转

  • 功能模块?不要写死,看开关可以整体隐藏关闭

在编译运行的过程中,代码字符串被编译成了可执行的编译结果,在对应平台上运行。在“不要写死”这个思路中,描述配置信息等json字符串,被通过网络在程序运行的过程中,实时根据配置改变运行结果。

所有的这些“不要写死”,其实是一种可以通过网络下发的描述性值的信息,以json形式,从代码逻辑通过读取这些描述信息,去改变预先准备好的功能逻辑,从而做到功能改变。甚至有些功能页面例如“账单详情”,他的现在几乎进入了高度后端可配的状态,面对不同商家不同种类交易,完全无需前端迭代,纯靠后端强大的配置平台即可完成频繁的各业务方接入需求。

原则上讲,如果我们的描述配置语言设计的足够全面,应用程序中固化的那部分识别描述配置并且执行的固有能力也足够全面。既能考虑到任意当前已规划的需求,以及胜任未来的功能需求,那么我们的描述配置语言,无形中其实就是一种动态更新,这种描述配置语言其实也就形成了 DSL 领域专用语言,在你的应用程序配置体系中专用的描述语言。

布局引擎 – 界面动态化

在界面这一块确实就存在这种几乎能够完美涵盖所有界面需求的“DSL”设计,也存在着能够解析这种完美“DSL”设计的native固定代码“布局引擎”,从而做到一套布局引擎的程序代码,根据输入的 “DSL” 不同,呈现出风格迥异的页面。

布局引擎并不是因为动态化的诉求而产生的,本质上是为了解决屏幕多尺寸适配的问题,为了能用一种“通用”的描述方式,通过传入的窗口区域大小,实时的运算出每一个子元素在当前窗口里的 x y w h 绝对坐标,这个过程便是布局算法

类似的布局算法有很多种,但可以确定的是,浏览器内核中的布局引擎是眼下最完美的,并且还在被 W3C 以及浏览器厂商不断的完善,浏览器内核的布局引擎的输入是 HTML 这种文件来表达界面元素层级描述 CSS 这种文件来表达布局约束信息与样式信息,并且支持绝对布局,相对布局,盒子模型,弹性盒子(知名的 FlexBox),网格布局等多种布局算法相互之间嵌套使用~本质上是一种多种算法可扩展的布局引擎。(后面会略微详细介绍一下布局算法)

image.png

这种布局引擎本质上还是 native 的(浏览器内核是用c++)写的,如果不是直接使用 WebView,也有很多有着几乎一样思路的 native 布局 SDK

  • iOS 的 AutoLayout :iOS中的 xib 布局文件,本质上就是一种树型的 XML,它里面包含着界面层级信息与样式信息,经过原生代码 initWithNibName: 来读取这种 xml 最终生成界面。而如果不使用 xib,而是代码创建约束,那么原生 VFL 其实本质上也是一种DSL,而他内部的算法是通过计算约束 N 元一次方程组得出最终渲染坐标的,其算法性能出了名的差,在很多情况下即便原生 autolyout 非常的卡(在iOS 12以后得到优化)

  • 安卓的 XML 写界面:安卓的布局也很像浏览器内核,他也支持用 XML 去写,并且同样支持几种布局,从而满足丰富的界面展现需求

  • DTCoreText:基于 iOS原生 CoreText 排版库的上层封装,可以识别 HTML 从而直接渲染出native的

  • RN / Weex 的渲染框架:实际上也是 HTML/CSS 来通过 FlexBox 弹性盒子这种布局算法,构建出Native界面

  • Texture 框架:原名 AsyncDisplayKit ,FB 出品,也是基于 FlexBox 弹性盒子这种布局算法,其实还有 YogaKit , ComponentsKit 等,归根结底还是来自 FB 开源的 FlexBox 布局算法库 “Yoga”(我们的鸟巢也是来源自 yoga 同一个布局内核)

所以本质上,HTML+CSS 这种网页的界面编写模式,和安卓原生的 xml 写界面,和 iOS 原生的 xib 渲染界面本质上是没区别的,甚至有时候还比原生快(iOS Autolayout被人喷太慢了) ,浏览器与H5慢是多方复杂原因共同导致的,但在布局渲染这块,Web的技术体系在UI的描述能力以及灵活度上确实设计得非常优秀,越来越多的原生动态化方案,在渲染这块都还离不开这一套技术体系

解释执行 – 逻辑动态化

单纯通过界面动态化,这样我们做出来的这样的一个动态App,一个界面的 DSL 中给可点击交互的元素加上一个 scheme 的路由属性,每当这个元素被点击的时候,就跳转这个路由,打开一个新界面,而新的界面又是全新的一个新下载下来的 DSL,从而实现了纯展示型页面跳转的 “动态” App。仔细分析一下,这其实就是早期最原始的浏览器的网页,每个网页由一个 url 去下载 html/css ,然后布局展现页面,每个元素的点击交互的效果是跳转一个新的 url,所以可以认为这样的一种 “动态界面” App,是最纯正的网页技术,虽然他是 Native 的。

但这种动态远不是我们所希望,我们希望可以在不同的交互与生命周期下进行:

  • 发出一个网络请求,获取最新最全的数据

  • 进行本地数据存储读取,数据持久化

  • 针对数据进行多方逻辑判断,根据逻辑结果,呈现给用户不通的表现

  • 等等

纯界面动态化是不可能满足这种需求的,俺着我们的上面探讨的思路,我们需要一种“能够描述逻辑的表达式”,以字符串的形式下发到客户端,然后通过客户端内置的一套固定的“运行环境”,来展现出不同的运行结果。

对比一下界面动态

  • “能够描述界面的表达式-字符串”:就是一种描述语言 DSL (领域特定语言)

  • “运行环境”:执行描述表达式需要一套用 native 代码编写的布局引擎,包含各种布局算法

逻辑动态会复杂的多

  • “能够描述逻辑的表达式-字符串”:单纯是 DSL 已经无法满足需求,我们需要正经代码编程的语言(脚本语言)

  • “运行环境”:编程语言的编译结果能否再任何平台下直接执行?我们需要的是一个用c编写的,脚本语言(JS/Lu

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DuktapeJava 是针对 Android 平台封装的 Duktape  JavaScript 引擎;实现Javascript 和 Java的无缝调用。 初始化DuktapeEngnine   @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); duktapeEngine = new DuktapeEngine(); duktapeEngine.put("activity",this); duktapeEngine.execute(AssetScript.toScript(getBaseContext(), "duk.js")); duktapeEngine.call("activityListener", "onCreate", savedInstanceState); } @Override protected void onDestroy() { if (duktapeEngine != null) { duktapeEngine.destory(); duktapeEngine = null; } super.onDestroy(); }   duk.js javascript 代码示例   importClass("com.furture.react.R") importClass("android.view.View.OnClickListener") importClass("android.widget.Toast") importClass("java.lang.Runnable") var activityListener = {}; activityListener.onCreate = function(){ print("activity onCreate onJavaScript"); activity.setContentView(R.layout.activity_duk) button1 = activity.findViewById(R.id.button1); button1.setOnClickListener(new OnClickListener(function(){ Toast.makeText(activity, "Button1 Clicked", Toast.LENGTH_SHORT).show(); var intent = new Intent(activity, "com.furture.react.activity.DetailActivity"); activity.startActivity(intent); })); button2 = activity.findViewById(R.id.button2); button2.setOnClickListener(new OnClickListener({ onClick:function(){ Toast.makeText(activity, "Button2 Clicked", Toast.LENGTH_SHORT).show(); } })); } activityListener.onStart = function(){ print("activity onStart"); } activityListener.onResume = function(){ print("activity onResume"); } activityListener.onPause = function(){ print("activity onPause"); } activityListener.onStop = function(){ print("activity onStop"); } activityListener.finish = function(){ print("activity finish" num); }   Java 和 Javascript 的无缝调用 javascript 调用java方法示例: importClass
原生HTML实现国际化可以采用以下方法: 1. 使用lang属性:在HTML标签中使用lang属性来标明当前页面使用的语言,例如:lang="zh-CN"表示中文。 2. 使用meta标签:在HTML头部使用meta标签来指定当前页面的语言,例如:<meta http-equiv="Content-Language" content="zh-CN"> 3. 使用文本编码:在HTML中使用Unicode编码来显示各种语言文字,同时使用UTF-8等编码格式来支持多种字符集。 4. 使用JavaScript:通过JavaScript动态修改页面中的文本内容来实现国际化,可以通过定义多个语言版本的文本文件,然后根据用户的语言环境加载相应的文本文件。 例如,以下是一个简单的JavaScript代码示例,根据用户的语言环境加载不同的文本文件: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>国际化示例</title> <script> var lang = navigator.language || navigator.userLanguage; // 获取用户的语言环境 if (lang === "zh-CN") { // 如果是中文 var script = document.createElement('script'); script.src = 'zh-CN.js'; // 加载中文版的文本文件 document.head.appendChild(script); } else { // 否则默认加载英文版的文本文件 var script = document.createElement('script'); script.src = 'en-US.js'; // 加载英文版的文本文件 document.head.appendChild(script); } </script> </head> <body> <h1 id="title"></h1> <p id="content"></p> </body> </html> ``` 在上述代码中,根据用户的语言环境动态加载相应的文本文件,并在页面中显示对应的文本内容。 综上所述,使用JavaScript动态修改页面中的文本内容是一种原生HTML实现国际化的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值