Skip to main content

Quick Integration of Android SDK

It is recommended to refer to the source code of the Demo project for reading

1 Obtain Credentials​

Integrating the SDK requires first creating an application in the Host Platform, obtaining the APP KEY, APP SECRET, and Service Token specific to each application.

When starting the SDK, it needs to be initialized with APP KEY and APP SECRET.

The access party can also contact Unity to create App Service Token / App Key / App Secret.

The following is an example of test App Service Token / App Key / App Secret

  • App Service Token

  • App Key: AKIDt1vMSYVFf1twI53iDecobx6QsTK0

  • App Secret: ufzN1iD8PsnMW0ViDCMhpJFBiLR1E1Xf

Service Token is mainly used to call the following API to obtain the game list, from which Game ID can be retrieved to start the game.

Use a token to obtain a list of minigames ​

The platform can use the server token to obtain the list of minigames of the host application.

Example of request:

GET /api/game/list?appId={appId} HTTP/1.1
Host: minihost.tuanjie.cn
Authorization: Bearer {service_token}

Request Parameter Description:

ParameterDescription
appIdHost Application ID
AuthorizationServer token, formatted as Bearer {service_token}

Response Example:

HTTP/1.1 200 OK
Content-Type: application/json

[
{
"id": "67b*********************",
"appId": "67b*********************",
"name": "name",
"briefIntro": "briefIntro",
"gameType": "weixinminigame",
"iconUrl": "https://minihost.cdn.tuanjie.cn/game_app/67b6eb506ffcda2559ecd19d/app_icon/********.png",
"launchKey": "67b*********************",
}
]

Response field description:

FieldDescription
idGame ID
appIdHost Application ID
nameGame Name
briefIntroGame Introduction
gameTypeGame Type
iconUrlGame Icon URL
launchKeyThe return value here is the game ID

In a real production environment, Service Token should be used on the server side, and the above API should be called from the server. In the Demo project, Service Token is used on the Client side, which is just an example and not recommended for use in a production environment.

2 Import SDK​

  • SDK supports Android API Level 21+

  • Place host-toolkit-release.aar into the libs directory of the Android project, and add dependencies in Gradle

dependencies {
implementation files('host-runtime-release.aar')
}
  • Other dependent support libraries required to join the Host SDK are as follows
// 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
}

The corresponding dependency version is as follows

## 1.2 libs.versions.toml
[versions]
okhttp = "3.12.13"
mmkv = "1.3.2"
gson = "2.10.1"
javaWebsocket = "1.5.1"
cameraCamera2 = "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 Start the game in multi-process mode​

Under the multi-process mode, the game will run in an independent game process, and the number of game processes that can run simultaneously is determined by the platform. It is recommended to refer to the main branch of the Demo project source code for reading

3.1 Initialize the multi-process launcher​

Declare the host Activities that the game can use in AndroidManifest. These Activities all inherit from the template MultiProcHostContainerActivityTemplate, and the key code in the template will be posted in the subsequent steps.

If you need to maintain multiple game processes simultaneously, simply expand the declared 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>

According to the above statement, call the MultiProcessLauncher.configContainer interface to inject the registered Activity and process name information into the multi-process launcher.

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

3.2 Dynamically Load so Library​

host-runtime-release.aar only contains basic interface functionality, and the minigame runtime environment needs to be loaded from the CDN. You can use the following API to load and cache it locally, and subsequent calls to this API will directly enter the onSuccess callback.

This interface is a preloading interface, which can be called at an appropriate time to preload the so library, but please ensure that the loading is successful before proceeding with subsequent logic.

TJHost.prepare(this, new OnLoadSharedLibraryListener() {
@Override
public void onSuccess(String libPath) {
// save libPath here for later usage
// run other logic only if it succecced
}
// ...
});

3.3 Launch the game Activity via the multi-process launcher​

When a user clicks a button to start a game, the game with the corresponding Game ID is launched via a multi-process launcher. Here, the libPath after successfully dynamically loading the so library can be passed in via an Intent extra parameter, or it can be passed into the game process via other means such as AIDL.

The multi-process launcher will determine, based on the incoming Game ID, which idle Activity to use or reuse the Activity that has been launched the longest to start the game. If the game corresponding to the incoming Game ID has already been launched and is still alive in the background, it will bring it from the background to the foreground.

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

3.4 Add game Activity lifecycle monitoring​

Starting from this step, all the code involved is in the game Activity, which corresponds to MultiProcHostContainerActivityTemplate in the Demo.

Generate a lifecycle Observer corresponding to the multi-process launcher. This Observer will monitor the lifecycle functions of this game Activity and notify the multi-process launcher of the creation/destruction events of the game Activity, enabling it to update the corresponding Activity status.

This Observer is recommended to be added in onCreate and removed in onDestroy.

// launchKey here equals to Game ID, which can be obtained from game list
private void initLifeCycleObserver() {
launchKey = getIntent().getStringExtra("launchKey");
Log.i("MultiProcess", "launchKey: " + launchKey);
gameLifeCycle = new MultiProcessLauncher.MiniGameLifeCycle(this, launchKey);
getLifecycle().addObserver(gameLifeCycle);
}

3.5 Set WebView data directory​

After Android 10, the use of WebView in multi-process has been restricted, with each process only able to access its own network data. Therefore, it is necessary to specify a data storage directory for each process. The following WebViewCompat is a utility class, and the specific code can refer to the implementation in the Demo.

WebViewCompat.setDataDirectorySuffix(this);

3.6 Game process loads the so library​

Load the so library loaded from the CDN in step 3.2 into the game process.

private void initDynamicLib() {
String nativeLibraryPath = getIntent().getStringExtra("v8LibPath");
// result indicates whether loading process succeeded or not
boolean result = TJHost.installNativeLibraryPath(getApplicationContext(), nativeLibraryPath);

if (result) {
initTJHostHandle();
}
}

3.7 Initialize TJHostHandle​

InitializeTJHostHandle, and after successful initialization, the initialized TJHostHandle.getInstance() can be obtained to get the successfully initialized TJHostHandle. Subsequent game control-related APIs require instance calls.

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) {
// create GameHandle after initialization succeeded,refer to section 1.8
handler.post(() -> initGame());
}
// ...
});
}

3.8 Create GameHandle​

Create and initialize a GameHandle using the HostHandle initialized in the previous step. The GameHandle is effective throughout the lifecycle of the minigame. Initialization parameters such as the HTTP cache directory can be set here. Refer to the API Reference for the full list of parameters.

// create GameHandle
private void initGame() {
// TJHostHandle singleton is initialized in previous step
hostHandle = TJHostHandle.getInstance();

// configure WebView data directory subfix. Different processes must use different data directories.
WebViewCompat.setDataDirectorySuffix(this);

// configure host options like HTTP cache directories, etc.
Bundle hostBundle = new Bundle();
hostBundle.putString(TJConstants.HTTP_CACHE_PATH, getDefaultCachePath());
hostBundle.putLong(TJConstants.HOST_PAUSE_DELAY_MILLIS, 100);

// use launchKey as Game ID
launchKey = getIntent().getStringExtra("gameId");

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

3.9 Set startup parameters​

Pass in Game ID for the SDK to obtain the game package download URL and game type. For the list of other customizable parameters, refer to the API Reference. After success, game resources can be loaded.

// launchKey is Game ID
private void setGameStartOptions(String launchKey) {
// GameStartOptions can be referred in Section 2.8.2.3
Bundle bundle = new Bundle();
bundle.putString(TJRuntimeConstants.LAUNCH_KEY, launchKey);

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

3.10 Load Game Resources​

After success, the game interface can be added to a view container, request focus, and start the game.

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 Start the Game​

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

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

4 Start the game in single-process mode​

Starting the game in single-process mode is simpler than in multi-process mode, but if the game crashes for some reason, it will cause the main application to crash as well. Therefore, this type of implementation is not recommended. For this part of the content, it is recommended to refer to the source code of the Demo project, the static-aar-single branch for reading

4.1 Declare the Host Activity​

In single-process mode, there is no need to initialize and use a multi-process launcher. You can directly declare the host Activity used by the game in the AndroidManifest and start the game directly via startActivity. Before starting the game Activity, you need to pass in the Game ID for subsequent setting of game startup parameters.

<activity android:name=".host.HostContainerActivity" android:launchMode="singleTask"></activity>
// launchKey is 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 Initialize TJHostHandle​

InitializeTJHostHandle, and after successful initialization, the initialized TJHostHandle.getInstance() can be obtained to get the successfully initialized TJHostHandle. Subsequent game control-related APIs require instance calls.

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) {
// create GameHandle after initialization succeeded,refer to section 2.3
handler.post(() -> initGame());
}
// ...
});
}

4.3 Create GameHandle​

Create and initialize a GameHandle using the HostHandle initialized in the previous step. The GameHandle is effective throughout the lifecycle of the minigame. Initialization parameters such as the HTTP cache directory can be set here. Refer to the API Reference for the full list of parameters.

// similiar to section 1.9
private void initGame() {
// TJHostHandle singleton is initialized in previous step
hostHandle = TJHostHandle.getInstance();

// configure WebView data directory subfix. Different processes must use different data directories.
WebViewCompat.setDataDirectorySuffix(this);

// configure host options like HTTP cache directories, etc.
Bundle hostBundle = new Bundle();
hostBundle.putString(TJConstants.HTTP_CACHE_PATH, getDefaultCachePath());
hostBundle.putLong(TJConstants.HOST_PAUSE_DELAY_MILLIS, 100);

// use launchKey as Game ID
launchKey = getIntent().getStringExtra("gameId");

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

4.4 Set startup parameters​

Pass in Game ID for the SDK to obtain the game package download URL and game type. For the list of other customizable parameters, refer to the API Reference. After success, game resources can be loaded.

// launchKey is Game ID
private void setGameStartOptions(String launchKey) {
// GameStartOptions can be referred in Section 2.8.2.3
Bundle bundle = new Bundle();
bundle.putString(TJRuntimeConstants.LAUNCH_KEY, launchKey);

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

4.5 Load Game Resources​

After success, the game interface can be added to a view container, request focus, and start the game.

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 Start the Game​

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

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