跳转到内容

SuperSDK Android 接入文档

版本:dev | 稳定性:开发中 — 内容随时变更,仅供内部参考

SuperSDK 是一个 Android WebView 容器 SDK,提供 WebView 容器、JSBridge、离线包、云控配置等能力,帮助宿主 App 快速集成 H5 容器。

项目要求
minSdkVersion23 (Android 6.0)
compileSdkVersion35 或更高(本仓使用 36)
Java 版本Java 11+
依赖库AndroidX
dependencies {
// 必选:SDK 核心(容器 / JSBridge / 离线包 / 云控)
implementation 'com.supersdk:supersdk:版本号'
// 可选:Google 登录(详见「可选能力模块接入 · Google 登录」)
implementation 'com.supersdk:supersdk-auth-google:版本号'
// 可选:Google Pay 应用内购买(详见「可选能力模块接入 · Google Pay」)
implementation 'com.supersdk:supersdk-pay-google:版本号'
// 可选:AdMob 广告 Provider(详见「AdService · AdMob 接入」)
implementation 'com.supersdk:supersdk-ad-admob:版本号'
}

可选模块按需引入:只接 H5 容器不需要登录、支付或广告时,仅引 com.supersdk:supersdk 即可;核心 artifact 不传递引入 Google Mobile Ads。

SNAPSHOT 版本需额外添加快照仓库(最新版本号见 CHANGELOG):

repositories {
maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
}
  1. 将 supersdk-版本号.aar 放入项目 libs/ 目录
  2. 在 build.gradle 中添加:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
}

SDK 核心模块不会替宿主声明 INTERNET 权限。android-sdk-impl 会 merge CAMERA(用于 <input type="file" capture> 拍照),不会 merge RECORD_AUDIO。宿主必须自行在 AndroidManifest.xml 添加:

<manifest ...>
<!-- 必选:WebView / 网络请求 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- H5 getUserMedia 麦克风:SDK 不 merge,宿主必须自己声明,否则系统弹不出权限框 -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
...
<!-- 仅当需要加载 http(非 https)资源时 -->
android:usesCleartextTraffic="true">
...
</application>
</manifest>

CAMERA 仍由 SDK merge,宿主不必再抄一遍。RECORD_AUDIO 必须由宿主自己声明(即使只用 SuperPageActivity、Java 零改动)。

可选模块(登录/支付/广告)各自的 Manifest 与 Google 后台配置,见下文「可选能力模块接入」与「AdService」。

HTML5 视频全屏横屏:SDK 内置的 SuperPageActivity(即 IPageController.open() 打开的全屏页)已声明 configChanges,视频进入全屏会自动横屏。若你把 AbsSuperPageView 嵌入自己的 Activity(createPageView() 方式),需给该 Activity 声明 android:configChanges="orientation|screenSize|keyboardHidden",否则 SDK 为避免 Activity 重建中断播放,不会触发横屏旋转(仅竖屏全屏)。

WebView 麦克风与文件选择(宿主接入)

Section titled “WebView 麦克风与文件选择(宿主接入)”

H5 navigator.mediaDevices.getUserMedia({ audio: true }) 与 <input type="file"> 由 SDK 容器内的 WebChromeClient 处理。禁止替换 WebChromeClient / WebViewClient(含 getWebView().setWebChromeClient(...)),否则会丢掉 JSBridge、离线包、文件选择和麦克风授权。

路径 1 · 默认全屏页(零 Java)

IPageController.open() / SuperPageActivity 已实现公开 com.supersdk.api.page.IFileChooserHost(含独立的 launchMicrophonePermission),不用写 Java。宿主只需在自己的 Manifest 声明 RECORD_AUDIO(见上一节)。CAMERA 仍由 SDK merge。

路径 2 · 自包装 Activity

把 createPageView() 嵌进自己的 Activity 时:实现 com.supersdk.api.page.IFileChooserHost,在 onCreate(STARTED 之前)注册 launcher,再 setFileChooserHost(this)。不要反射、不要强转 impl 类型、不要换 ChromeClient。相机与麦克风必须使用独立 pending / launcher,不可共用一个 callback 槽。

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.supersdk.SuperSDK;
import com.supersdk.api.page.AbsSuperPageView;
import com.supersdk.api.page.FileChooserResultCallback;
import com.supersdk.api.page.IFileChooserHost;
import com.supersdk.api.page.PageLoadConfig;
import com.supersdk.api.page.PermissionResultCallback;
public class MyWebActivity extends AppCompatActivity implements IFileChooserHost {
private AbsSuperPageView pageView;
private ActivityResultLauncher<Intent> fileChooserLauncher;
private ActivityResultLauncher<String> cameraPermissionLauncher;
private ActivityResultLauncher<String> microphonePermissionLauncher;
@Nullable private FileChooserResultCallback pendingFileChooserCallback;
@Nullable private PermissionResultCallback pendingCameraCallback;
@Nullable private PermissionResultCallback pendingMicrophoneCallback;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fileChooserLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
FileChooserResultCallback cb = pendingFileChooserCallback;
pendingFileChooserCallback = null;
if (cb != null) cb.onResult(result.getResultCode(), result.getData());
});
cameraPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
granted -> {
PermissionResultCallback cb = pendingCameraCallback;
pendingCameraCallback = null;
if (cb != null) cb.onResult(Boolean.TRUE.equals(granted));
});
microphonePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
granted -> {
PermissionResultCallback cb = pendingMicrophoneCallback;
pendingMicrophoneCallback = null;
if (cb != null) cb.onResult(Boolean.TRUE.equals(granted));
});
pageView = SuperSDK.getPageController().createPageView();
setContentView(pageView);
pageView.setFileChooserHost(this);
pageView.load("https://example.com", new PageLoadConfig("https://example.com"));
}
@Override
public void launchFileChooser(@NonNull Intent intent, @NonNull FileChooserResultCallback callback) {
pendingFileChooserCallback = callback;
fileChooserLauncher.launch(intent);
}
@Override
public void launchCameraPermission(@NonNull PermissionResultCallback callback) {
pendingCameraCallback = callback;
cameraPermissionLauncher.launch(Manifest.permission.CAMERA);
}
@Override
public void launchMicrophonePermission(@NonNull PermissionResultCallback callback) {
pendingMicrophoneCallback = callback;
microphonePermissionLauncher.launch(Manifest.permission.RECORD_AUDIO);
}
@NonNull
@Override
public Context getHostContext() {
return getApplicationContext();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (pageView != null) pageView.destroyView();
}
}

本轮 Android 只授权纯音频。H5 若同时要摄像头(VIDEO_CAPTURE),整次 deny。getUserMedia 需 https 安全源。


在 Application.onCreate() 中初始化 SDK:

import com.supersdk.SuperSDK;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Replace ab12cd34 with the real spid issued by the SuperSDK console.
SuperSDK.init(this, "ab12cd34", new MySDKProvider());
}
}

参数说明:

参数类型说明
contextContextApplication Context
spidString控制台签发的 8 位小写字母/数字项目标识
providerSuperSDKProvider宿主实现的服务提供者

项目环境对应控制台顶栏的 env(如 prod / staging),决定 SDK 拉取哪套云控配置(settings)以及埋点上报归属的环境。它不再是 init 参数,而是独立的运行时状态。

方法签名:public static void setAPIEnvironment(String environment) / public static String getAPIEnvironment()

// 设置项目环境(init 前后均可调用;非法值抛 IllegalArgumentException 且不落地)
SuperSDK.setAPIEnvironment("staging");
// 读取当前项目环境(未设置时返回 "prod",不要求已 init)
String env = SuperSDK.getAPIEnvironment();

规则与行为:

  • 取值限 1–10 位小写字母/数字(^[a-z0-9]{1,10}$),不 trim、不做大小写转换;null 视为 prod
  • SDK 不持久化该值:进程启动默认 prod,宿主如需记忆用户选择,需自行持久化并在启动后调用 setAPIEnvironment
  • 运行时切换的副作用顺序:冲刷旧环境的存量上报事件 → 换值 → settings 清本地缓存并按新环境重拉 → 刷新存活 WebView 的 UA(仅影响后续导航)
  • 切换后 settings 拉取自动带 env 参数、上报 basic_data.env 与请求头 X-Super-API-ENV 均使用新值;H5 侧经 getEnv() 读到新值
  • 该环境与 HostConfigService.getSdkApiBaseUrls()(API 域名)是不同维度:切环境不需要也不应该改 API 域名

SuperSDKProvider 是宿主 App 向 SDK 提供能力的桥梁。所有 Service 方法均为可选实现(默认返回 null),按需实现即可。

import com.supersdk.api.SuperSDKProvider;
public class MySDKProvider extends SuperSDKProvider {
private final MyHostConfigService hostConfigService = new MyHostConfigService();
@Override
public HostConfigService getHostConfigService() {
return hostConfigService;
}
@Override
public EventService getEventService() {
return null; // 按需实现
}
@Override
public LoginService getLoginService() {
return null; // 按需实现
}
@Override
public HostMethodService getHostMethodService() {
return null; // 按需实现
}
@Override
public NavigationService getNavigationService() {
return null; // 按需实现
}
@Override
public AdService getAdService() {
return null; // 按需实现
}
@Override
public PayService getPayService() {
return null; // 按需实现(见「可选能力模块接入 · Google Pay」)
}
@Override
public INativeBlockProvider getNativeBlockService() {
return null; // 按需实现
}
}

通过 SuperSDK.getPageController() 获取。

IPageController pageController = SuperSDK.getPageController();
// 打开全屏页面
pageController.open("https://example.com");
// 创建 PageView 嵌入到自己的布局中
AbsSuperPageView pageView = pageController.createPageView();
方法说明
open(String url)在新的全屏页面中打开 URL
createPageView()创建一个可嵌入的 PageView 实例

AbsSuperPageView 继承自 FrameLayout,是 SDK 的核心容器视图。

AbsSuperPageView pageView = SuperSDK.getPageController().createPageView();
// 添加到布局
container.addView(pageView);
// 加载 URL
pageView.load("https://example.com", new PageLoadConfig("https://example.com"));
// 适配沉浸式安全区域(自动获取状态栏和导航栏高度)
pageView.fitSystemWindows(this);
// 处理返回键(优先级:前端拦截 > 页面历史回退 > exitAction)
@Override
public void onBackPressed() {
pageView.handleBackPressed(() -> super.onBackPressed());
}
// 销毁(必须调用)
@Override
protected void onDestroy() {
super.onDestroy();
pageView.destroyView();
}
方法说明
load(String url, PageLoadConfig config)加载 URL
reload()重新加载当前页面
stopLoading()停止加载
canGoBack()是否可以后退
goBack()后退一页
handleBackPressed(Runnable exitAction)处理返回键,优先级:前端拦截 > 页面历史回退 > exitAction
setSafeArea(int statusBarHeight, int navigationBarHeight)设置安全区域(物理像素)
fitSystemWindows(Activity activity)自动适配沉浸式安全区域
setImmersiveBottom(boolean immersive)底部是否沉浸(默认 true=内容延伸到系统导航栏下,现有行为)。置 false(浏览器场景):WebView 内容缩短到导航栏之上 + 页面 env(safe-area-inset-bottom)=0 + UA SafeAreaBottom=0,第三方站点底栏紧贴系统导航栏、无多余白边。仅作用底部(顶部恒沉浸);应在 fitSystemWindows/首次 load 前调用。需宿主为 edge-to-edge 且不在上游消费底部 inset
setOnClosePageListener(Runnable listener)设置关闭页面监听(前端调用 closePage 时触发)
sendBroadcast(String eventKey, String jsonData)向当前 WebView 发送广播
destroyView()销毁视图,释放资源
getWebView()获取底层 WebView 实例(用于直调 evaluateJavascript/settings 等原生能力);⚠️ 勿替换 WebViewClient/WebChromeClient 或重置 settings,否则会破坏内置 JSBridge 与离线包拦截

通过 SuperSDK.getOfflineController() 获取。

IOfflineController offlineController = SuperSDK.getOfflineController();
// 查询所有离线包状态
List<OfflinePackageState> packages = offlineController.getAllPackages();
// 查询指定离线包
OfflinePackageState state = offlineController.getPackage("example.com");
// 更新指定离线包(拉取站点远程清单 → 下载安装,详见「离线包远程更新」小节)
offlineController.preloadPackage("example.com", new OfflineUpdateListener() {
@Override
public void onProgress(@NonNull String host, int progress) {
// 下载进度 0-100
}
@Override
public void onComplete(@NonNull OfflineUpdateResult result) {
if (result.isSuccess()) {
// 更新成功
}
}
});
// 更新全部离线包
offlineController.preloadAllPackages(listener);

仅包含当前状态正常(可用)的离线包信息。

方法返回类型说明
getHost()String离线包域名标识
getLocalVersion()String本地版本号
getSource()Source来源:ASSETS(内置)/ DOWNLOAD(云端下载)/ MANUAL(外部手动安装)
方法返回类型说明
getHost()String离线包域名标识
getCode()Code结果码(见下表)
getMessage()String结果描述信息
getNewVersion()String更新后的版本号
isSuccess()boolean是否成功(SUCCESS 或 ALREADY_UP_TO_DATE)

Code 枚举值:

Code说明
SUCCESS更新成功
ALREADY_UP_TO_DATE已是最新版本
DOWNLOAD_FAILED下载失败
VERIFY_FAILED校验失败
EXTRACT_FAILED解压失败
PACKAGE_NOT_CONFIGURED离线包未配置
ALREADY_UPDATING正在更新中
UNKNOWN_ERROR未知错误

SDK 支持从远程站点自动获取并安装最新离线包,无需宿主额外接入代码即可生效。

自动更新行为:

  • 探测时机:打开任意 http(s) 页面时,SDK 在页面加载过程中后台静默探测该 host 站点根路径下的 super_meta.json(站点侧发布规范见 OFFLINE_PKG_SPEC.md),不阻塞页面渲染,站点未接入(404)视为正常状态
  • 生效时机:探测到新版本并下载安装成功后,下次加载该 host 页面时生效,不会刷新当前已打开的页面
  • 大小上限:自动模式下单包 50MB 以内才会自动下载,超过则跳过(仅显式调用可下载)
  • 探测频控:同一 host 最小探测间隔 30 分钟,避免频繁开关页对站点造成请求压力

显式触发更新:宿主可主动调用 preloadPackage / preloadAllPackages 立即检查更新,绕过频控与大小上限:

IOfflineController offlineController = SuperSDK.getOfflineController();
// 立即检查并更新指定 host 的离线包(绕过频控与 50MB 上限)
offlineController.preloadPackage("example.com", new OfflineUpdateListener() {
@Override
public void onProgress(@NonNull String host, int progress) {
// 下载进度 0-100
}
@Override
public void onComplete(@NonNull OfflineUpdateResult result) {
if (result.isSuccess()) {
// 更新成功,下次加载该 host 页面生效
}
}
});
// 立即检查并更新全部已装离线包
offlineController.preloadAllPackages(listener);

首次调用时若本地尚未安装该 host 的离线包,只要远程清单探测成功,同样会走首装流程(首装同样在下次加载时生效,不影响本次页面渲染)。

站点侧要求:站点需在根路径发布符合 super_meta.json v1 规范的清单文件,并保证声明的 pkg_path 与站点同源。完整字段定义、zip 打包工具链约束(zip -r deflate,不支持流式 data-descriptor 产物)见 OFFLINE_PKG_SPEC.md。

SDK 内置的用户反馈提交(com.supersdk.base.feedback),与 H5 的 SuperSDK.submitFeedback 同一条链路:POST /superapi/v2/feedback → 控制台「业务运营 → 用户反馈」按项目 × 环境查看、搜索并标记处理状态。SDK 自动补齐 spid / env / device_id / platform / app_version / sdk_version / os_version / device_model,服务端记录提交时间与用户 IP;宿主只需提供业务字段。

import com.supersdk.base.feedback.FeedbackRequest;
import com.supersdk.base.feedback.SFeedback;
FeedbackRequest request = new FeedbackRequest("settings", "打开设置页闪退", "进入设置页后约 1 秒必现闪退")
.setSubScene("about") // 可选
.setUserId(currentUserId) // 可选:业务用户 ID,SDK 不维护用户身份
.setExtra(new JSONObject().put("page", "settings")); // 可选:附加信息(≤4KB)
SFeedback.submit(request, new SFeedback.Callback() {
@Override
public void onSuccess(long feedbackId) { /* 已入库,feedbackId 为服务端记录 ID */ }
@Override
public void onFailure(@NonNull String code, @NonNull String message) {
// code: INVALID_ARGUMENT(本地校验失败,未发请求)/ NETWORK_ERROR / RATE_LIMITED /
// UNAVAILABLE / PROTOCOL_MISMATCH,或服务端业务 code(INVALID_REQUEST / NOT_FOUND …)
}
});
字段必填限制
scene✓≤64 字符
title✓≤200 字符
content✓≤5000 字符
subScene≤64 字符
userId≤128 字符
extraJSON object,序列化后 ≤4KB

回调恒在 UI 线程;所有字符串会被 trim,长度按字符数计(服务端为权威)。限流:每 IP 10 次/分钟、每项目 600 次/分钟。提交不是幂等的,SDK 不自动重试。

Native Block 允许 H5 页面嵌入宿主 App 提供的原生视图(如广告、视频播放器等)。

// 1. 实现 INativeBlockProvider
public class MyNativeBlockProvider implements INativeBlockProvider {
@Override
public boolean supports(String blockType) {
return "ad-banner".equals(blockType) || "video-player".equals(blockType);
}
@Override
public View createBlockView(Context context, String blockId, String blockType, String config) {
switch (blockType) {
case "ad-banner":
return createAdView(context, config);
case "video-player":
return createVideoView(context, config);
default:
return null;
}
}
@Override
public void onBlockShow(String blockId, View view) {
// Block 进入可视区域
}
@Override
public void onBlockHide(String blockId, View view) {
// Block 离开可视区域
}
@Override
public void onBlockDestroy(String blockId, View view) {
// Block 被销毁,释放资源
}
}
// 2. 注册 Provider(二选一)
// 方式 A:通过 SuperSDKProvider
@Override
public INativeBlockProvider getNativeBlockService() {
return new MyNativeBlockProvider();
}
// 方式 B:动态设置
SuperSDK.setNativeBlockProvider(new MyNativeBlockProvider());
方法说明
supports(String blockType)是否支持指定类型
createBlockView(Context, String, String, String)创建原生视图
onBlockShow(String, View)Block 进入可视区域回调
onBlockHide(String, View)Block 离开可视区域回调
onBlockDestroy(String, View)Block 被销毁回调

提供宿主 App 的基本配置信息。

public class MyHostConfigService implements HostConfigService {
@Override
public String getAppChannel() {
return "GooglePlay"; // 渠道标识
}
// getNetEnv() / ApiEnv 已废弃(SDK 从未消费):项目环境请使用
// SuperSDK.setAPIEnvironment(...);API 域名请使用 getSdkApiBaseUrls()。
@Override
public int getAppVersionCode() {
return BuildConfig.VERSION_CODE;
}
@Override
public String getAppVersionName() {
return BuildConfig.VERSION_NAME;
}
@Override
public String getSdkApiBaseUrl() {
return null; // 自定义 API 地址,null 使用默认
}
@Override
public String generateDeviceId() {
return null; // 宿主接管 deviceId 生成,null 使用 SDK 默认逻辑
}
}

generateDeviceId - 宿主接管 deviceId 生成(可选):

  • 仅在 SDK 需要生成新 deviceId 时调用(首次安装、或本地旧值失效),本地已持久化的 deviceId 继续沿用,不受影响(缓存优先)。
  • 返回非空字符串:SDK 采用该值作为 deviceId 并持久化;返回 null/空串:走 SDK 默认生成逻辑(自生成随机 UUID 并持久化到 SP)。
  • 实现要求:同步快速返回(可能在网络请求/事件上报路径早期调用),且多次调用应返回稳定值。宿主实现抛异常时 SDK 会捕获并回退默认逻辑。

🔒 隐私合规:SDK 不读取 Settings.Secure.ANDROID_ID,也不读取 IMEI / MAC / OAID 等系统设备标识。 deviceId 的完整降级链为 内存缓存 → SP 持久化值 → 宿主 generateDeviceId() → SDK 自生成 UUID。 旧版本 SDK 曾把 ANDROID_ID 写入 SP,升级后这些存量值继续沿用(不迁移,避免老用户设备标识断层); 如宿主的合规要求需要彻底清除存量值,可在自身 generateDeviceId() 中接管并返回自有标识。

接收 SDK 产生的事件,宿主可对接自己的埋点平台。

public class MyEventService implements EventService {
@Override
public void onEvent(@NonNull Event evt) {
String eventName = evt.getEventName();
Map<String, Object> params = evt.getParams();
// 上报到自己的埋点平台
Analytics.track(eventName, params);
}
}

当 H5 页面需要登录时,SDK 会调用此服务。

public class MyLoginService implements LoginService {
@Override
public void requestLogin(@NonNull Context context,
@NonNull LoginResultCallback callback) {
// 拉起宿主的登录流程
LoginManager.login(context, result -> {
if (result.isSuccess()) {
HostLoginInfo loginInfo = new HostLoginInfo(result.getAuthCode());
callback.onResult("SUCCESS", "登录成功", loginInfo);
} else {
callback.onResult("FAIL", "登录失败", null);
}
});
}
}

HostLoginInfo:

方法类型说明
getAuthCode()String登录授权码
getExtraAuthInfo()HostLoginExtraAuthInfo扩展登录信息(可选)

HostLoginExtraAuthInfo:

方法类型说明
getLoginType()String登录方式
getToken()StringToken
getRefreshToken()StringRefresh Token
getRefreshTokenExpiresAt()longRefresh Token 过期时间
isFirstLogin()boolean是否首次登录
getExtra()JSONObject额外扩展数据

允许 H5 调用宿主自定义的方法,实现业务扩展。

public class MyHostMethodService implements HostMethodService {
@Override
public void invokeAsync(String methodName, @Nullable JSONObject param,
@NonNull HostMethodCallback callback) {
switch (methodName) {
case "getUserInfo":
JSONObject result = new JSONObject();
result.put("name", "张三");
callback.onResult("SUCCESS", "", result);
break;
default:
callback.onResult("NOT_FOUND", "方法不存在", null);
break;
}
}
}
Section titled “NavigationService - 接管 openPage(URL 路由)”

宿主实现此接口即可接管 openPage:拿到 URL + 配置,自行决定如何打开(自有原生页 / 重定向 / 自有 WebView 等)。返回 true 表示已自行处理;返回 false 交回 SDK 默认容器打开(选择性拦截 + SDK 兜底)。

public class MyNavigationService implements NavigationService {
@Override
public boolean openPage(Context context, String url, PageConfig config) {
// 命中宿主路由 → 打开自己的原生页,返回 true 表示已接管
if (MyRouter.matchNative(url)) {
MyRouter.openNative(context, url);
return true;
}
// 其余交回 SDK 默认容器打开
return false;
}
}

若你想用 SDK 的 WebView 容器但自己控制展示(放进自己的 Activity/Fragment),在 openPage 里自取容器即可,无需走 SDK 默认页:

AbsSuperPageView v = SuperSDK.getPageController().createPageView();
v.load(url, null);
// v.setOnClosePageListener(...) 可响应 H5 的 closePage
// 把 v 放进你自己的容器展示,然后 return true

容器行为 · target="_blank" / window.open:H5 中 <a target="_blank"> 链接点击与 window.open(url) 会被容器自动识别,统一通过 openPage 在端上以新页打开(等价于 appbridge.openPage),并走上面的 NavigationService。H5 无需额外适配;与 iOS 行为一致。

提供广告加载和展示能力。

public class MyAdService implements AdService {
@Override
public void loadNativeAd(@NonNull Context context, @NonNull AdSource adSource,
@NonNull String adUnitId, @NonNull NativeAdLoadCallback callback) {
// 加载原生广告
}
@Override
public void loadRewardedAd(@NonNull Context context, @NonNull AdSource adSource,
@NonNull String adUnitId, @NonNull SuperCallback<IRewardAd> callback) {
// 加载激励视频广告
}
@Override
public void loadInterstitialAd(@NonNull Context context, @NonNull AdSource adSource,
@NonNull String adUnitId, @NonNull SuperCallback<IInterstitialAd> callback) {
// 加载插屏广告
}
@Override
public void loadRewardedInterstitialAd(@NonNull Context context, @NonNull AdSource adSource,
@NonNull String adUnitId,
@NonNull SuperCallback<IRewardedInterstitialAd> callback) {
// 加载激励插屏广告
}
}

广告类型:

AdType说明
NATIVE原生广告
REWARD激励视频广告
REWARD_INTERSTITIAL激励插屏广告
INTERSTITIAL插屏广告

广告来源:

AdSource说明
ADMOBGoogle AdMob
APPLOVINAppLovin

AdMob 由可选 artifact supersdk-ad-admob 提供完整的 Android 原生 AdService 实现。模块传递解析 Google Mobile Ads 25.4.0,核心 supersdk 不包含该依赖。

宿主接入清单(全部必读):

宿主必须做要求
引入 Provider添加 supersdk-ad-admob;不需要直接添加 play-services-ads
配置 App ID每个白牌产品在 <application> 声明自己的 com.google.android.gms.ads.APPLICATION_ID
配置五类广告位在 AdMob 后台创建 Rewarded、Interstitial、Banner、Native、Rewarded Interstitial Ad Unit ID,并在对应 load* 调用中传入
完成隐私流程宿主自行处理 GDPR/CCPA、儿童与青少年广告策略以及需要时的 UMP,同意完成后才初始化
初始化并注册AdMobAdService.initialize 成功后向 SuperSDKProvider 注册 AdService
管理 UI/生命周期Banner/Native 页面退出、替换或 stale callback 时 destroy();Rewarded Interstitial 展示前提供奖励说明和可跳过 intro
真机验收Debug 使用 Google 官方测试 ID;正式发版前验证五类广告加载、展示、失败、奖励和资源释放

模块已处理 Google Ads 依赖、网络权限、主线程调度、初始化幂等、错误映射、重复终态保护和 Native 默认模板;不会替宿主创建 AdMob 后台资源、保存业务 ID、执行隐私同意或决定页面生命周期。模块级完整说明见仓库 packages/android-sdk-ad-admob/README.md。

  1. 引入 Provider(minSdk ≥ 23、compileSdk ≥ 35):

    implementation 'com.supersdk:supersdk-ad-admob:版本号'
  2. 在 AndroidManifest.xml 的 <application> 内声明 AdMob 应用 ID(缺失会导致 App 启动崩溃):

    <meta-data
    android:name="com.google.android.gms.ads.APPLICATION_ID"
    android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy" />

    测试期可用 Google 官方测试 App ID:ca-app-pub-3940256099942544~3347511713

    多白牌项目建议用 manifest placeholder 按 flavor/build type 注入,避免不同产品或测试/生产环境串用:

    android {
    defaultConfig {
    manifestPlaceholders.ADMOB_APP_ID = 'ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy'
    }
    }

    Manifest 中对应使用 android:value="${ADMOB_APP_ID}"。App ID 必须进入 Manifest;五类 Ad Unit ID 可由 BuildConfig 或远程配置管理,但每次 load* 必须显式传入。

  3. 先完成宿主自己的 GDPR/CCPA/儿童与青少年广告等隐私同意流程,再初始化 Provider。只在回调成功后创建并注册服务;回调只使用 SuperSDK 的 SuperResult,不会向宿主泄漏 Google SDK 类型:

    AdMobAdService.initialize(getApplicationContext(), result -> {
    if (!result.isSuccess()) {
    Log.e("MyApp", "AdMob init failed: " + result.getCode()
    + " " + result.getMessage());
    return;
    }
    AdService adService = new AdMobAdService();
    // AdMob App ID 独立配置;这里使用控制台签发的 SuperSDK spid。
    SuperSDK.init(getApplicationContext(), "ab12cd34", new SuperSDKProvider() {
    @Override
    public AdService getAdService() {
    return adService;
    }
    });
    });

AdMobAdService.initialize 对并发、重复调用幂等;未初始化就调用任一 load* 会返回 NOT_INITIALIZED。Provider 不内置 App ID、广告位 ID,也不会自动处理隐私同意。

能力边界:

调用入口支持类型
Android 原生 AdServiceRewarded、Interstitial、Banner、Native、Rewarded Interstitial(五类)
Android H5 / JSBridgeRewarded、Rewarded Interstitial、Interstitial、Banner(四类)
  • Native 使用 Provider 内置的默认 NativeAdView 最低资产集,通过 INativeAd.showAtContainer(container) 挂载;宿主退出页面时必须调用 destroy()。
  • Rewarded Interstitial 在调用 show(...) 前,宿主必须先展示明确说明奖励内容且允许用户跳过的 intro screen。Provider 只响应宿主的显式 show,不会生成或替代这段业务 UI。

Google 后台(AdMob 控制台):

步骤说明
注册应用添加 App,获得 App ID(填入上面的 APPLICATION_ID)
创建广告单元为每种广告位创建 Ad Unit ID;Android H5 四类由 adUnitId 指定,Native 由宿主原生调用传入
测试广告开发期使用官方测试广告单元,避免无效流量导致封号

参考:AdMob Android 快速开始


HostNetworkExtensionService - 网络请求扩展(可选)

Section titled “HostNetworkExtensionService - 网络请求扩展(可选)”

允许宿主为 SDK 内部网络请求(settings / 事件上报 / 归因 / 短链等)追加 query 参数与 request header。接口全部为 default 方法,不实现即不生效,既有接入方无需改动。

public class MyProvider implements SuperSDKProvider {
@Override
public HostNetworkExtensionService getHostNetworkExtensionService() {
return new HostNetworkExtensionService() {
@Override
public Map<String, String> getExtraQueryParams(SuperRequestContext context) {
// 开启响应混淆:SDK 自身不会默认追加该参数
return Collections.singletonMap("suenc", "1");
}
@Override
public Map<String, String> getExtraHeaders(SuperRequestContext context) {
return Collections.singletonMap("X-Host-Trace", traceId());
}
};
}
}

SuperRequestContext 提供 getUrl() / getPath() / getHost(),宿主可据此按请求条件化返回。

合并规则(SDK 侧强制,宿主无法绕过):

  1. SDK 自有 query / header 恒胜——query 名精确匹配,header 名大小写不敏感匹配。例如 SDK 已设 X-Super-API-ENV,宿主返回 x-super-api-env 也不会覆盖它
  2. Authorization、Cookie 等认证凭据 header 不接受本接口注入
  3. 非法项(key/value 为空、key 含非法字符、header 名不合 token 规范)单项跳过,其余照常生效
  4. 实现抛出的异常一律被捕获,不阻断 SDK 主流程
  5. 每次请求实时读取,SDK 不缓存返回值——宿主可随时改变返回内容
  6. 两个方法在发起请求的线程上同步调用,实现必须快速返回、不得阻塞
  7. 下载类请求(离线包、downloadFile)与支付回执直投不注入 query,但仍适用 header 扩展

服务端在请求带 ?suenc=1 时会用自定义 Base64 表编码 JSON 响应体并回 X-Su-Enc: b64t1。SDK 内部请求会自动解码(含非 2xx 的错误响应),宿主无需介入——只要按上文注入 suenc=1 即可。

⚠️ 只用于降低通用关键字扫描的有效性,不是加密:不提供机密性、防篡改、防重放。

若宿主自己发起了 suenc 请求,可调用 SDK 的解码能力:

// 查询本端支持的 codec,决定是否发送 suenc=1
JSONObject capabilities = SuperSDK.getSuEncCapabilities(); // {"supported":true,"codecs":["b64t1"]}
// codec 必须直接使用响应 X-Su-Enc header 的原始值,不接受自定义编码表
try {
String json = SuperSDK.decodeSuEnc(encodedBody, codec);
} catch (SuEncException e) {
// UNSUPPORTED_SUENC_CODEC / SUENC_DECODE_FAILED / SUENC_INVALID_UTF8
String errorCode = e.getErrorCode();
}

注意事项:

  1. 解码失败一律视为请求失败,绝不回落读取明文——那正是该协议要防的降级
  2. 若请求带了 suenc=1 却收到无 X-Su-Enc 的响应,SDK 判定该线路失败并自动切换下一个 baseUrl
  3. 编码表在 SDK 发布后永不变更;新表一定使用新的 codec key,因此 codec 必须取自响应 header 而非硬编码

以下模块为可选,按宿主业务需要引入。每个模块的接入都包含三件事:① 引依赖 · ② 注册 Provider · ③ Google 后台配置。(广告 AdMob 接入见上文「AdService」章节。)

基于 Android Credential Manager + Sign in with Google,返回 Google 签发的 ID Token,交由业务后端校验后换取业务登录态。

① 依赖(minSdk ≥ 23):

implementation 'com.supersdk:supersdk-auth-google:版本号'

② 注册 Provider —— 在 LoginService 中挂载 GoogleAuthProvider:

private final LoginService loginService = new SDKLoginService.Builder()
.addProvider(new GoogleAuthProvider(SERVER_CLIENT_ID)) // provider 名 = "google"
.build();
@Override
public LoginService getLoginService() {
return loginService;
}
  • SERVER_CLIENT_ID 是 Web application 类型 的 OAuth Client ID(不是 Android 类型),同时也是返回 ID Token 的 aud。
  • 模块 Manifest 为空,无需额外权限(Credential Manager 内部处理),仅依赖公共 INTERNET 权限。

③ Google 后台(Google Cloud Console → APIs & Services → 凭据):

步骤说明
配置 OAuth 同意屏幕填应用信息与 Scope(至少 email / profile)
创建 Web OAuth Client ID即代码中的 SERVER_CLIENT_ID;业务后端校验 ID Token 时 aud 须等于它
创建 Android OAuth Client ID填 包名 + SHA-1 签名指纹(debug / release 各一份)。Credential Manager 取 Google 凭据要求该指纹已注册,否则回调 ERROR / 取不到账号

获取 SHA-1:./gradlew signingReport(或 keytool -list -v -keystore <keystore>)。

参考:Sign in with Google (Credential Manager) · 后端校验 ID Token

Google Pay 应用内购买(supersdk-pay-google)

Section titled “Google Pay 应用内购买(supersdk-pay-google)”

基于 Google Play Billing Library。H5 通过 SuperSDK.pay.* 查询当前账号可购买的订阅 offer 并发起购买,原生侧由 GooglePayProvider(provider 名 "google_play")对接 Google Play。

① 依赖(minSdk ≥ 23):

implementation 'com.supersdk:supersdk-pay-google:1.0.0.63-SNAPSHOT'

② 注册 Provider —— 在 PayService 中挂载 GooglePayProvider:

private final PayService payService = new SDKPayService.Builder()
.addProvider(new GooglePayProvider(context)) // provider 名 = "google_play"
.build();
@Override
public PayService getPayService() {
return payService;
}
  • com.android.vending.BILLING 权限已由模块 Manifest 声明并自动合并,宿主无需手动添加。
  • Android 公共查询 DTO 为 SubscriptionOfferInfo / SubscriptionPricingPhase;PayProvider 与 PayService 的新查询方法都有默认 NOT_SUPPORTED 实现,旧宿主/provider 兼容。

③ 查询当前账号 eligible offers 并购买:

const offers = await SuperSDK.pay.querySubscriptionOffers({
provider: 'google_play',
productIds: ['matrixvplan01']
});
const selected = offers[0];
const result = await SuperSDK.pay.purchase({
provider: 'google_play',
payParams: {
productId: selected.productId,
productType: 'subs',
basePlanId: selected.basePlanId,
offerId: selected.offerId, // 普通 base plan 为 null
accountId: accountHash,
profileId: orderTokenUuid
}
});
  • 查询结果只含 Play Billing 为当前账号返回的 SubscriptionOfferDetails,不拼接 Play Console 完整目录,也不经 Settings 过滤。
  • 查询响应不会包含 offerToken。购买时 Provider 会重新查询 ProductDetails,精确匹配 productId + basePlanId + nullable offerId,只在 Native 内把最新 token 交给 setOfferToken()。
  • 资格变化返回 OFFER_UNAVAILABLE,不会静默切换到其他价格。其他订阅错误:PRODUCT_NOT_FOUND、BILLING_UNAVAILABLE、QUERY_FAILED、USER_CANCELLED、PURCHASE_FAILED。
  • 旧 INAPP 与 queryProducts() 的公开行为不变;业务创单、权益映射、服务端验单/acknowledge/RTDN 不在本模块范围。

④ Google 后台(Google Play Console):

步骤说明
上传应用须以相同包名 + 签名上传到至少 internal testing 轨道(Billing 仅对 Play 安装的签名包生效,本地 debug 直装无法测)
创建商品Monetize → Products → 应用内商品 / 订阅,product ID 必须与 H5 传入的一致
配置测试账号添加 License testers,用沙盒账号测试购买,不产生真实扣费
服务端发货(推荐)配置 Real-time developer notifications (RTDN) + Cloud Pub/Sub + Play Developer API,以服务端为真相源做幂等发货 —— 完整架构见 JSSDK 文档《IAP 推荐架构》

参考:Play Billing 概览 · 创建商品 · 配置 RTDN · 测试购买


SDK 使用 SuperCallback<T> + SuperResult<T> 模式进行异步回调。

SuperSDK.someAsyncMethod(new SuperCallback<SomeData>() {
@Override
public void onResult(@NonNull SuperResult<SomeData> result) {
if (result.isSuccess()) {
SomeData data = result.getData();
// 处理成功
} else {
String code = result.getCode();
String message = result.getMessage();
// 处理失败
}
}
});

SuperResult 错误码:

Code说明
SUCCESS成功
FAIL_UNKNOWN未知错误
FAIL_TIMEOUT超时

// 1. Application 初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SuperSDK.init(this, "ab12cd34", new MySDKProvider());
}
}
// 2. 在 Activity 中使用
public class WebActivity extends AppCompatActivity {
private AbsSuperPageView pageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web);
FrameLayout container = findViewById(R.id.web_container);
// 创建 PageView
pageView = SuperSDK.getPageController().createPageView();
container.addView(pageView);
// 适配沉浸式安全区域
pageView.fitSystemWindows(this);
// 设置关闭页面监听(前端调用 closePage 时触发)
pageView.setOnClosePageListener(this::finish);
// 加载 URL
String url = "https://example.com";
pageView.load(url, new PageLoadConfig(url));
}
@Override
protected void onDestroy() {
super.onDestroy();
if (pageView != null) {
pageView.destroyView();
}
}
@Override
public void onBackPressed() {
// 优先级:前端拦截 > 页面历史回退 > 关闭 Activity
if (pageView != null) {
pageView.handleBackPressed(() -> super.onBackPressed());
} else {
super.onBackPressed();
}
}
}

如果项目开启了 ProGuard/R8 混淆,请添加以下规则:

-keep class com.supersdk.** { *; }
-keep class * implements com.supersdk.api.SuperSDKProvider { *; }

  1. 初始化时机:必须在使用任何 SDK 功能前调用 SuperSDK.init(),建议在 Application.onCreate() 中调用
  2. 生命周期:SDK 内部通过 onWindowVisibilityChanged 自动感知 Activity 生命周期,无需手动分发
  3. 返回键处理:使用 handleBackPressed(exitAction) 处理返回键,SDK 内部自动处理前端拦截和页面历史回退
  4. 销毁:Activity 销毁时必须调用 pageView.destroyView() 释放资源
  5. 线程安全:SDK 的 UI 操作已自动切换到主线程,宿主无需额外处理
  6. 离线包:SDK 内置离线包支持,加载 URL 时会自动匹配本地资源