#集成到现有应用
将 Lynx 作为高性能、跨平台的 UI 渲染引擎,灵活地嵌入到现有应用的任意位置。这种 既有(Brownfield) 接入方式具有最高的灵活性,支持全部平台,让你无需重写就能渐进式采用 Lynx,常用于大规模生产应用。
#将 Lynx 集成到 iOS 平台
- 本文假设你已熟悉原生 iOS 应用开发的基本概念。
- 下文中的所有代码,你都可以参考项目:integrating-lynx-demo-projects
#1. 依赖配置
使用 Cocoapods 可以方便的将 Lynx 集成到你的应用中
- Cocoapods: >= 1.11.3
- Ruby: >= 2.6.10
#配置依赖
- Lynx
Lynx Engine 核心能力,包含了解析 Bundle、样式解析、排版以及渲染视图等基础能力。
从 Cocoapods 中获取 Lynx 的最新版本。然后将 Lynx 添加到你的 Podfile 中:
source 'https://cdn.cocoapods.org/'
platform :ios, '10.0'
target 'YourTarget' do
pod 'Lynx', '3.8.0', :subspecs => [
'Framework',
]
pod 'PrimJS', '3.8.0', :subspecs => ['quickjs', 'napi']
end- Lynx Service
Lynx Service 包括 LynxImageService、LynxLogService 等,旨在提供一些宿主应用特性强相关的能力,允许宿主应用在运行时注入自定义实现 Image Service 默认是使用 SDWebImage 图片库实现,在没有集成 SDWebImage 组件的宿主应用上则可以依赖其他图片库。
Lynx 提供了标准的原生 Image、Log、Http 服务的能力,接入方可以快速接入并使用;
从 Cocoapods 中获取 Lynx Service 的最新版本。然后将 Lynx Service 添加到你的 Podfile 中:
source 'https://cdn.cocoapods.org/'
platform :ios, '10.0'
target 'YourTarget' do
pod 'Lynx', '3.8.0', :subspecs => [
'Framework',
]
pod 'PrimJS', '3.8.0', :subspecs => ['quickjs', 'napi']
# integrate image-service, log-service, and http-service
pod 'LynxService', '3.8.0', :subspecs => [
'Image',
'Log',
'Http',
]
# ImageService
pod 'SDWebImage','5.15.5'
pod 'SDWebImageWebPCoder', '0.11.0'
end- More Elements
XElement 是 Lynx 团队维护的客户端扩展元件集合,提供更丰富的元件能力,能够让 Lynx 能够更快速的被用到生产环境中,提升 Lynx 生态的活力。
从 Cocoapods 中获取 XElement 的最新版本。然后将 XElement 添加到你的 Podfile 中:
source 'https://cdn.cocoapods.org/'
platform :ios, '10.0'
target 'YourTarget' do
pod 'Lynx', '3.8.0', :subspecs => [
'Framework',
]
pod 'PrimJS', '3.8.0', :subspecs => ['quickjs', 'napi']
# integrate image-service, log-service, and http-service
pod 'LynxService', '3.8.0', :subspecs => [
'Image',
'Log',
'Http',
]
# ImageService
pod 'SDWebImage','5.15.5'
pod 'SDWebImageWebPCoder', '0.11.0'
pod 'XElement', '3.8.0'
end#安装依赖
运行 pod install 安装依赖,然后打开你的 XCode 工程,同时需要确保关闭 Sandbox Scripting 能力。
为了关闭 Sandbox Scripting 能力,在 XCode 中点击应用,切换至 Build Settings。过滤 script 关键字,并且将 User Script Sandboxing 置为 NO。

#2. Lynx 环境初始化
#LynxEnv 初始化
LynxEnv 提供了 Lynx Engine 的全局初始化接口。请保证 LynxEnv 的初始化发生在 Lynx Engine 的任何接口调用之前;例如可以在 AppDelegate 中完成初始化
#import <Lynx/LynxEnv.h>
#import <Lynx/LynxView.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[LynxEnv sharedInstance];
return YES;
}import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
LynxEnv.sharedInstance()
return true
}
}
#3. 渲染 LynxView
LynxView 是 Lynx Engine 提供的渲染基本单元,LynxView 是一个继承自 iOS 原生 UIView 的实现,你可以快速的构造一个 LynxView,并将其添加到 ViewController 的视图上。
#创建 Bundle 加载器
Lynx Engine 自身并没有资源加载的能力,因此需要在初始化 LynxEnv,或者构造 LynxView 时传入 LynxTemplateProvider 协议的具体实现,Lynx 会采用注入的资源加载器来获取真实的 Bundle 内容
你可以使用多种方式获取 Bundle 的资源内容,在这里我们选择将 Bundle 的内容内置在应用中:
- 首先请将快速上手阶段生成的 Bundle 文件或者将下面的文件下载到本机上,然后按照如下步骤将文件内置:
内置文件步骤:
- 在项目的目标设置中,选择目标 Target;
- 选择 Build Phases 选项卡;
- 在 Copy Bundle Resources 部分,点击加号按钮(+)以添加文件;
- 实现 Bundle 加载器
#import <Foundation/Foundation.h>
#import <Lynx/LynxTemplateProvider.h>
NS_ASSUME_NONNULL_BEGIN
@interface DemoLynxProvider : NSObject <LynxTemplateProvider>
@end
NS_ASSUME_NONNULL_ENDLynx 是使用 Objective-C 构建的项目,如果你使用的是 Swift 工程,请使用 Bridging Header 引入所需的 Lynx 头文件。
#import <Lynx/LynxConfig.h>
#import <Lynx/LynxEnv.h>
#import <Lynx/LynxTemplateProvider.h>
#import <Lynx/LynxView.h>#import <Foundation/Foundation.h>
#import "DemoLynxProvider.h"
@implementation DemoLynxProvider
- (void)loadTemplateWithUrl:(NSString*)url onComplete:(LynxTemplateLoadBlock)callback {
NSString *filePath = [[NSBundle mainBundle] pathForResource:url ofType:@"bundle"];
if (filePath) {
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:filePath options:0 error:&error];
if (error) {
NSLog(@"Error reading file: %@", error.localizedDescription);
callback(nil, error);
} else {
callback(data, nil);
}
} else {
NSError *urlError = [NSError errorWithDomain:@"com.lynx"
code:400
userInfo:@{NSLocalizedDescriptionKey : @"Invalid URL."}];
callback(nil, urlError);
}
}
@endimport Foundation
class DemoLynxProvider: NSObject, LynxTemplateProvider {
func loadTemplate(withUrl url: String!, onComplete callback: LynxTemplateLoadBlock!) {
if let filePath = Bundle.main.path(forResource: url, ofType: "bundle") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
callback(data, nil)
} catch {
print("Error reading file: \(error.localizedDescription)")
callback(nil, error)
}
} else {
let urlError = NSError(domain: "com.lynx", code: 400, userInfo: [NSLocalizedDescriptionKey: "Invalid URL."])
callback(nil, urlError)
}
}
}#构造基础 LynxView
按照如下的方式构造一个最基础的 LynxView 实例:
#import <Lynx/LynxView.h>
#import "ViewController.h"
#import "DemoLynxProvider.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LynxView *lynxView = [[LynxView alloc] initWithBuilderBlock:^(LynxViewBuilder *builder) {
builder.config = [[LynxConfig alloc] initWithProvider:[[DemoLynxProvider alloc] init]];
builder.screenSize = self.view.frame.size;
builder.fontScale = 1.0;
}];
lynxView.preferredLayoutWidth = self.view.frame.size.width;
lynxView.preferredLayoutHeight = self.view.frame.size.height;
lynxView.layoutWidthMode = LynxViewSizeModeExact;
lynxView.layoutHeightMode = LynxViewSizeModeExact;
}
@endimport UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let lynxView = LynxView { builder in
builder.config = LynxConfig(provider: DemoLynxProvider())
builder.screenSize = self.view.frame.size
builder.fontScale = 1.0
}
lynxView.preferredLayoutWidth = self.view.frame.size.width
lynxView.preferredLayoutHeight = self.view.frame.size.height
lynxView.layoutWidthMode = .exact
lynxView.layoutHeightMode = .exact
}
}
#将 LynxView 添加到视图
然后将 LynxView 添加到应用视图中:
#import <Lynx/LynxView.h>
#import "ViewController.h"
#import "DemoLynxProvider.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// ...
[self.view addSubview:lynxView];
}
@endimport UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ...
self.view.addSubview(lynxView)
}
}
#渲染视图
当你完 成以上步骤之后,你就已经完成了 LynxView 初始化的全部工作,调用 lynxView.loadTemplateFromURL 方法,即可将对应的 Bundle 渲染到 LynxView 视图上,
#import <Lynx/LynxView.h>
#import "ViewController.h"
#import "DemoLynxProvider.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// ...
[lynxView loadTemplateFromURL:@"main.lynx" initData:nil];
}
@endimport UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ...
lynxView.loadTemplate(fromURL: "main.lynx", initData: nil)
}
}
然后你将在屏幕上看到如下内容:

恭喜你,现在你现在已经完成了 Lynx Engine 集成的全部工作!
#4. 进入 Lynx 世界
#将 Lynx 集成到 Android 平台
- 本文假设你已熟悉原生 Android 应用开发的基本概念。
- 下文中的所有代码,你都可以参考项目: integrating-lynx-demo-projects
#1. 依赖配置
#包依赖
- Lynx
Lynx Engine 核心能力,包含了解析 Bundle、样式解析、排版以及渲染视图等基础能力,以及 Lynx 页面依赖的 JavaScript 运行时基础代码
dependencies {
// lynx dependencies
implementation "org.lynxsdk.lynx:lynx:3.8.0"
implementation "org.lynxsdk.lynx:lynx-jssdk:3.8.0"
implementation "org.lynxsdk.lynx:lynx-trace:3.8.0"
implementation "org.lynxsdk.lynx:primjs:3.8.0"
}dependencies {
// lynx dependencies
implementation("org.lynxsdk.lynx:lynx:3.8.0")
implementation("org.lynxsdk.lynx:lynx-jssdk:3.8.0")
implementation("org.lynxsdk.lynx:lynx-trace:3.8.0")
implementation("org.lynxsdk.lynx:primjs:3.8.0")
}- Lynx Service
Lynx Service 包括 LynxImageService、LynxLogService 等,旨在提供一些宿主应用特性强相关的能力,允许宿主应用在运行时注入自定义实现,也可以使用 Lynx 提供的默认实现。
例如 LynxImageService 默认是使用 Fresco 图片库实现,在没有集成 Fresco 组件的应用上则可以依赖其他图片库,比如 Glide 来实现。Lynx 提供了标准的原生 Image、Log、Http 服务的能力,接入方可以快速接入并使用;
dependencies {
// lynx dependencies
implementation "org.lynxsdk.lynx:lynx:3.8.0"
implementation "org.lynxsdk.lynx:lynx-jssdk:3.8.0"
implementation "org.lynxsdk.lynx:lynx-trace:3.8.0"
implementation "org.lynxsdk.lynx:primjs:3.8.0"
// integrating image-service
implementation "org.lynxsdk.lynx:lynx-service-image:3.8.0"
// image-service dependencies, if not added, images cannot be loaded; if the host APP needs to use other image libraries, you can customize the image-service and remove this dependency
implementation "com.facebook.fresco:fresco:2.3.0"
implementation "com.facebook.fresco:animated-gif:2.3.0"
implementation "com.facebook.fresco:animated-webp:2.3.0"
implementation "com.facebook.fresco:webpsupport:2.3.0"
implementation "com.facebook.fresco:animated-base:2.3.0"
implementation "com.squareup.okhttp3:okhttp:4.9.0"
// integrating log-service
implementation "org.lynxsdk.lynx:lynx-service-log:3.8.0"
// integrating http-service
implementation "org.lynxsdk.lynx:lynx-service-http:3.8.0"
}dependencies {
// lynx dependencies
implementation("org.lynxsdk.lynx:lynx:3.8.0")
implementation("org.lynxsdk.lynx:lynx-jssdk:3.8.0")
implementation("org.lynxsdk.lynx:lynx-trace:3.8.0")
implementation("org.lynxsdk.lynx:primjs:3.8.0")
// integrating image-service
implementation("org.lynxsdk.lynx:lynx-service-image:3.8.0")
// image-service dependencies, if not added, images cannot be loaded; if the host APP needs to use other image libraries, you can customize the image-service and remove this dependency
implementation("com.facebook.fresco:fresco:2.3.0")
implementation("com.facebook.fresco:animated-gif:2.3.0")
implementation("com.facebook.fresco:animated-webp:2.3.0")
implementation("com.facebook.fresco:webpsupport:2.3.0")
implementation("com.facebook.fresco:animated-base:2.3.0")
// integrating log-service
implementation("org.lynxsdk.lynx:lynx-service-log:3.8.0")
// integrating http-service
implementation("org.lynxsdk.lynx:lynx-service-http:3.8.0")
implementation("com.squareup.okhttp3:okhttp:4.9.0")
}- More Elements
XElement 是 Lynx 团队维护的客户端扩展元件集合,提供更丰富的元件能力,能够让 Lynx 能够更快速的被用到生产环境中,提升 Lynx 生态的活力。
dependencies {
// lynx dependencies
implementation "org.lynxsdk.lynx:lynx:3.8.0"
implementation "org.lynxsdk.lynx:lynx-jssdk:3.8.0"
implementation "org.lynxsdk.lynx:lynx-trace:3.8.0"
implementation "org.lynxsdk.lynx:primjs:3.8.0"
// integrating image-service
implementation "org.lynxsdk.lynx:lynx-service-image:3.8.0"
// image-service dependencies, if not added, images cannot be loaded; if the host APP needs to use other image libraries, you can customize the image-service and remove this dependency
implementation "com.facebook.fresco:fresco:2.3.0"
implementation "com.facebook.fresco:animated-gif:2.3.0"
implementation "com.facebook.fresco:animated-webp:2.3.0"
implementation "com.facebook.fresco:webpsupport:2.3.0"
implementation "com.facebook.fresco:animated-base:2.3.0"
implementation "com.squareup.okhttp3:okhttp:4.9.0"
// integrating log-service
implementation "org.lynxsdk.lynx:lynx-service-log:3.8.0"
// integrating http-service
implementation "org.lynxsdk.lynx:lynx-service-http:3.8.0"
// integrating XElement
implementation "org.lynxsdk.lynx:xelement:3.8.0"
implementation "org.lynxsdk.lynx:xelement-input:3.8.0"
implementation "org.lynxsdk.lynx:xelement-overlay:3.8.0"
implementation "org.lynxsdk.lynx:xelement-svg:3.8.0"
implementation "org.lynxsdk.lynx:servalsvg:0.0.1-alpha.3"
}dependencies {
// lynx dependencies
implementation("org.lynxsdk.lynx:lynx:3.8.0")
implementation("org.lynxsdk.lynx:lynx-jssdk:3.8.0")
implementation("org.lynxsdk.lynx:lynx-trace:3.8.0")
implementation("org.lynxsdk.lynx:primjs:3.8.0")
// integrating image-service
implementation("org.lynxsdk.lynx:lynx-service-image:3.8.0")
// image-service dependencies, if not added, images cannot be loaded; if the host APP needs to use other image libraries, you can customize the image-service and remove this dependency
implementation("com.facebook.fresco:fresco:2.3.0")
implementation("com.facebook.fresco:animated-gif:2.3.0")
implementation("com.facebook.fresco:animated-webp:2.3.0")
implementation("com.facebook.fresco:webpsupport:2.3.0")
implementation("com.facebook.fresco:animated-base:2.3.0")
// integrating log-service
implementation("org.lynxsdk.lynx:lynx-service-log:3.8.0")
// integrating http-service
implementation("org.lynxsdk.lynx:lynx-service-http:3.8.0")
implementation("com.squareup.okhttp3:okhttp:4.9.0")
// integrating XElement
implementation("org.lynxsdk.lynx:xelement:3.8.0")
implementation("org.lynxsdk.lynx:xelement-input:3.8.0")
implementation("org.lynxsdk.lynx:xelement-overlay:3.8.0")
implementation("org.lynxsdk.lynx:xelement-svg:3.8.0")
implementation("org.lynxsdk.lynx:servalsvg:0.0.1-alpha.3")
}#配置混淆规则 (Proguard)
Lynx Engine 混淆规则如下,建议参考最新的源码配置:
# LYNX START
# use @Keep to annotate retained classes.
-dontwarn android.support.annotation.Keep
-keep @android.support.annotation.Keep class **
-keep @android.support.annotation.Keep class ** {
@android.support.annotation.Keep <fields>;
@android.support.annotation.Keep <methods>;
}
-dontwarn androidx.annotation.Keep
-keep @androidx.annotation.Keep class **
-keep @androidx.annotation.Keep class ** {
@androidx.annotation.Keep <fields>;
@androidx.annotation.Keep <methods>;
}
# native method call
-keepclasseswithmembers,includedescriptorclasses class * {
native <methods>;
}
-keepclasseswithmembers class * {
@com.lynx.tasm.base.CalledByNative <methods>;
}
# to customize a module, you need to keep the class name and the method annotated as LynxMethod.
-keepclasseswithmembers class * {
@com.lynx.jsbridge.LynxMethod <methods>;
}
-keepclassmembers class * {
@com.lynx.tasm.behavior.LynxProp <methods>;
@com.lynx.tasm.behavior.LynxPropGroup <methods>;
@com.lynx.tasm.behavior.LynxUIMethod <methods>;
}
-keepclassmembers class com.lynx.tasm.behavior.ui.UIGroup {
public boolean needCustomLayout();
}
# in case R8 compiler may remove mLoader in bytecode.
# as mLoader is not used in java and passed as a WeakRef in JNI.
-keepclassmembers class com.lynx.tasm.LynxTemplateRender {
private com.lynx.tasm.core.resource.LynxResourceLoader mLoader;
private com.lynx.tasm.core.resource.LynxResourceLoader mResourceLoader;
}
# the automatically generated setter classes use the class names of LynxBaseUI and ShadowNode and their subclasses.
-keep class com.lynx.tasm.behavior.ui.LynxBaseUI
-keep class com.lynx.tasm.behavior.shadow.ShadowNode
-keep class com.lynx.jsbridge.LynxModule { *; }
-keep class * extends com.lynx.tasm.behavior.ui.LynxBaseUI
-keep class * extends com.lynx.tasm.behavior.shadow.ShadowNode
-keep class * extends com.lynx.jsbridge.LynxModule { *; }
-keep class * extends com.lynx.jsbridge.LynxContextModule
-keep class * implements com.lynx.tasm.behavior.utils.Settable
-keep class * implements com.lynx.tasm.behavior.utils.LynxUISetter
-keep class * implements com.lynx.tasm.behavior.utils.LynxUIMethodInvoker
-keep class com.lynx.tasm.rendernode.compat.**{
*;
}
-keep class com.lynx.tasm.rendernode.compat.RenderNodeFactory{
*;
}
# LYNX END#2. 环境初始化
#LynxService 初始化
- Lynx Service 提供了宿主特性相关能力,推荐在应用的
Application#onCreate生命周期中完成 Lynx Service 的初始化; - Lynx Service 需主动注入;
Lynx 需要在应用启动时进行一些全局的初始化操作,请在 AndroidManifest.xml 文件中指定你自定义的 Application 类。
<application
android:name=".YourApplication">
</application>import android.app.Application;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.memory.PoolConfig;
import com.facebook.imagepipeline.memory.PoolFactory;
import com.lynx.service.http.LynxHttpService;
import com.lynx.service.image.LynxImageService;
import com.lynx.service.log.LynxLogService;
import com.lynx.tasm.service.LynxServiceCenter;
public class YourApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initLynxService();
}
private void initLynxService() {
// init Fresco which is needed by LynxImageService
final PoolFactory factory = new PoolFactory(PoolConfig.newBuilder().build());
ImagePipelineConfig.Builder builder =
ImagePipelineConfig.newBuilder(getApplicationContext()).setPoolFactory(factory);
Fresco.initialize(getApplicationContext(), builder.build());
LynxServiceCenter.inst().registerService(LynxImageService.getInstance());
LynxServiceCenter.inst().registerService(LynxLogService.INSTANCE);
LynxServiceCenter.inst().registerService(LynxHttpService.INSTANCE);
}
}import android.app.Application
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.facebook.imagepipeline.memory.PoolConfig
import com.facebook.imagepipeline.memory.PoolFactory
import com.lynx.service.http.LynxHttpService
import com.lynx.service.image.LynxImageService
import com.lynx.service.log.LynxLogService
import com.lynx.tasm.service.LynxServiceCenter
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
initLynxService()
}
private fun initLynxService() {
// init Fresco which is needed by LynxImageService
val factory = PoolFactory(PoolConfig.newBuilder().build())
val builder = ImagePipelineConfig.newBuilder(applicationContext).setPoolFactory(factory)
Fresco.initialize(applicationContext, builder.build())
LynxServiceCenter.inst().registerService(LynxImageService.getInstance())
LynxServiceCenter.inst().registerService(LynxLogService)
LynxServiceCenter.inst().registerService(LynxHttpService)
}
}#LynxEnv 初始化
LynxEnv 提供了 Lynx Engine 的全局初始化接口, 请保证 LynxEnv 的初始化发生在 Lynx Engine 的任何接口调用之前; 推荐在应用的 Application#onCreate 生命周期中完成 LynxEnv 的初始化;
import com.lynx.tasm.LynxEnv;
public class YourApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initLynxService();
initLynxEnv();
}
private void initLynxEnv() {
LynxEnv.inst().init(
this,
null,
null,
null
);
}
}