SuperSDK Android 接入文档
This content is not available in your language yet.
版本:dev | 稳定性:开发中 — 内容随时变更,仅供内部参考
SuperSDK 是一个 Android WebView 容器 SDK,提供 WebView 容器、JSBridge、离线包、云控配置等能力,帮助宿主 App 快速集成 H5 容器。
| 项目 | 要求 |
|---|---|
| minSdkVersion | 23 (Android 6.0) |
| compileSdkVersion | 35 或更高(本仓使用 36) |
| Java 版本 | Java 11+ |
| 依赖库 | AndroidX |
Maven 依赖(推荐)
Section titled “Maven 依赖(推荐)”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/' }}
AAR 手动集成
Section titled “AAR 手动集成”- 将
supersdk-版本号.aar放入项目libs/目录 - 在
build.gradle中添加:
dependencies { implementation fileTree(dir: 'libs', include: ['*.aar'])}必要的 AndroidManifest 配置
Section titled “必要的 AndroidManifest 配置”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()); }}参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| context | Context | Application Context |
| spid | String | 控制台签发的 8 位小写字母/数字项目标识 |
| provider | SuperSDKProvider | 宿主实现的服务提供者 |
项目环境(API Environment)
Section titled “项目环境(API Environment)”项目环境对应控制台顶栏的 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
Section titled “实现 SuperSDKProvider”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; // 按需实现 }}核心 API
Section titled “核心 API”IPageController - 页面管理
Section titled “IPageController - 页面管理”通过 SuperSDK.getPageController() 获取。
IPageController pageController = SuperSDK.getPageController();
// 打开全屏页面pageController.open("https://example.com");
// 创建 PageView 嵌入到自己的布局中AbsSuperPageView pageView = pageController.createPageView();| 方法 | 说明 |
|---|---|
open(String url) | 在新的全屏页面中打开 URL |
createPageView() | 创建一个可嵌入的 PageView 实例 |
AbsSuperPageView - 页面视图
Section titled “AbsSuperPageView - 页面视图”AbsSuperPageView 继承自 FrameLayout,是 SDK 的核心容器视图。
AbsSuperPageView pageView = SuperSDK.getPageController().createPageView();
// 添加到布局container.addView(pageView);
// 加载 URLpageView.load("https://example.com", new PageLoadConfig("https://example.com"));
// 适配沉浸式安全区域(自动获取状态栏和导航栏高度)pageView.fitSystemWindows(this);
// 处理返回键(优先级:前端拦截 > 页面历史回退 > exitAction)@Overridepublic void onBackPressed() { pageView.handleBackPressed(() -> super.onBackPressed());}
// 销毁(必须调用)@Overrideprotected 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 与离线包拦截 |
IOfflineController - 离线包管理
Section titled “IOfflineController - 离线包管理”通过 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);OfflinePackageState
Section titled “OfflinePackageState”仅包含当前状态正常(可用)的离线包信息。
| 方法 | 返回类型 | 说明 |
|---|---|---|
getHost() | String | 离线包域名标识 |
getLocalVersion() | String | 本地版本号 |
getSource() | Source | 来源:ASSETS(内置)/ DOWNLOAD(云端下载)/ MANUAL(外部手动安装) |
OfflineUpdateResult
Section titled “OfflineUpdateResult”| 方法 | 返回类型 | 说明 |
|---|---|---|
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 | 未知错误 |
离线包远程更新
Section titled “离线包远程更新”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。
SFeedback - 用户反馈
Section titled “SFeedback - 用户反馈”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 字符 | |
| extra | JSON object,序列化后 ≤4KB |
回调恒在 UI 线程;所有字符串会被 trim,长度按字符数计(服务端为权威)。限流:每 IP 10 次/分钟、每项目 600 次/分钟。提交不是幂等的,SDK 不自动重试。
INativeBlockProvider - Native Block
Section titled “INativeBlockProvider - Native Block”Native Block 允许 H5 页面嵌入宿主 App 提供的原生视图(如广告、视频播放器等)。
// 1. 实现 INativeBlockProviderpublic 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@Overridepublic 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 被销毁回调 |
Service 协议说明
Section titled “Service 协议说明”HostConfigService - 宿主配置
Section titled “HostConfigService - 宿主配置”提供宿主 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()中接管并返回自有标识。
EventService - 事件上报
Section titled “EventService - 事件上报”接收 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); }}LoginService - 登录服务
Section titled “LoginService - 登录服务”当 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() | String | Token |
getRefreshToken() | String | Refresh Token |
getRefreshTokenExpiresAt() | long | Refresh Token 过期时间 |
isFirstLogin() | boolean | 是否首次登录 |
getExtra() | JSONObject | 额外扩展数据 |
HostMethodService - 宿主自定义方法
Section titled “HostMethodService - 宿主自定义方法”允许 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; } }}NavigationService - 接管 openPage(URL 路由)
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 行为一致。
AdService - 广告服务
Section titled “AdService - 广告服务”提供广告加载和展示能力。
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 | 说明 |
|---|---|
ADMOB | Google AdMob |
APPLOVIN | AppLovin |
AdMob 接入示例
Section titled “AdMob 接入示例”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。
-
引入 Provider(minSdk ≥ 23、compileSdk ≥ 35):
implementation 'com.supersdk:supersdk-ad-admob:版本号' -
在
AndroidManifest.xml的<application>内声明 AdMob 应用 ID(缺失会导致 App 启动崩溃):<meta-dataandroid: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*必须显式传入。 -
先完成宿主自己的 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() {@Overridepublic AdService getAdService() {return adService;}});});
AdMobAdService.initialize 对并发、重复调用幂等;未初始化就调用任一 load* 会返回 NOT_INITIALIZED。Provider 不内置 App ID、广告位 ID,也不会自动处理隐私同意。
能力边界:
| 调用入口 | 支持类型 |
|---|---|
Android 原生 AdService | Rewarded、Interstitial、Banner、Native、Rewarded Interstitial(五类) |
| Android H5 / JSBridge | Rewarded、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 由宿主原生调用传入 |
| 测试广告 | 开发期使用官方测试广告单元,避免无效流量导致封号 |
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 侧强制,宿主无法绕过):
- SDK 自有 query / header 恒胜——query 名精确匹配,header 名大小写不敏感匹配。例如 SDK 已设
X-Super-API-ENV,宿主返回x-super-api-env也不会覆盖它Authorization、Cookie等认证凭据 header 不接受本接口注入- 非法项(key/value 为空、key 含非法字符、header 名不合 token 规范)单项跳过,其余照常生效
- 实现抛出的异常一律被捕获,不阻断 SDK 主流程
- 每次请求实时读取,SDK 不缓存返回值——宿主可随时改变返回内容
- 两个方法在发起请求的线程上同步调用,实现必须快速返回、不得阻塞
- 下载类请求(离线包、
downloadFile)与支付回执直投不注入 query,但仍适用 header 扩展
响应混淆(suenc)
Section titled “响应混淆(suenc)”服务端在请求带 ?suenc=1 时会用自定义 Base64 表编码 JSON 响应体并回 X-Su-Enc: b64t1。SDK 内部请求会自动解码(含非 2xx 的错误响应),宿主无需介入——只要按上文注入 suenc=1 即可。
⚠️ 只用于降低通用关键字扫描的有效性,不是加密:不提供机密性、防篡改、防重放。
若宿主自己发起了 suenc 请求,可调用 SDK 的解码能力:
// 查询本端支持的 codec,决定是否发送 suenc=1JSONObject 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();}注意事项:
- 解码失败一律视为请求失败,绝不回落读取明文——那正是该协议要防的降级
- 若请求带了
suenc=1却收到无X-Su-Enc的响应,SDK 判定该线路失败并自动切换下一个 baseUrl- 编码表在 SDK 发布后永不变更;新表一定使用新的 codec key,因此
codec必须取自响应 header 而非硬编码
可选能力模块接入
Section titled “可选能力模块接入”以下模块为可选,按宿主业务需要引入。每个模块的接入都包含三件事:① 引依赖 · ② 注册 Provider · ③ Google 后台配置。(广告 AdMob 接入见上文「AdService」章节。)
Google 登录(supersdk-auth-google)
Section titled “Google 登录(supersdk-auth-google)”基于 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();
@Overridepublic 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>)。
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();
@Overridepublic 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 · 测试购买
通用回调模式
Section titled “通用回调模式”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 | 超时 |
完整接入示例
Section titled “完整接入示例”// 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 { *; }- 初始化时机:必须在使用任何 SDK 功能前调用
SuperSDK.init(),建议在Application.onCreate()中调用 - 生命周期:SDK 内部通过
onWindowVisibilityChanged自动感知 Activity 生命周期,无需手动分发 - 返回键处理:使用
handleBackPressed(exitAction)处理返回键,SDK 内部自动处理前端拦截和页面历史回退 - 销毁:Activity 销毁时必须调用
pageView.destroyView()释放资源 - 线程安全:SDK 的 UI 操作已自动切换到主线程,宿主无需额外处理
- 离线包:SDK 内置离线包支持,加载 URL 时会自动匹配本地资源