跳到主要内容

Android SDK 快速集成

此部分内容建议参考 Demo 项目源码 阅读

1. 获取凭据​

集成 SDK 需要先在 Host 平台 中创建应用,获得每个应用专属的 APP KEY, APP SECRET 及 Service Token 后。 启动 SDK 时,需要通过 APP KEY,APP SECRET 初始化。接入方也可联系 Unity 以创建 App Service Token / App Key / App Secret。

如下是测试用 App Service Token / App Key / App Secret 示例

  • App Service Token
  • App Key: AKIDt1vMSYVFf1twI53iDecobx6QsTK0
  • App Secret: ufzN1iD8PsnMW0ViDCMhpJFBiLR1E1Xf

Service Token 的主要作用是调用以下 API 获取游戏列表,并可从中获取 Game ID 以启动游戏。

使用令牌获取小游戏列表

实际生产环境中,Service Token 应该在服务端使用,从服务端调用上面的 API 。Demo 项目中,Service Token 是在客户端使用,这个仅仅是一个实例,不建议在生产环境中这样使用。

2. 导入 SDK​

  • SDK 支持 Android API Level 21+
  • 将 host-toolkit-release.aar 放入 Android 项目的 libs 目录下,并在 Gradle 中添加依赖
dependencies {
implementation files('host-runtime-release.aar')
}
  • 加入 Host SDK 需要的其他依赖支持库如下
// build.gradle
dependencies {
// dependencies for host SDK
implementation libs.okhttp
implementation libs.mmkv
implementation libs.gson
implementation libs.java.websocket

implementation libs.camera.core
implementation libs.camera.camera2
implementation libs.camera.lifecycle
implementation libs.camera.video
implementation libs.camera.view
implementation libs.camera.extensions

implementation libs.tbssdk
}

对应的依赖版本如下

# libs.versions.toml
[versions]
okhttp = "3.12.13"
mmkv = "1.3.2"
gson = "2.10.1"
javaWebsocket = "1.5.1"
cameraCamera2 = "1.2.3"
tbssdk = "44286"

[libraries]
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "javaWebsocket" }
camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCamera2" }
camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCamera2" }
camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCamera2" }
camera-video = { module = "androidx.camera:camera-video", version.ref = "cameraCamera2" }
camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCamera2" }
camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "cameraCamera2" }
tbssdk = { module = "com.tencent.tbs:tbssdk", version.ref = "tbssdk" }

3. 多进程模式启动游戏​

多进程模式下,游戏将运行在独立的游戏进程,可同时运行的游戏进程数由平台决定。此部分内容建议参考 Demo 项目源码 main 分支阅读

3.1 初始化多进程启动器​

在 AndroidManifest 中声明游戏可使用的宿主 Activity。这些 Activity 都继承自模板 MultiProcHostContainerActivityTemplate,模板中的关键代码将在后续步骤中贴出。 如果需要能同时保持多个游戏进程,则扩充声明的 Activity 即可。

<activity android:name=".host.MultiProcHostContainerActivity1"
android:launchMode="singleTask"
android:process=":hostProc1"
android:taskAffinity="com.u3d.appwithhostsdkdemo.host.affinity1">
</activity>
<activity android:name=".host.MultiProcHostContainerActivity2"
android:launchMode="singleTask"
android:process=":hostProc2"
android:taskAffinity="com.u3d.appwithhostsdkdemo.host.affinity2">
</activity>

根据上述声明,调用 MultiProcessLauncher.configContainer 接口,将注册的 Activity 及进程名信息注入多进程启动器。

private void initMultiProcessLauncher() {
Map<String, Class<?>> map = new HashMap<>();
map.put(":hostProc1", MultiProcHostContainerActivity1.class);
map.put(":hostProc2", MultiProcHostContainerActivity2.class);
MultiProcessLauncher.configContainer(map);
}

3.2 动态加载 so 库​

host-runtime-release.aar 仅包含基础接口功能,小游戏运行环境需要从 CDN 加载。可使用如下 API 加载并缓存在本地,后续调用此 API 会直接进入 onSuccess 回调。 此接口为预加载接口,可在合适的时机调用此接口以预加载 so 库,但请确保加载成功后再进行后续逻辑。

TJHost.prepare(this, new OnLoadSharedLibraryListener() {
@Override
public void onSuccess(String libPath) {
// 此处保存 libPath,后续将使用到此路径
// 成功之后再进行后续逻辑
}
// ...
});

3.3 通过多进程启动器启动游戏 Activity​

当用户点击某启动游戏的按钮后,通过多进程启动器启动对应 Game ID 的游戏,此处可将动态加载 so 库成功后的 libPath 通过 Intent 额外参数的方式传入,也可通过 AIDL 等其他方式将其传入游戏进程。 多进程启动器会根据传入的 Game ID 判断,使用哪个空闲的 Activity,或复用启动时间最久的 Activity 来启动游戏。如果传入的 Game ID 对应的游戏已启动且仍在后台存活,其会将其从后台拉起到前台。

// 这里 launchKey 值为 Game ID。可在游戏列表中获取
MultiProcessLauncher.launch(context, launchKey, intent -> {
intent.putExtra("v8LibPath", MainActivity.v8LibPath);
intent.putExtra("isTempSession", false);
intent.putExtra("title", title);
// 可按需传入其他自定义字段
});

3.4 添加游戏 Activity 生命周期监听​

从此步骤开始,所有涉及到的代码均在游戏 Activity 中,Demo 中对应 MultiProcHostContainerActivityTemplate。 生成一个多进程启动器对应的生命周期 Observer,此 Observer 将监测此游戏 Activity 的生命周期函数,并将游戏 Activity 的创建 / 销毁事件通知多进程启动器,使其更新对应 Activity 状态。 此 Observer 建议在 onCreate 中添加并在 onDestroy 中移除。

// 这里 launchKey 值为 Game ID。可在游戏列表中获取
private void initLifeCycleObserver() {
launchKey = getIntent().getStringExtra("launchKey");
Log.i("MultiProcess", "launchKey: " + launchKey);
gameLifeCycle = new MultiProcessLauncher.MiniGameLifeCycle(this, launchKey);
getLifecycle().addObserver(gameLifeCycle);
}

3.5 设置 WebView 数据目录​

Android 10 后 WebView 在多进程中的使用做了限制,每个进程只能访问自己的网络数据,因此需要为每个进程制定数据存储目录。下述 WebViewCompat 是一个工具类,具体代码可参考 Demo 内实现。

WebViewCompat.setDataDirectorySuffix(this);

3.6 游戏进程加载 so 库​

将步骤 3.2 中从 CDN 加载的 so 库载入到游戏进程。

private void initDynamicLib() {
String nativeLibraryPath = getIntent().getStringExtra("v8LibPath");
// result 表示是否载入成功
boolean result = TJHost.installNativeLibraryPath(getApplicationContext(), nativeLibraryPath);

if (result) {
initTJHostHandle();
}
}

3.7 初始化 TJHostHandle​

初始化 TJHostHandle,初始化成功后可通过 TJHostHandle.getInstance() 获取到初始化成功的 TJHostHandle。后续游戏控制相关 API 需要实例调用。

private void initTJHostHandle() {
Context context = this;
PropertiesManager.init(context);
PropertiesManager p = PropertiesManager.getInstance();
TJHostHandle.initialize(context, p.getProperty("app.key"), p.getProperty("app.secret"), gameContainer, new OnTJHostHandleInitializeListener() {
@Override
public void onSuccess(TJHostHandle hostHandle) {
// 初始化成功后,创建 GameHandle,参考步骤3.8
handler.post(() -> initGame());
}
// ...
});
}

3.8 创建 GameHandle​

使用前一步骤中初始化的 HostHandle 创建并初始化 GameHandle,GameHandle 作用于整个小游戏生命周期。此处可设置 HTTP 缓存目录等初始化参数,全部参数列表参考 API Reference。

// 初始化 GameHandle
private void initGame() {
// 此处的 TJHostHandle 单例在前一步骤中初始化
hostHandle = TJHostHandle.getInstance();

// 设置 WebView 数据目录后缀,各进程必须使用不同的数据目录
WebViewCompat.setDataDirectorySuffix(this);

// 设置 HTTP 缓存目录等初始化参数
Bundle hostBundle = new Bundle();
hostBundle.putString(TJConstants.HTTP_CACHE_PATH, getDefaultCachePath());
hostBundle.putLong(TJConstants.HOST_PAUSE_DELAY_MILLIS, 100);

// 设置 Game ID 为 launchKey
launchKey = getIntent().getStringExtra("gameId");

// 创建 GameHandle
hostHandle.createGameHandle(this, hostBundle, new OnTJHostCreateHandleListener() {
@Override
public void onSuccess(TJHostHandle result) {
setGameStartOptions(launchKey);
}
// ...
});
}

3.9 设置启动参数​

传入 Game ID 供 SDK 获取游戏包下载 URL 及游戏类型,其余可自定义参数列表参考 API Reference,成功后可加载游戏资源。

// launchKey 为 Game ID
private void setGameStartOptions(String launchKey) {
// GameStartOptions can be referred in the API Reference manual
Bundle bundle = new Bundle();
bundle.putString(TJRuntimeConstants.LAUNCH_KEY, launchKey);

hostHandle.setGameStartOptions(bundle, new OnTJHostHandleListener() {
@Override
public void onSuccess() {
startGame();
}
// ...
});
}

3.10 加载游戏资源​

成功后可将游戏界面添加至某视图容器中,请求焦点,并启动游戏。

private void startGame() {
setCustomCommandListeners(); // Used for custom perf data upload and custom ad service

hostHandle.start("[Demo App]", new OnTJHostHandleListener() {
@Override
public void onSuccess() {
isGameStarted = true;
Log.v(TAG, "Host start game success");
gameView = hostHandle.getGameView();
if (gameContainer.indexOfChild(gameView) == -1) {
gameContainer.addView(gameView);
}

gameView.requestFocus();
resumeGame();
}
// ...
});
}

3.11 启动游戏​

private void resumeGame() {
if (hostHandle == null) return;
if (!isGameStarted) return;

// Resume game
hostHandle.play(new OnTJHostHandleListener() {
@Override
public void onSuccess() {
isGamePlayed = true;
addEnterOptions();
}
// ...
});
}

4. 单进程模式启动游戏​

单进程模式启动游戏相比多进程更简单,但如果游戏因为某些原因崩溃,会导致主应用一起崩溃,因此不建议使用此类实现。此部分内容建议参考 Demo 项目源码 static-aar-single 分支阅读

4.1 声明宿主 Activity​

单进程模式下,无需初始化和使用多进程启动器,可直接在 AndroidManifest 中声明游戏使用的宿主 Activity,并通过 startActivity 直接启动游戏,启动游戏 Activity 之前需要传入 Game ID 用于后续设置游戏启动参数。

<activity android:name=".host.HostContainerActivity" android:launchMode="singleTask"></activity>
// 这里 launchKey 值为 Game ID。可在游戏列表中获取
Intent intent = new Intent(context, HostContainerActivity.class);
intent.putExtra("gameId", launchKey);
intent.putExtra("isTempSession", false);
intent.putExtra("title", title);
startActivity(intent);

4.2 初始化 TJHostHandle​

初始化 TJHostHandle,初始化成功后可通过 TJHostHandle.getInstance() 获取到初始化成功的 TJHostHandle。后续游戏控制相关 API 需要实例调用。

private void initTJHostHandle() {
Context context = this;
PropertiesManager.init(context);
PropertiesManager p = PropertiesManager.getInstance();
TJHostHandle.initialize(context, p.getProperty("app.key"), p.getProperty("app.secret"), gameContainer, new OnTJHostHandleInitializeListener() {
@Override
public void onSuccess(TJHostHandle hostHandle) {
// 初始化成功后,创建 GameHandle,参考步骤4.3
handler.post(() -> initGame());
}
// ...
});
}

4.3 创建 GameHandle​

使用前一步骤中初始化的 HostHandle 创建并初始化 GameHandle,GameHandle 作用于整个小游戏生命周期。此处可设置 HTTP 缓存目录等初始化参数,全部参数列表参考 API Reference。

// 初始化 GameHandle
private void initGame() {
// 此处的 TJHostHandle 单例在前一步骤中初始化
hostHandle = TJHostHandle.getInstance();

// 设置 WebView 数据目录后缀,各进程必须使用不同的数据目录
WebViewCompat.setDataDirectorySuffix(this);

// 设置 HTTP 缓存目录等初始化参数
Bundle hostBundle = new Bundle();
hostBundle.putString(TJConstants.HTTP_CACHE_PATH, getDefaultCachePath());
hostBundle.putLong(TJConstants.HOST_PAUSE_DELAY_MILLIS, 100);

// 设置 Game ID 为 launchKey
launchKey = getIntent().getStringExtra("gameId");

// 创建 GameHandle
hostHandle.createGameHandle(this, hostBundle, new OnTJHostCreateHandleListener() {
@Override
public void onSuccess(TJHostHandle result) {
setGameStartOptions(launchKey);
}
// ...
});
}

4.4 设置启动参数​

传入 Game ID 供 SDK 获取游戏包下载 URL 及游戏类型,其余可自定义参数列表参考 API Reference,成功后可加载游戏资源。

// launchKey 为 Game ID
private void setGameStartOptions(String launchKey) {
// GameStartOptions can be referred in the API Reference manual
Bundle bundle = new Bundle();
bundle.putString(TJRuntimeConstants.LAUNCH_KEY, launchKey);

hostHandle.setGameStartOptions(bundle, new OnTJHostHandleListener() {
@Override
public void onSuccess() {
startGame();
}
// ...
});
}

4.5 加载游戏资源​

成功后可将游戏界面添加至某视图容器中,请求焦点,并启动游戏。

private void startGame() {
setCustomCommandListeners(); // Used for custom perf data upload and custom ad service

hostHandle.start("[Demo App]", new OnTJHostHandleListener() {
@Override
public void onSuccess() {
isGameStarted = true;
Log.v(TAG, "Host start game success");
gameView = hostHandle.getGameView();
if (gameContainer.indexOfChild(gameView) == -1) {
gameContainer.addView(gameView);
}

gameView.requestFocus();
resumeGame();
}
// ...
});
}

4.6 启动游戏​

private void resumeGame() {
if (hostHandle == null) return;
if (!isGameStarted) return;

// Resume game
hostHandle.play(new OnTJHostHandleListener() {
@Override
public void onSuccess() {
isGamePlayed = true;
addEnterOptions();
}
// ...
});
}