一、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        # 原生工厂

抽象工厂

1a7e960211994801002e7d76daf0faf2.svg

抽象工厂接口

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_PROVIDERWEBVIEW_ARC实际使用工厂
好看mixed-sailornairobi_onlyMixedBrowserFactory
贴吧nativenairobi_onlyNativeBrowserFactory
地图mixed-sailornairobi_onlyMixedBrowserFactory
知道mixed-sailornairobi_onlyMixedBrowserFactory
输入法mixed-sailornairobi_onlyMixedBrowserFactory
手百sailor没有LightBrowserFactory

运行时调用流程

1770693772295-9a1bd25d-58a4-47fa-9a4b-733136c2bba4.png ## 二、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);  
    }  
}
 
 
363bdd21c841b5837e020f831ea6324b.svg

其他媒体:源码编译 -> 打入 APK
手百:compileOnly JAR 保证 SDK 编译时不报错 -> 不打包,运行时用宿主的

compileOnly

  1. 编译期可见:JAR 提供类信息,代码可正常编译
  2. 打包期排除:不进入最终产物
  3. 运行时外置:由宿主环境提供实际实现

目录结构

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_PANEL0 / 1是否启用 WebPanel
BUILD_WITH_WEB_PANEL_DEPuniversal / 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_PANELBUILD_WITH_WEB_PANEL_DEP浏览框架
好看1universalnairobi
贴吧1universalnairobi
地图1universalnairobi
手百1没有compileOnly JAR
极速版1没有compileOnly JAR

维护指南

webpanel 新增/修改 览框架接口

新增/修改 手百适配层接口
  1. 修改手百宿主侧的实现,商业LightBrowserView的包装类NadWrappedBrowserView 1770703868372-c50928ac-a2f0-4dfa-a8bb-f90e1dbd3927.png
  2. 从手百导出新的 JAR 更新到libs/compileOnly/baiduboxapp/ 1770703890653-23fbe67f-0511-4ab1-b9e7-667e1411cd16.png
新增/修改 Nairobi 接口
  1. 修改AbsNadBrowserView抽象方法 1770703906256-2b6fa824-b36f-44f7-9e32-751fa079a809.png
  2. 同步修改NadLightBrowserViewNadNativeBrowserView 1770703920933-34131e25-0f81-4736-b124-9b42d7568fbe.png

更新: 2026-02-10 14:12:35
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/kwrszqrm90hg7hde


相关笔记