Android Integration Documentation
Integration Instructions
- Android SDK Demo Download
- (must support Android 11, otherwise it will affect the channel listing)
- It is recommended to use Unity to export Android Studio project for packaging
- For first-time integration, ensure full-screen adaptation for Android phones (common adaptation methods usually involve displaying the game in full-screen mode and ensuring that the top notch or camera does not interfere with the game’s UI functions, or the rating may be affected during cloud testing)
- Ensure that all SDK interface calls are made on the
- When calling Java interfaces in Unity, make sure to switch to the UI thread before calling the SDK interface
- Please review the documentation carefull. If you have any questions about the integration, communicate directly with the technical team in the integration group
- This document will be updated periodically, and any interface changes will be communicated in the group. For more updates, please refer to the FastSdk update log
Maven Repository Configuration
project:build.gradle
buildscript {
repositories {
maven {
url 'https://sdk.wdyxgames.com/nexus/repository/sdk-public/'
}
}
}
allprojects {
repositories {
maven {
url 'https://sdk.wdyxgames.com/nexus/repository/sdk-public/'
}
}
}
app:build.gradle
dependencies {
implementation 'com.hoolai.access.open:hoolai-core:1.0.5.2'
}
app: AndroidManifest.xml Special Handling
Note:
Since the packaging tool dynamically replaces the package name in the APK, please follow the rules below to modify and replace the configuration in AndroidManifest.xml during integration. Otherwise, issues such as the inability to install the channel package onto the same device or abnormal crashes may occur.
()
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.hoolai.access.channel.impl">
<!-- 示例 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- application中有以下三个新增配置,必加(游戏无自定义 Application 时android:name需要配置 HLApplication)-->
<application
android:name="com.hoolai.access.open.fastaccess.HLApplication"
android:allowBackup="false"
android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true">
<meta-data
android:name="android.max_aspect"
android:value="2.5"
android:allowBackup="false"
android:requestLegacyExternalStorage="true"/>
<activity
android:name="游戏的主Activity"
android:configChanges="screenLayout|orientation|keyboardHidden|keyboard|fontScale|layoutDirection|density|smallestScreenSize|screenSize|uiMode|navigation|touchscreen|locale|mnc|mcc"
android:launchMode="standard">
...
<!-- copy以下内容到游戏的启动类中,必接 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="hlscheme" android:host="hlhost"/>
</intent-filter>
<!-- 懂球帝分享配置(选接) -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!--game可自定义,与请求参数保持一致即可 -->
<data android:scheme="game" />
</intent-filter>
</activity>
<!-- MAIN_ACTIVITY标签的value=游戏的入口类Activity全路径,否则应用宝渠道登录失败 -->
<!-- 该标签必须加 -->
<meta-data
android:name="MAIN_ACTIVITY"
android:value="游戏的主Activity,如com.demo.GameActivity"/>
<!-- 注意!!!! ${applicationId}需要替换成hlApplicationId -->
<!-- 以下为示例 无需copy -->
<!-- TODO 原配置 -->
<provider
android:name="xx.FileProvider"
android:authorities="${applicationId}.Fileprovider"
... />
<!-- TODO 修改后 -->
<provider
android:name="xx.FileProvider"
android:authorities="hlApplicationId.Fileprovider"
... />
<!-- 以上为示例 无需copy -->
</application>
</manifest>
Note:
If third-party SDKs are integrated, the configuration containing ${applicationId} in AndroidManifest will automatically be replaced with the current APK's package name. This will prevent the package name from being replaced during channel package creation. Developers should perform a self-check after the package is generated.
Self-check process:
Open the APK package in Android Studio and locate the AndroidManifest.xml file. Find the package tag, which corresponds to the APK's package name.
Search for this package name in the file and copy all configurations (except those with package="package_name") to the project's AndroidManifest file. Replace all occurrences with hlApplicationId and repackage.
Ensure that the game's Activity launch mode is set to android:launchMode="standard".
Interface Documentation
Application Integration ()Note: This configuration must be declared in AndroidManifest.xml.
- Method 1: If the game does not use a custom Application, use HLApplication.
- Method 2: If the game uses a custom Application that extends HLApplication.class, no additional configuration is needed.
- Method 3: If the game uses a custom Application that does not extend HLApplication.class, add the following call:
- After completing one of the above configurations, make sure the following line is present in AndroidManifest.xml: <application android:name="xxxx.xxxx.XXXApplication"
public class YourApplication extends YourBaseApplication {
@Override
public void onCreate() {
super.onCreate();
FastSdk.onApplicationCreate(this);
}
@Override public void onTerminate() {
super.onTerminate();
FastSdk.onTerminate(this);
}
@Override public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
FastSdk.onConfigurationChanged(this, newConfig);
}
@Override protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
FastSdk.attachBaseContext(this, context);
}
}
SDK Feature Integration
Initialization ()
Note:
- During SDK initialization, to prevent a black screen in the game, it is recommended to prepare an image named unity_static_splash and place it in the res/drawable directory. This interface must be called first when the game starts. can hot updates, version checks, game initialization, login, and other features be used.
- If no initialization callback is received or if onInitFailed is received, the game must not proceed with any further operations.
- In case of initialization failure, it is recommended to guide the user to re-initialize or restart the game.
// Inside the onCreate lifecycle method of the game’s main entry class (not the splash screen class
// SDK initialization callback
FastSdk.hlSystemListener = new HLSystemListener() {
@Override
public void onInitSuccess(InitResult initResult) {
// Initialization succeeded
// InitResult parameters: gameId, channelId, channel
// gameId: The product ID, needed for login verification
// channelId: ID for different distribution channels under the product
// channel: Channel identifier, such as xiaomi, huawei, etc.
}
@Override
public void onInitFailed(String reason) {
// It is recommended to show a dialog prompt here,
// and provide options to retry or exit the game
}
@Override
public void onCustomExit() {
// Custom game exit interface
// Implement your own exit logic here
// Otherwise, the app might fail channel review
// The following is a demo example — do NOT directly copy it into production
new AlertDialog.Builder(GameActivity.this)
.setTitle("Game Exit Dialog")
.setMessage("This is the game's custom exit interface.")
.setNegativeButton("Cancel", null)
.setPositiveButton("Confirm", (dialog, which) -> onExitSuccess(s))
.setCancelable(false).show();
}
@Override
public void onExitSuccess(String result) {
// Recommended exit process for maximum compatibility with platforms like Oppo, Vivo, etc.
// Not following this may cause black screens when relaunching the game
moveTaskToBack(true);
finish();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
}
@Override
public void onUpdate(String data) {
// Version update handling (reserved for future implementation)
}
};
// SDK account callback
FastSdk.hlAccountListener = new HLAccountListener() {
@Override
public void onRefreshUser(LoginResult result) {
// Sub-account switch In this callback, only verify the user's legitimacy in the game
// If your game does not support switching user info internally, you need to log out first, then log back in (no need to reinitialize)
}
@Override
public void onLoginSuccess(LoginResult result) {
// Login successful
}
@Override
public void onLoginFailed(String reason) {
// Login failed
}
@Override
public void onLogout(Object... var1) {
// Logout
}
};
// SDK payment callback
FastSdk.hlPaymentListener = new HLPaymentListener() {
@Override
public void onPaySuccess(String result) {
// Payment successful, this only indicates a successful callback.
// Actual item delivery should be based on server-side notification.
// Note: result may be an empty string.
}
@Override
public void onPayFailed(String reason) {
// Payment failed. Note: 'reason' may be an empty string.
}
@Override
public void onQuerySuccess(List<GoodsInfo> list) {
// Used for overseas games to fetch the product list for display.
}
};
// SDK share callback (note: some third-party shares may not receive a callback, such as WeChat sharing)
FastSdk.hlShareListener = new HLShareListener() {
@Override
public void onShareSuccess() {
// Share successful
}
@Override
public void onShareFailed(String reason) {
// Share failed
}
};
// Note: Before calling onCreate, you must instantiate all the interfaces used above first
FastSdk.onCreate(this);
InitResult
Parameter Name | Type | Description |
---|---|---|
gameId | int | Product ID |
channel | String | Channel Name |
channelId | int | Channel ID |
LoginResult
Parameter Name | Type | Description |
---|---|---|
uid | long | User ID |
accessToken | String | Token |
nickName | String | Nickname |
channelUid | String | Channel User ID |
channel | String | Channel Name |
serverArea | String | Extension Parameter |
GoodsInfo
Parameter Name | Type | Description |
---|---|---|
itemId | String | Product ID |
itemName | String | Product Name |
itemCount | String | Product Quantity |
itemPrice | String | Product Price |
showTag | String | Ignored in domestic |
currency | String | Currency Type |
Login ()
Note:
- Ensure that the "game_init_result" event has been reported before calling this method. For more details, please refer to Game Custom Report
- Do not call the login interface if the initialization callback has not been received or if initialization fails.
FastSdk.login();
Data Reporting ()
Note (Basic data reporting must be done before payment)
- There are four types of event reports: character creation, entering the server, level up, and custom
- When reporting character creation, entering the server, and level up events, the parameters roleId, roleName, serverId, and serverName must not be empty and must be provided; otherwise, it will affect the payment functionality! For other required data, please refer to the documentation.
//eventType values:
//Character Creation: EventType.CreateRole
//Entering Server: EventType.EnterServer
//Level Up: EventType.LevelUp
//The first three must be reported, the following is for custom event reporting based on operational needs
//Custom Event: EventType.CustomerEvent
//After setting the custom point, you must set setExtendAction("xx"), setPhylum("xx"), etc.
PlayerInfo playerInfo = new PlayerInfo();
playerInfo.setRoleId("32424"); // Role unique identifier, mandatory
playerInfo.setRoleName("Nickname"); // Mandatory, String
playerInfo.setRoleLevel("6"); // Mandatory, note that the string must be a number, e.g., "123"
playerInfo.setZoneId("1"); // Mandatory, if not available, default to "1", ensure the value in the string is numeric
playerInfo.setZoneName("Eastern China Zone"); // Mandatory, if not available, default to something like "Zone 1"
playerInfo.setServerId("1"); // Mandatory, if not available, default to "1", ensure the value in the string is numeric
playerInfo.setServerName("Zone Name"); // Mandatory, if not available, default to something like "Zone 1"
playerInfo.setBalance("66"); // Mandatory, if not available, use ""
playerInfo.setVip("5"); // Mandatory
playerInfo.setPartyname("Party Name"); // Optional
// New content: extra must carry data, refer to the special data reporting for client-side data reporting
// Extended information, format: key:value, key:value
playerInfo.setExtra("a:arm,b:bom,gameResourceUrl:xxx,gameLoginServerUrl:xxx");
playerInfo.setClassField(""); // Event result, can be left blank
// playerInfo.setPhylum(""); // When sending extended data, provide the event number as instructed, e.g., 1
// playerInfo.setExtendAction("Point name"); // For basic reporting, do not pass this; for custom reporting, pass it
FastSdk.report(EventType.EnterServer, playerInfo);
Note:
- Those who need to integrate CLS network detection functionality, please take note! Click to view detailed rules.
- This functionality should be reported as soon as possible after SDK initialization. It can be reported separately or along with other event points.
- The detection address can be a domain name or IP.
- Replace the colon in the detection address with @ to avoid formatting errors.
- The detection address key must use the following three fields: gameResourceUrl, gameLoginServerUrl, gameServerUrl.
For example: playerInfo.setExtra("key:value,gameResourceUrl:http@xxx/open/init, gameServerUrl:tcp@111.222.333.6@8888");
EventType
Parameter Name | Description | Required |
---|---|---|
EventType.CreateRole | Create Role | Yes |
EventType.EnterServer | Game Enter Server | Yes |
EventType.LevelUp | Role Level Up | Yes |
EventType.CustomerEvent | Custom Event | Yes |
PlayerInfo
Parameter Name | Type | Description | Required |
---|---|---|---|
roleId | String | Role ID | Yes |
roleName | String | Role Name | Yes |
roleLevel | String | Level | Yes |
zoneId | String | Zone ID (if no zone, use serverId) | Yes |
zoneName | String | Zone Name (if no zone, use serverName) | Yes |
serverId | String | Server ID | Yes |
serverName | String | Server Name | Yes |
balance | String | Balance | Yes |
vip | String | VIP Level | Yes |
partyName | String | Guild Name | No |
appVersion | String | App Version | Yes |
appResVersion | String | Game Resource Version | Yes |
extendAction | String | Event Name | Yes |
roleCreateTime | String | Role Creation Time | Yes |
phylum | String | Event ID (refer to "Client Data Reporting" documentation) | Yes |
classField | String | Event Result (ok/fail) | No |
extra | String | Extended Information (used for cls reporting) | No |
Logout
FastSdk.logout();
Payment (), must be called after reporting upon entering the game or creating a character; otherwise, payment cannot be initiated.
PayParams payParams = new PayParams();
payParams.setItemId("item60"); // Product ID, not mandatory
payParams.setItemName("60 Diamonds"); // Product Name, not mandatory
payParams.setAmount(100); // Product Amount, mandatory, unit: cents
payParams.setNotifyUrl(""); // Payment callback URL
// Extra information, returned as-is in the callback, since each channel may have different callback parameter restrictions.
// The callback parameters currently support a length of 255. Avoid using these symbols: "|", "=", "+", "/".
payParams.setCallbackInfo("Payment extra information, game transmission parameters");
payParams.setCurrency("CNY"); // Optional for domestic, or set to "CNY"
FastSdk.pay(payParams);
Note: Debouncing suggestions
- The SDK already implements debouncing, triggering the first payment only within 3 seconds.
- It is recommended that the client display a mask layer when the purchase event is triggered to prevent players from clicking repeatedly. After receiving the payment callback, close the mask. If no callback is received, set a timeout, and automatically close the mask after the timeout.
- Caching payment data and checking after the payment is completed is unnecessary. If the player quickly clicks different payment tiers, the actual payment may be the first one, but the second one could be cached, causing the payment to be successful but the check to fail. The success dialog should rely on the SDK callback. ::: PayParams
Parameter Name | Type | Description | Required |
---|---|---|---|
itemId | String | Product ID | Yes |
itemName | String | Product Name | Yes |
amount | int | Amount, unit: cents | Yes |
count | int | Quantity, default is 1 | Yes |
callbackInfo | String | Pass-through field | No |
notifyUrl | String | Payment callback URL | No |
currency | String | Currency type, default: CNY | Yes |
opt | Map<String, String> | Extension parameters | No |
Exit Program
FastSdk.exit();
Retrieve Product List Information
//Note: This method may not need to be integrated for domestic games.
FastSdk.queryGoodsInfo();
Lifecycle Methods ()
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
FastSdk.onSaveInstanceState(outState);
}
@Override
protected void onStart() {
super.onStart();
FastSdk.onStart(this);
}
@Override
protected void onResume() {
super.onResume();
FastSdk.onResume(this);
}
@Override
protected void onStop() {
super.onStop();
FastSdk.onStop(this);
}
@Override
protected void onPause() {
super.onPause();
FastSdk.onPause(this);
}
@Override
protected void onRestart() {
super.onRestart();
FastSdk.onRestart(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
FastSdk.onDestroy(this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
FastSdk.onNewIntent(intent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
FastSdk.onActivityResult(this, requestCode, resultCode, data);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); FastSdk.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); FastSdk.onConfigurationChanged(this, newConfig, getResources());
}
@Override
public void onBackPressed() {
FastSdk.exit();
}
@Override
public Resources getResources() {
return FastSdk.getResources(super.getResources());
}
// The following are updates
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
FastSdk.onRestoreInstanceState(savedInstanceState);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
FastSdk.onWindowFocusChanged(hasFocus);
}
Share
ShareParams params = new ShareParams();
params.setTitle("Title");
params.setContent("Content");
params.setPicPath("picPath"); // Local or network image link
// Share link (Dongqiudi fixed as: dongqiudi://share/circle/)
params.setShareUrl("http://xx.com");
// WeChat image params.setBmp(bmp);
FastSdk.share(ShareType.WX, ShareType.ChildType.WX_CHAT, params);
ShareType
Parameter Name | Type | Description |
---|---|---|
ShareType | QQ share | |
WX | ShareType | WeChat share |
DQD | ShareType | Dongqiudi share |
ShareType | Facebook share | |
SYSTEM | ShareType | System share |
ShareType.ChildType
Parameter Name | Type | Description |
---|---|---|
WX_CHAT | ShareType.ChildType | WeChat chat |
WX_CIRCLE | ShareType.ChildType | WeChat Moments |
WX_FAVORITES | ShareType.ChildType | WeChat Favorites |
QQ_TEXT_AND_QQZONE | ShareType.ChildType | Share text and sync to QQ Zone |
QQ_IMG_AND_QQZONE | ShareType.ChildType | Share image and sync to QQ Zone |
QQ_TEXT | ShareType.ChildType | Share text |
QQ_IMG | ShareType.ChildType | Share image |
DQD | ShareType.ChildType | Dongqiudi |
FACEBOOK_LINK | ShareType.ChildType | Facebook link |
FACEBOOK_IMG | ShareType.ChildType | Facebook image |
FACEBOOK_VIDEO | ShareType.ChildType | Facebook video |
SYSTEM_TEXT | ShareType.ChildType | System share text link |
SYSTEM_IMG | ShareType.ChildType | System share image |
SYSTEM_FILE | ShareType.ChildType | System share file |
ShareParams
Parameter Name | Type | Description | Required |
---|---|---|---|
id | String | Dongqiudi ID, only for Dongqiudi | Yes |
wxType | int | 1: Chat, 2: Moments, 3: Favorites | No |
qqType | int | 1: Default, 2: Pure Image | No |
showQQZone | boolean | Whether to display on QQ Zone | No |
title | String | Title | No |
content | String | Content | No |
picPath | String | Image URL | No |
shareUrl | String | Share URL | No |
callbackUrl | String | Dongqiudi callback URL, only for Dongqiudi | Yes |
bmp | Bitmap | WeChat image, only for WeChat image sharing | Yes |
Permission request ()
Note:
As per policy requirements, after a sensitive permission request is denied, it cannot be requested multiple times. Therefore, the SDK limits the number of requests to one. When users request permission, they can choose whether to show a prompt (default is false). If set to true and the request is made again, a prompt will appear guiding the user to manually grant permission. This prompt is usually shown when denying permissions would affect the functionality of the business.
// Single dangerous permission request, returns the application result
boolean hasPermission = FastSdk.checkPermission(Activity activity, String permission, boolean showRefuseDialog);
// activity: Current page context
// permission: Permission name
// showRefuseDialog: Whether to show the dialog when the permission is denied, default is true, optional
boolean hasPermission = FastSdk.checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, true);
if (hasPermission) {
// your code
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
FastSdk.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Permission request result callback, check if granted
if (requestCode == Math.abs(Manifest.permission.WRITE_EXTERNAL_STORAGE.hashCode())) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Logger.i("Permission granted");
// your code
} else {
Logger.i("Permission denied");
}
}
}
Fangzhou Reporting (optional; note that calling this interface will directly upload data to Fangzhou without going through the SDK)
String action = "game_xxx";
Map<String, Object> hashMap = new HashMap<>();
hashMap.put("xxx", 123);
hashMap.put("ooo", "321");
FastSdk.gameDataReport(action, hashMap);
Fangzhou Parameters
Parameter Name | Type | Description | Required |
---|---|---|---|
action | String | Event Name | Yes |
hashMap | Map<String, Object> | Event Content | Yes |
CD-KEY (optional, gift code redemption)
AccessActivityDataInfo info = new AccessActivityDataInfo();
info.setCode("兑换码"); // Redemption code
FastSdk.accessParticipate(AccessActivityType.CD_KEY, info);
CD-KEY Parameters
Parameter Name | Type | Description | Required |
---|---|---|---|
type | AccessActivityType | Activity type | Yes |
info | AccessActivityDataInfo | Redemption code object | Yes |
Q&A
What do I do if initialization fails and displays a "non-legitimate app" message?
The signature file is incorrect. If there is no signature file, you can contact the operations team.
What if I have no account for logging in?
The account/password login method does not have a registration feature. You will need the operations team to provide an account/password.
How do I test payment features in a sandbox environment?
Confirm with the operations team to ensure that the sandbox environment is enabled, and that the account has been added to the testing whitelist.
What if I'm unable to initiate payment?
Check if the character creation or server entry event has been reported when entering the game.
What do I do if the payment was successful, but doesn't go through?
Check if the provided payment callback URL is correct.
Check the payment interface to see if the setNotifyUrl(url) has been called in the payment parameters. If it has, this URL will be prioritized for callbacks.
What do I do if the floating button is not hidden when logging out from the game in the channel package?
The game has not called FastSdk.logout(). It is recommended to create two buttons in the game’s “Settings” interface: “Switch Server” and “Switch Account”. When “Switch Account” is clicked, call FastSdk.logout(). When “Switch Server” is clicked, no logout is needed; just return to the server selection screen.