一、Nairobi 浏览框架分包
商业 SDK 支持两种浏览内核:
- 原生 WebView:系统自带,兼容性好
- 百度 T7 内核:百度自研,本土化改造,更安全?
Nairobi 框架的作用:统一封装这两种内核,上层业务代码通过AbsNadBrowserView调用。屏蔽了底层差异,业务代码不需要关心是原生还是百度 T7。
目录结构
lib-webview/src/main/nadbrowser/
.
├── common # 共享代码
│ └── com.baidu.nadcore.webview
│ ├── container
│ │ ├── AbsBrowserContainer.kt # 容器抽象基类
│ │ └── NadBrowserContainer.kt # 容器实现le
│ ├── NadBrowserFactory.kt # 抽象工厂
│ ├── view
│ │ ├── AbsNadBrowserView.kt # 视图抽象基类
│ │ ├── BdGeolocationDialog.java
│ │ ├── BdHttpAuthenticationDialog.java
│ │ ├── NadBrowserStateView.kt
│ │ └── OnWebViewHookHandler.kt
│ ├── webevent
│ └───└── IWebEventNotifier.kt
│
├── light.com.baidu.nadcore.webview # T7 内核实现(sailor/swan 构建)
│ ├── LightBrowserFactory.kt
│ └── NadLightBrowserView.kt
├── mixed.com.baidu.nadcore.webview
│ └── MixedBrowserFactory.kt # 混合工厂
│
└── native.com.baidu.nadcore.webview
├── NadNativeBrowserView.kt
└── NativeBrowserFactory.kt # 原生工厂抽象工厂
抽象工厂接口:
object NadBrowserFactory {
// 编译时注入的工厂实例
@JvmStatic
val FACTORY: Factory = BuildConfig.BROWSER_FACTORY
// 内核类型常量
const val TYPE_LIGHT_BROWSER = 0 // T7 内核
const val TYPE_NATIVE_BROWSER = 1 // 原生内核
interface Factory {
// 创建 BrowserView
fun createBrowserView(context: Context, consumeData: ConsumeData?, type: Int): AbsNadBrowserView
// 省略一些其他没看懂的方法...
}Gradle 配置
| 参数 | 说明 | 值 |
|---|---|---|
WEBVIEW_PROVIDER | 浏览内核类型 | native / sailor / swan / mixed-sailor / mixed-swan |
WEBVIEW_ARC | 框架版本 | nairobi_only / old_arc_only |
源码集
选择编译打包的目录。
// lib-webview/build.gradle
// 新架构
sourceSets {
if ("${WEBVIEW_ARC}" != 'old_arc_only') {
println("WEBVIEW_ARC is ${WEBVIEW_ARC}, add nadbrowser src")
main.java.srcDirs 'src/main/java'
main.java.srcDirs 'src/main/arch'
main.java.srcDirs 'src/main/nadbrowser/common'
switch ("${WEBVIEW_PROVIDER}") {
case 'swan':
// swan和sailor都使用LightFactory
case 'sailor':
main.java.srcDirs 'src/main/nadbrowser/light'
break
case 'mixed-swan':
case 'mixed-sailor':
main.java.srcDirs 'src/main/nadbrowser/mixed'
main.java.srcDirs 'src/main/nadbrowser/light'
main.java.srcDirs 'src/main/nadbrowser/native'
break
case 'native':
main.java.srcDirs 'src/main/nadbrowser/native'
break
default:
throw new IllegalStateException("Unsupported WEBVIEW_PROVIDER:${property('WEBVIEW_PROVIDER')}")
break
}
}
if (property("BUILD_WITH_BAIDU_CRASHPAD") == 1) {
main.java.srcDirs 'src/main/zwcrashpad'
} else {
main.java.srcDirs 'src/main/emptycrashpad'
}
}
变体
通过 Gradle 在编译时确定具体工厂实现,注入到 BuildConfig:
// lib-webview/build.gradle
// 新架构
android.libraryVariants.all { variant ->
if ("${WEBVIEW_ARC}" == 'old_arc_only') {
return
}
// webview实现编译配置
def PACKAGE = "com.baidu.nadcore.webview."
switch ("${WEBVIEW_PROVIDER}") {
case 'swan':
// swan和sailor都使用LightFactory
case 'sailor':
variant.buildConfigField "${PACKAGE}NadBrowserFactory.Factory", "BROWSER_FACTORY", "new ${PACKAGE}LightBrowserFactory()"
break
case 'mixed-swan':
case 'mixed-sailor':
variant.buildConfigField "${PACKAGE}NadBrowserFactory.Factory", "BROWSER_FACTORY", "new ${PACKAGE}MixedBrowserFactory()"
break
case 'native':
variant.buildConfigField "${PACKAGE}NadBrowserFactory.Factory", "BROWSER_FACTORY", "new ${PACKAGE}NativeBrowserFactory()"
break
default:
throw new IllegalStateException("Unsupported WEBVIEW_PROVIDER:${property('WEBVIEW_PROVIDER')}")
break
}
}编译时自动生成,不同构建配置生成不同内容的 BuildConfig:
public final class BuildConfig {
...
// Fields from the variant
// sailor 构建
public static final NadBrowserFactory.Factory BROWSER_FACTORY = new com.baidu.nadcore.webview.LightBrowserFactory();
// native 构建
public static final NadBrowserFactory.Factory BROWSER_FACTORY = new com.baidu.nadcore.webview.NativeBrowserFactory();
// mixed 构建
public static final NadBrowserFactory.Factory BROWSER_FACTORY = new com.baidu.nadcore.webview.MixedBrowserFactory();
}历史逻辑
如果大家自己看代码,可能会发现除了上面的逻辑以外还有很多其他逻辑。
代码里面分了新架构和老架构,可以看下目录结构。
es
❯ cd src/main/
❯ tree -L 2
❯ pwd
lib-webview/src/main
.
├── AndroidManifest.xml
├── arch
│ └── com
├── emptycrashpad
│ └── com.baidu.nadcore.crashpad
├── nadbrowser # nairobi_only
│ ├── common
│ ├── light
│ ├── mixed
│ └── native
├── res
│ ├── anim
│ ├── drawable
│ ├── drawable-xxhdpi
│ ├── layout
│ └── values
├── webview # old_arc_only
│ ├── common
│ ├── light
│ ├── mixed
│ └── native
└── zwcrashpad
└── com.baidu.nadcore.crashpad源码集
// lib-webview/build.gradle
// 老架构
sourceSets {
if ("${WEBVIEW_ARC}" != 'nairobi_only') {
println("WEBVIEW_ARC is ${WEBVIEW_ARC}, add webview src")
main.java.srcDirs 'src/main/java'
main.java.srcDirs 'src/main/arch'
main.java.srcDirs 'src/main/webview/common'
switch ("${WEBVIEW_PROVIDER}") {
case 'swan':
// swan和sailor都使用LightFactory
case 'sailor':
main.java.srcDirs 'src/main/webview/light'
break
case 'mixed-swan':
case 'mixed-sailor':
main.java.srcDirs 'src/main/webview/mixed'
main.java.srcDirs 'src/main/webview/light'
main.java.srcDirs 'src/main/webview/native'
break
case 'native':
main.java.srcDirs 'src/main/webview/native'
break
default:
throw new IllegalStateException("Unsupported WEBVIEW_PROVIDER:${property('WEBVIEW_PROVIDER')}")
break
}
}
}变体
// lib-webview/build.gradle
// 老架构
android.libraryVariants.all { variant ->
if ("${WEBVIEW_ARC}" == 'nairobi_only') {
return
}
// webview实现编译配置
def PACKAGE = "com.baidu.nadcore.webview."
if ("${WEBVIEW_ARC}" != 'old_arc_only') {
// 混合模式,启用新包名
PACKAGE = "com.baidu.nadcore.webviewx."
}
switch ("${WEBVIEW_PROVIDER}") {
case 'swan':
// swan和sailor都使用LightFactory
case 'sailor':
variant.buildConfigField "${PACKAGE}BrowserContainer.Factory", "BROWSERCONTAINER_FACTORY", "new ${PACKAGE}LightFactory()"
break
case 'mixed-swan':
case 'mixed-sailor':
variant.buildConfigField "${PACKAGE}BrowserContainer.Factory", "BROWSERCONTAINER_FACTORY", "new ${PACKAGE}MixedFactory()"
break
case 'native':
variant.buildConfigField "${PACKAGE}BrowserContainer.Factory", "BROWSERCONTAINER_FACTORY", "new ${PACKAGE}NativeFactory()"
break
default:
throw new IllegalStateException("Unsupported WEBVIEW_PROVIDER:${property('WEBVIEW_PROVIDER')}")
break
}
}各媒体配置
| 媒体 | WEBVIEW_PROVIDER | WEBVIEW_ARC | 实际使用工厂 |
|---|---|---|---|
| 好看 | mixed-sailor | nairobi_only | MixedBrowserFactory |
| 贴吧 | native | nairobi_only | NativeBrowserFactory |
| 地图 | mixed-sailor | nairobi_only | MixedBrowserFactory |
| 知道 | mixed-sailor | nairobi_only | MixedBrowserFactory |
| 输入法 | mixed-sailor | nairobi_only | MixedBrowserFactory |
| 手百 | sailor | 没有 | LightBrowserFactory |
运行时调用流程
## 二、WebPanel 分包适配
WebPanel 是一个半屏 PopupWindow + 浏览框架的组合组件。问题:
- Nairobi 是 SDK 浏览框架抽象层,通过抽象工厂 + b突变体屏蔽了 WebView 和 T7 内核的差异
- 其他媒体(好看、贴吧等)使用 SDK 的 nairobi 框架。手百宿主又很特殊,直接使用宿主的轻浏览框架,不走 nairobi
- WebPanel 相当于是上层的业务,需要适配使用 Nairobi 和使用宿主浏览框架两种情况
WebPanel 需要适配这两种情况,让同一套业务代码能在两种环境下都能运行
同包名类替换
比如WebPanelBrowserContainer等类,使用相同的包名和类名,但有两套实现。一套在手百中维护,一套在 SDK 中维护。
手百:
package com.baidu.nadcore.webpanel.proxy;
public class WebPanelBrowserContainer extends NadWrappedBrowserContainer {
public WebPanelBrowserContainer(@NonNull IFrameContext frameContext,
@NonNull IFrameExtHandler frameExtHandler,
@Nullable IWebViewNotifier webViewNotifier) {
super(frameContext, frameExtHandler, webViewNotifier);
}
}SDK:
package com.baidu.nadcore.webpanel.proxy;
public class WebPanelBrowserContainer extends NadBrowserContainer implements IBrowserContainerLike {
public WebPanelBrowserContainer(@NonNull IFrameContext frameContext,
@NonNull IFrameExtHandler frameExtHandler,
@Nullable IWebEventNotifier frameWebEventNotifier) {
super(frameContext, frameExtHandler, frameWebEventNotifier);
}
@Override public void registerAction(@NonNull IContainerUrlLoadAction action) {
super.registerAction(action);
}
}
其他媒体:源码编译 -> 打入 APK
手百:compileOnly JAR 保证 SDK 编译时不报错 -> 不打包,运行时用宿主的
compileOnly:
- 编译期可见:JAR 提供类信息,代码可正常编译
- 打包期排除:不进入最终产物
- 运行时外置:由宿主环境提供实际实现
目录结构
lib-business/src/main/
.
├── webpanel/
│ ├── java/ # 上层业务
│ │ └── com/baidu/nadcore/webpanel/
│ │ ├── NadWebPanel.kt
│ │ ├── PanelBrowserView.kt
│ │ ├── PanelPopupWindow.java
│ │ └── ...
│ │
│ ├── compat/universal/ # 通用媒体适配,nairobi 走这个
│ │ └── com/baidu/nadcore/webpanel/
│ │ ├── proxy/
│ │ │ └── WebPanelBrowserContainer.java
│ │ └── event/
│ │ └── WebpanelWebEventNotifier.kt
│
└── libs/compileOnly/baiduboxapp/
├── classes_ad_runtime.jar # WebPanelBrowserContainer 等等
└── classes_lightbrowser.jar # 轻浏览框架依赖Gradle 配置
| 变量 | 可选值 | 作用 |
|---|---|---|
BUILD_WITH_WEB_PANEL | 0 / 1 | 是否启用 WebPanel |
BUILD_WITH_WEB_PANEL_DEP | universal / baiduboxapp | 哪种浏览内核依赖 |
// 手百特定依赖
if ("${BUILD_WITH_WEB_PANEL}" == "1" && "$targetApp".startsWith('baiduboxapp')) {
compileOnly fileTree(dir: "libs/compileOnly/baiduboxapp", include: ["*.jar"])
}
// 通用模式依赖 lib-webview
if ("${BUILD_WITH_WEB_PANEL_DEP}" == "universal") {
implementation deps.business.lib_nadcore_sdk.webview
}各媒体配置
| 媒体 | BUILD_WITH_WEB_PANEL | BUILD_WITH_WEB_PANEL_DEP | 浏览框架 |
|---|---|---|---|
| 好看 | 1 | universal | nairobi |
| 贴吧 | 1 | universal | nairobi |
| 地图 | 1 | universal | nairobi |
| 手百 | 1 | 没有 | compileOnly JAR |
| 极速版 | 1 | 没有 | compileOnly JAR |
维护指南
webpanel 新增/修改 览框架接口
新增/修改 手百适配层接口
- 修改手百宿主侧的实现,商业
LightBrowserView的包装类NadWrappedBrowserView
- 从手百导出新的 JAR 更新到
libs/compileOnly/baiduboxapp/
新增/修改 Nairobi 接口
- 修改
AbsNadBrowserView抽象方法
- 同步修改
NadLightBrowserView和NadNativeBrowserView
更新: 2026-02-10 14:12:35
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/kwrszqrm90hg7hde