接入准备

工程必须使用 Android 最新 SDK 编译

APK包必须签名,提交Google Play商店以后,签名不可更改

APK包必须 zipalign 优化

必须保证测试手机的Google框架完整

minSdkVersion 19 (最低支持Android 4.4)

SDK支持Androix 检查AndroidX配置项

平台将提供以下信息,用于Android App工程的配置

							
Package Name        Android应用标识
Display Name        应用显示名
AppID               SDK 应用ID
AppKey              SDK 应用Key
Fuid                渠道标识
Facebook ID         Facebook 应用ID
							
						
以上参数可以在SDK zip包中的 SDK更新说明.txt 文件中找到.

SDK 包文件结构

(ANDROID)com.txwy.passport.sdk(游戏名)版本.zip/
├── SDK更新说明(Bundle identifier).txt
└── xdsdk-XXXX_XXXXXX-XXXXXXX.aar

						

OBB 分包

注意

提交 Google Play 的APK 必须小于 100 MB, 大于 100MB 的App,需要进行OBB分包 .

建议APK不大于 97 MB

Google 官方文档 http://developer.android.com/google/play/expansion-files.html 需要翻墙访问

OBB文件名格式


[main|patch].<expansion-version>.<package-name>.obb

main               OBB正式包
patch              OBB补丁包
expansion-version  android:versionCode
package-name       Your application's Java-style package name.

实例:<shared-storage>/Android/obb/com.example.app/main.314159.com.example.app.obb

<shared-storage>     可以通过系统函数 getExternalStorageDirectory 获取正确的存放位置


						

不用实现OBB文件的下载

玩家在Google Play商店安装游戏时,会自动安装APK和OBB,提示安装成功时,OBB已经被正确的安装.

读取OBB文件中的资源


// The shared path to all app expansion files
private final static String EXP_PATH = "/Android/obb/";

static String[] getAPKExpansionFiles(Context ctx, int mainVersion,
      int patchVersion) {
    String packageName = ctx.getPackageName();
    Vector<String> ret = new Vector<String>();
    if (Environment.getExternalStorageState()
          .equals(Environment.MEDIA_MOUNTED)) {
        // Build the full path to the app's expansion files
        File root = Environment.getExternalStorageDirectory();
        File expPath = new File(root.toString() + EXP_PATH + packageName);

        // Check that expansion file path exists
        if (expPath.exists()) {
            if ( mainVersion > 0 ) {
                String strMainPath = expPath + File.separator + "main." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strMainPath);
                if ( main.isFile() ) {
                        ret.add(strMainPath);
                }
            }
            if ( patchVersion > 0 ) {
                String strPatchPath = expPath + File.separator + "patch." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strPatchPath);
                if ( main.isFile() ) {
                        ret.add(strPatchPath);
                }
            }
        }
    }
    String[] retArray = new String[ret.size()];
    ret.toArray(retArray);
    return retArray;
}


// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile =
    APKExpansionSupport.getAPKExpansionZipFile(appContext,
        mainVersion, patchVersion);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

						

项目需支持AndroidX,检查配置

检查gradle.properties中是否缺少以下配置(⚠️缺少会发生错误):
		
android.useAndroidX=true
android.enableJetifier=true
		
	

引用依赖项

注意:⚠️需要替换XXXX_XXXXXX-XXXXXXX

  1. 以下依赖项正确应用到工程

    依赖库
    xdsdk-XXXX_XXXXXX-XXXXXXX.aar 平台SDK
  2. 向您的moudle级 build.gradle 文件添加规则,以纳入插件:

    
        
        dependencies {
            // ...
            
            implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])
        	
            /*facebook登录*/
        	implementation ('com.facebook.android:facebook-login:12.3.0'){
        	    exclude group: 'com.google.zxing'
        	}
        	/*facebook分享*/
        	implementation ('com.facebook.android:facebook-share:12.3.0'){
                exclude group: 'com.google.zxing'
        	}
    
    	/* Google */
        	implementation 'com.google.android.gms:play-services-auth:18.1.0'
    	/* firebase推送 */
        	implementation 'com.google.firebase:firebase-core:17.0.0'
        	implementation 'com.google.firebase:firebase-messaging:20.2.1'
    
        	//AndroidX
        	implementation 'androidx.legacy:legacy-support-v4:1.0.0'
            implementation 'androidx.appcompat:appcompat:1.2.0'
    
        	//Google Request in-app reviews 游戏内评价Android5.0+
        	implementation 'com.google.android.play:core:1.8.0'
    
    	
        	
    
        	//appsflyer
        	implementation 'com.appsflyer:af-android-sdk:6.2.3'
        	implementation 'com.android.installreferrer:installreferrer:2.2'
    
        	/* Twitter --start--  注意:如果没用到twitter,则不用加该配置  */
        	implementation ('com.twitter.sdk.android:twitter-core:3.1.1'){
                exclude group: 'com.google.code.gson'
        	}
        	implementation ('com.twitter.sdk.android:tweet-composer:3.1.1'){
                exclude group: 'com.google.code.gson'
        	}
        	/* Twitter --end--  注意:如果没用到twitter,则不用加该配置  */
    
        	/* Google play billing库 */
        	def billing_version = "3.0.2"
        	api "com.android.billingclient:billing:$billing_version"
    
        }
    
    
    		

SDK API使用说明

在代码中引用SDK

		
import com.txwy.passport.sdk.SDKTxwyPassport;
		
	

验证obb文件大小

		
this 为 Activity 类指针.
size obb文件的字节数(Bytes) 可以使用Linux wc命令获得字节数:   wc -c xxx.obb   
VerifyObbFileListener 验证结果监听

SDKTxwyPassport.verifyObbFileSize(this, long size ,  new SDKTxwyPassport.VerifyObbFileListener() {

        @Override
        public void onVerifyFinish(boolean success, String msg ) {
            if(!success){

            	//todo XDObbFileErrorActivity是SDK中的默认错误页面,根据需求确定是否使用
                //Intent intent = new Intent(PermissionsActivityLauncher.this, XDObbFileErrorActivity.class);
                //intent.putExtra("erroMgs" , msg);
                //PermissionsActivityLauncher.this.startActivity(intent);

                LogUtil.d(TAG ,"obb文件有问题!" + msg);
            }else{
                LogUtil.d(TAG , msg);
            }
        }
});

注意: 如果size<=0,默认读取xml配置的txwy_obbfile_size大小,如果都没有则验证失败。
		
	

初始化 SDK

		
请确保在 Launcher Activity OnCreate 函数中初始化SDK

protected void onCreate(Bundle savedInstanceState) {

	String appID  = "SDK APPID";
	String appKey = "SDK APPKEY";
	String fuid   = "渠道标识";
	String language   = "语言字符";

	SDKTxwyPassport.setAppInfo(this, appid, appkey, fuid , language);
}

this 为 Activity 类指针.

所需要的参数,可以在 SDK更新说明.txt 文件中找到.
		
	

设置SDK 语言

		
this 为 Activity 类指针.

SDKTxwyPassport.setLanguage(this, "语言字符");
		
	
tw 繁体 cn 简体 en 英语 de 德语
jp 日语 kr 韩语 th 泰国 vi 越南
tr 土耳其语 es 西班牙语 ru 俄语 fr 法语
pt 葡萄牙语 id 印尼语

登录通行证

		
this 为 Activity 类指针.

SDKTxwyPassport.signIn(this, new SDKTxwyPassport.SignInDelegete() {

	@Override
	public void txwyDidPassport() {
		// 登录完成

		SDKTxwyPassportInfo passport = SDKTxwyPassport.getPassportInfo(this);
		// passport.uid 用户ID
		// passport.sid 用户登录凭证
		// passport.isGuest = true 则为游客身份登录
		// passport.isBindPhoneNum = true 则为用户已绑定手机

		if (passport == null) {
			// 帐号已登出
			return;
		}

		// 帐号已成功登录通行证
		// 将 passport.sid 传递给服务器,服务器通过通行证接口验证sid,以确保登录账号合法。
	}
});
		
	

SDKTxwyPassportInfo 结构

			
public class SDKTxwyPassportInfo  {

	int uid;              	通行证ID,玩家唯一数字标识
	String sid;          	通行证登录凭证
	String fuid;          	用户来源渠道标识
	boolean isGuest;      	标识是否游客身份登录
	boolean isBindPhoneNum; 标识用户是否已经绑定手机

}
			
		

切换账号

		

this 为 Activity 类指针.

// 首先登出当前账号,无任何事件回调
SDKTxwyPassport.signOut(this);

// 紧接着调登入接口
SDKTxwyPassport.signIn(this, new SDKTxwyPassport.SignInDelegete() {

	@Override
	public void txwyDidPassport() {
		// 登录完成
		SDKTxwyPassportInfo passport = SDKTxwyPassport.getPassportInfo(this);
	}
});
		
	

游戏切换账号流程

* 游戏内提供登出按钮

* 点击登出按钮,游戏退出到选择服务器界面

跟踪玩家数据

请在玩家登录游戏服务器或者角色升级以后调SDK跟踪接口


this 为 Activity 类指针.

SDKTxwyPassport.trackAccount(this, "服务器ID", 角色等级);

						

服务器ID需要与平台协商,平台的客服中心,平台充值等都需要使用相同的服务器ID。

客服中心

建议在游戏内设置界面和登录界面放置客服中心按钮,详情请跟运营确认。


this 为 Activity 类指针.

SDKTxwyPassport.bugReport(this, "服务器ID", "角色昵称", "游戏客户端版本号");

若未登录游戏服务器,服务器ID 请传入 "1"
若游戏角色未登录,角色昵称 请传入 "未登入"

						

用户中心


this 为 Activity 类指针.

SDKTxwyPassport.userCenter(this, "服务器ID", "角色昵称", "游戏客户端版本号");


						

系统回调

下列系统回调必须传递到SDK接口,否则SDK无法正常工作

onActivityResult

		
this 为 Activity 类指针.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	// Pass on the activity result to the helper for handling
	if (!SDKTxwyPassport.handleActivityResult(this, requestCode, resultCode, data)) {
		// not handled, so handle it ourselves (here's where you'd
		// perform any handling of activity results not related to in-app
		// billing...
		super.onActivityResult(requestCode, resultCode, data);
	}
}
		
	

OnRequestPermissionsResultCallback

		
this 为 Activity 类指针.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions
	, @NonNull int[] grantResults) {
        SDKTxwyPassport.onRequestPermissionsResult(this , requestCode , permissions , grantResults);
    }

需要注意,实现OnRequestPermissionsResultCallback接口的activity必须和传递给SDKTxwyPassport.setAppInfo(activity, appid, appkey, fuid)方法中的activity是同一个。

评分接口(应用内评价)

请在满足恰当的条件:例如抽到第一个5星卡牌时调用

this          Activity 类指针.
SDKTxwyPassport.storeReviewInApp(this);


						

充值接入说明

充值界面由游戏开发方提供.

充值项的设定,找运营索取

支付产品的价格需要通过SDK提供的接口查询.

查询充值项价格

请在游戏启动过程中,后台异步查询价格.

避免在显示商店时同步查询价格,这会导致打开商店界面有明显的延迟.


this 为 Activity 类指针.

List products = new ArrayList();

products.add("txwy_001");
products.add("txwy_002");

SDKTxwyPassport.queryWithProducts(this, products, new SDKTxwyPassport.ProductQueringListener() {
	@Override
	public void onQuery(List<SkuDetails> skus) {
		//查询失败
		if (null == skus)
			return;
		for (int i = 1; i <= skus.toArray().length; i++){
			SkuDetails sku = (SkuDetails) skus.toArray()[i-1];
			if (sku!=null){
				//sku.getSku()		     	-- 产品ID
				//sku.getAmount()	     	-- 价格
				//sku.getCur()		     	-- 货币
				//sku.getPrice()    		-- 显示价格
			}
		}
	}
});


						

注意

某些情况下 queryWithProducts 会失败,返回空的产品列表,请显示默认的产品价格,默认价格可以找运营索取 文件中找到

充值


this          Activity 类指针.
产品ID         充值产品ID,由平台配置,可以找运营索取
服务器ID       服务器ID
Mark 透传参数  充值透传参数,充值平台会原样传回到游戏服务器,详见支付服务器对接
level         角色等级,或者其它可以标识游戏进度的等级

SDKTxwyPassport.payWithProductID(this, "产品ID", "服务器ID", "Mark 透传参数", 角色等级, new SDKTxwyPassport.PaymentListener() {
	@Override
	public void onPayment(SkuDetails skuDetail) {
		// 平台充值成功,游戏服务器可能还没有收到平台的充值请求。
	}

	@Override
	public void onCancel() {
		//出错或用户取消
	}
});


						

注意

收到 onPayment 回调不代表充值已到账,以游戏服务端收到充值平台回调为准.

兑换


<第一步> 在游戏页面的onCreate方法中实现查询兑换商品的接口(注意:在初始化SDK之后)

查询到兑换的商品,返回商品id和库存清单,然后调用OOAPPurchase接口。 下面有参考代码 SDKTxwyPassport.OOAPQuery(this , new SDKTxwyPassport.QueryListener() { @Override public void onPurcharing(String productId, Inventory inventory) { //获得兑换ID,和库存清单 } });

<第二步> 查询到商品后,生成订单并消费掉兑换的商品

查询到兑换商品后,在查询的回调中调用该函数和服务器同步,并消费掉兑换的商品 注意:调用该接口之前,游戏方动态获得serverId和mark。并询问玩家是否兑换至当前角色。 productId 兑换产品ID,在onPurcharing回调中获得 serverId 游戏方传入 服务器ID mark 游戏方传入 Mark 充值透传参数,充值平台会原样传回到游戏服务器,详见支付服务器对接 inventory google 查询到的库存清单,在onPurcharing回调中获得 SDKTxwyPassport.OOAPPurchase(this, "productId", "serverId", "mark", inventory, new SDKTxwyPassport.PaymentListener() { @Override public void onPayment(SkuDetails skuDetail) { // 兑换成功 } @Override public void onCancel() { //出错或用户取消 } });

参考代码:

public class MainActivity extends Activity { static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LogUtil.d(TAG , "BillingManager:处理兑换商品"); dealWithOOAP(); } @Override protected void onResume() { super.onResume(); } public void dealWithOOAP(){ LogUtil.d(TAG , "BillingManager 查询兑换商品~"); SDKTxwyPassport.OOAPQuery(MainActivity.this , new SDKTxwyPassport.QueryListener() { @Override public void onPurcharing(String productId, Inventory inventory) { LogUtil.d(TAG , "获得的奖品ID: "+ productId); LogUtil.d(TAG , "获得的奖品 inventory: "+ inventory.toString()); if(TextUtils.isEmpty(productId) || inventory == null){ LogUtil.d(TAG , "未查询到兑换的商品"); return; } //todo 游戏方动态生成下面两个参数 //todo 游戏方动态生成下面两个参数 String serverId = "游戏方提供""; String mark = "游戏方提供""; //todo 在调用SDKTxwyPassport.OOAPPurchase接口之前,询问玩家是否兑换至当前角色。 ... ... ... //游戏方动态生成serverId、mark参数并询问是否兑换至当前角色后调用该接口。 //游戏方动态生成serverId、mark参数并询问是否兑换至当前角色后调用该接口。 //游戏方动态生成serverId、mark参数并询问是否兑换至当前角色后调用该接口。 SDKTxwyPassport.OOAPPurchase(MainActivity.this , productId , serverId , mark , inventory , new SDKTxwyPassport.PaymentListener() { @Override public void onPayment(SkuDetails skuDetail) { LogUtil.d(TAG , "兑换成功!"); } @Override public void onCancel() { LogUtil.d(TAG , "取消兑换!"); } }); } }); } }

注意

serverIdmark 参数需要游戏方传入。

注意

收到 onPayment 回调不代表充值已到账,以游戏服务端收到充值平台回调为准.

服务器对接

登录验证


游戏服务器端访问下面的连接

http://平台服务器/passport/auth?sid=1011e33ca7988e3804cc5e0da6298e27

sid       客户端登录成功以后通过 getPassportInfo 接口获取,并传递到游戏服务器。

请求成功返回
{"error":"","uid":12345678,"uname":"用户名"}

请求失败返回
{"error":"错误信息"}

请求失败返回 (需要补单)
{"error":"pay_black_list_not_login","uid":12345678"}

注意:⚠️验证失败后,需要做切换帐号的操作。

平台服务器 域名请与营运联系获得.

充值平台服务器回调

需要游戏开发方提供游戏服务器充值的接口.


充值平台向游戏服务器充值接口发起 GET 请求

http://游戏服务器域名/充值接口

GET 参数

productid    产品标识,作为充值依据,字符串类型
uid          玩家的唯一标识,数字类型,只要可以唯一标识一个玩家即可,要于登录接口一致
order        本次充值在平台上的订单号,字符串类型,充值请求的唯一性标识
sign         充值票据
server_id    游戏服务器ID,字符串类型
mark         充值透传参数(可选)
pay_way      支付方式. appstore iOS充值 | googleplay Google商店充值 | txwy 第三方充值

						

sign 充值票据验证算法



算法生成的哈希值(小写)
md5(uid + "_" + order + "_" + productid + "_" + mark + "_" + AppKey)


						

游戏服务器充值接口返回值协议


调用返回用纯文本的数字错误编号,数字错误编号如下(不允许有任何换行和html代码1        充值成功(重复订单号储值也返回1)
2        充值的服务器不存在,请确认游戏服域名正确并已被添加到后台
3        充值游戏币有误
5        md5错误,请确认密钥正确,充值票据算法跟文档描述一致,参与票据计算的参数于传递给接口的参数一致
7        不存在此账号,请确认用户名和登录接口传递的是一致的
0        充值失败,订单处于待充状态,可以重复请求直到返回值为1
-1       充值请求参数错误

						

注意

对于充值失败的订单服务器有重跑机制,一般每5分钟会对充值状态为待充的订单进行重新请求,处理为成功状态(也就是返回0的订单),若超过5分钟订单还未执行成功请与运营同学联系。

查询昵称


充值平台向游戏服务器查询昵称接口发起 GET 请求

http://游戏服务器域名/查询昵称接口

GET 参数

roleid           玩家游戏内角色id,唯一标识
sid          	游戏服务器ID,字符串类型

				

游戏服务器查询昵称接口返回值协议


调用返回用json格式数据

{
    ret: 0,
    error: "错误信息",
    uid: "游戏角色id",
    nickname: "昵称",
    level: 1
    account: "平台uid"
    lifecard: 1 (角色终身卡状态: 1为已拥有, 2为未拥有, 可选字段)
    monthcard: 1 (角色月卡状态: 1为已拥有, 2为未拥有, 可选字段)
}

ret 取值:
0      查询成功,需要返回 uid, nickname, level  字段
1      查询失败,要返回 error 字段  返回实例:{"ret":1,"error":"无此玩家"}

						

第三方充值

游戏服务器需要配置 网页充值需要的产品项, 这些充值产品项不显示在客户端。



需要增加额外产品项,以支持拆单:

例如:有固定50台币的点卡,但是游戏充值项最低是34台币,这时应有小额充值项用于补充,以满足50台币订单。
(50台币拆单:50台币=34台币+10台币+5台币+1台币)
注意:拆单是平台主动拆单,游戏只需配置小额充值项

web01          1台币
web05          5台币
web10          10台币



						

第三方充值页面由平台提供,接入时需要实现流程图中的红色字体部分

由于SDK无法进行准确的在线数据统计,这里提供服务端在线数据统计接口。游戏服务端可以每隔5分钟自行统计在线人数,通过接口发送到TapDB,TapDB进行数据汇总和展现。

接口:        https://se-sg.tapdb.net/tapdb/online
方法:        POST
格式:        json
必须头信息:   Content-Type: application/json

请求内容:

参数名 参数类型 参数说明
appid string TapDB的appid
onlines array 多条在线数据(最多100条)

其中onlines数组的结构为

参数名 参数类型 参数说明
server string 服务器,TapDB对同一服务器每一个自然5分钟仅接受一次数据
online int 在线人数
timestamp long 当前统计数据的时间戳(秒),TapDB会按照自然5分钟进行数据对齐

示例


{
  "appid":"gkjasd13bbsa1sdk",
  "onlines":[{
    "server":"s1",
    "online":123,
    "timestamp":1489739590
  },{
    "server":"s2",
    "online":188,
    "timestamp":1489739560
  }]
}

成功判断:返回的HTTP Code为200认为发送成功,否则认为失败
游戏服务器可以通过此接口,向全体用户或者指定用户发送消息推送.

接口:        https://push.xdg.com/firebase/notify?sign=[请求验证串]
方法:        POST
格式:        json
必须头信息:   Content-Type: text/plain

POST请求内容:

参数名 参数类型 参数说明
appid string SDK APPID
title string 消息标题,可为空字符串 必须为UTF-8
body string 消息内容,不可为空字符串 必须为UTF-8
image string 展示图片网络地址,可为空字符串
ts int 当前时间戳 误差不得超过10分钟
to array/string 推送目标
参数值为 "ALL" 时,对全体用户推送信息
为用户ID数组时 [uid1, uid2, ...] ,对特定用户发送信息 注意:一次最多500个用户
label string 分析标签,格式为:^[a-zA-Z0-9-_.~%]{1,50}$

GET请求参数:

sign 验证算法


    算法生成的哈希值(小写)
    md5(POST 内容 + AppKey)

						

示例


https://push.xdg.com/firebase/notify?sign=4deda4c253999d03b41587ac7d8040a3

POST 内容
{"title":"消息标题","body":"消息内容","image":"","appid":2000001,"to":[300095288,300095289],"ts":1616062391}

sign算法
md5(`{"title":"消息标题","body":"消息内容","image":"","appid":2000001,"to":[300095288,300095289],"ts":1616062391}` + `c4f66848841b1c5af83ae12f6f3273a9`) = 4deda4c253999d03b41587ac7d8040a3

Google 商店推荐

返回和向上导航的规则



“向上”按钮用于根据屏幕之间的层级关系在某个应用内部导航。 例如,如果屏幕 A 显示项目列表,并且选择某个项目会调出屏幕 B(该屏幕显示项目的更多详情),则屏幕 B 应提供可返回屏幕 A 的“向上”按钮。

如果屏幕是应用中层级最高的屏幕(即应用的主屏幕),则无需提供向上按钮。

系统的“返回”按钮用于按照用户最近操作的屏幕历史记录,按时间逆序导航。 它通常基于屏幕之间的时间关系,而非应用的层级关系。

如果之前查看的屏幕也是当前屏幕的父级项,按下“返回”按钮的作用跟按下“向上”按钮一样 — 这种情况很常见。 但是,“向上”按钮可确保用户留在应用内,与此不同的是,“返回”按钮可让用户返回到主屏幕,甚至返回不同的应用。

战斗支持暂停的游戏,战斗中按返回键要暂停

战斗支持暂停的游戏,返回桌面时游戏要自动暂停

关闭按钮需要关闭窗口不能返回上一层级

						

隐藏虚拟键盘


public void hideNavigationBar() {
  int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
	| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
	| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
	| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
	| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
	| View.SYSTEM_UI_FLAG_IMMERSIVE ;

  if( android.os.Build.VERSION.SDK_INT >= 19 ){
	uiFlags |= 0x00001000;
  } else {
	uiFlags |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
  }

  getWindow().getDecorView().setSystemUiVisibility(uiFlags);
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if( hasFocus ) {
	hideNavigationBar();
  }
}

						

整理权限

推荐使用平台提供的接口。


请求权限(上Google推荐,建议使用)


this                Activity 类指针.
permission          权限名称
requestCode         请求Code
necessaryPermisson  必要权限标记

注意⚠️:如果传入的必要条件被用户拒绝,会直接退出游戏。


SDKTxwyPassport.requstPermissionForResult(this, String permission , int requestCode , boolean necessaryPermisson);



例子:

private final int REQUEST_PERMISSION_CODE = 10011;
 
SDKTxwyPassport.requstPermissionForResult(this , Manifest.permission.WRITE_EXTERNAL_STORAGE , REQUEST_PERMISSION_CODE , true);

使用该接口,根据需要在调用该api的Activity中实现onActivityResult函数,得到权限获取结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if(requestCode == REQUEST_PERMISSION_CODE){
            LogUtil.d(TAG , "权限结果:" + resultCode);
            if(resultCode == Activity.RESULT_OK){ //权限获取成功
                LogUtil.d(TAG , "权限结果:获取成功");

            }else if(resultCode == Activity.RESULT_CANCELED){//权限获取失败
                LogUtil.d(TAG , "权限结果:获取失败");

            }
        }
        
        if (!SDKTxwyPassport.handleActivityResult(this, requestCode, resultCode, data)) {
        	super.onActivityResult(requestCode, resultCode, data);
	}
    ...
}

建议⚠️:AndroidManifest.xml注册此Activity时建议去掉android:launchMode="singleTask",防止部分机器调用onActivityResult的时机不对

						

如使用SDK提供的接口,则不在需要考虑以下规则。


尽量避免在启动时候请求设备权限。仅在需要使用的位置请求权限。
详情参考:https://developer.android.com/training/permissions/index.html
必须去掉的权限 (文档中要求增加的除外)

android.permission.WRITE_SETTINGS
android.permission.NFC
android.permission.MANAGE_ACCOUNTS
android.permission.GET_ACCOUNTS
android.permission.USE_CREDENTIALS
android.permission.AUTHENTICATE_ACCOUNTS
android.permission.MOUNT_UNMOUNT_FILESYSTEMS
android.permission.CHANGE_WIFI_STATE
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.CHANGE_CONFIGURATION
android.permission.SEND_SMS
android.permission.CALL_PHONE
android.permission.CHANGE_NETWORK_STATE
android.permission.PROCESS_OUTGOING_CALLS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.READ_SMS
android.permission.RECEIVE_SMS
android.permission.DOWNLOAD_WITHOUT_NOTIFICATION
com.android.vending.CHECK_LICENSE

						
建议去掉的权限 (文档中要求增加的除外)

android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
android.permission.BROADCAST_STICKY
android.permission.GET_TASKS
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.SYSTEM_ALERT_WINDOW
android.permission.RECORD_AUDIO
android.permission.READ_LOGS
android.permission.CAMERA
android.permission.READ_PHONE_STATE

						
必要权限,需要App启动时,拉取授权被用户拒绝给出提示并作出相应操作,如图所示:

跳转至应用设置页面,参考代码:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);



非必要权限,需要在使用前提示用户,如图所示:

推荐标准

获得 Google Play 商店推荐的必做需求 Google 官方文档 http://developer.android.com/intl/zh-cn/design/style/devices-displays.html

以下是经过提炼的需求,一般做到本文档需求即可获得推荐

<application
	...
	android:theme="@style/Theme.AppCompat.Light">
在AndroidManifest.xml中appilication层加上主题

targetSdkVersion 30 或者更高

App Icon 须提供以下规格

切换至后台,或者设备锁屏时,游戏应处于暂停状态。
游戏有可暂停的功能时,当在游戏界面弹出对话框时,必须把游戏暂停。

其它接口

自定义事件


SDKTxwyPassport.evtTrack(this,"eventName");


						

跟踪游戏的启停

跟踪玩家游戏次数和游戏时长。需要给游戏中每个Activity的onResume和onStop中添加对应的调用,如果多个Activity继承同一个父类,只需要在父类中添加调用即可。比如onResume方法,直接在Activity的onResume方法的最后添加SDKTxwyPassport.onResume(this)即可。

		
this 为 Activity 类指针.

SDKTxwyPassport.onResume(this);
SDKTxwyPassport.onStop(this);

		
	

Firebase推送开关



SDKTxwyPassport.firebaseMessageSwitch(Activity act , boolean isOpen);


						

获得Firebase开关状态



SDKTxwyPassport.isFirbaseMessageSwitchOpen(Activity act);


						

Facebook分享

Facebook 应用由平台配置,并提供可以测试分享的Facebook账号.

测试Facebook的相关功能,需要翻墙!



分享图片

this Activity 类指针. Bitmap Bitmap 类型图片对象 feedDelegete 回调 SDKTxwyPassport.feedWithImage(this, Bitmap, new SDKTxwyPassport.feedDelegete() { @Override public void txwyDidFeed(String str) { if (str.equals("success")) { //分享成功 } else { //分享失敗 } } });

分享链接

this Activity 类指针. link String 类型链接 feedDelegete 回调 SDKTxwyPassport.feedWithLink(this, link, new SDKTxwyPassport.feedDelegete() { @Override public void txwyDidFeed(String str) { if (str.equals("success")) { //分享成功 } else { //分享失敗 } } });

分享链接(带描述)

this Activity 类指针. msg String 类型消息 link String 类型链接 feedDelegete 回调 SDKTxwyPassport.feedWithLink(this, msg , link, new SDKTxwyPassport.feedDelegete() { @Override public void txwyDidFeed(String str) { if (str.equals("success")) { //分享成功 } else { //分享失敗 } } });

Instagram分享


this          Activity 类指针.
Bitmap        Bitmap 类型图片对象     

SDKTxwyPassport.shareWithInstagram(this, Bitmap);


						

获取礼包接口

在营运提出需求时,才需要接入此接口

this          Activity 类指针.

SDKTxwyPassport.iLikeWithSvrID(this, "服务器ID", "角色昵称");


						

Twitter 分享

在营运提出需求时,才需要接入此接口

Twitter 分享接口

		
this         Activity 类指针.
msg          分享文本
url          跳转链接
bitmap      图片

SDKTxwyPassport.twitterFeed(this, "msg", "url", Bitmap);
		
	

Twitter接入注意事项:

		
分享时必须安装Twitter客户端,否则分享失败。
		
	

Line 分享

在营运提出需求时,才需要接入此接口

Line 文本分享接口


this         Activity 类指针.
msg          分享文本

try {
	StringBuilder urlStr = new StringBuidler("line://msg/text/");
	urlStr.append(URLEncoder.encode(msg, "UTF-8"));

	Uri uri = Uri.parse(urlStr.toString());

	this.startActivity(new Intent(Intent.ACTION_VIEW, uri));
} catch (Exception e) {
	e.printStackTrace();
}


Line 图片分享接口

		
this         Activity 类指针.
bitmap       图片

SDKTxwyPassport.shareImageToLine(this,  Bitmap);
		
	
注意:Line应用不在后台运行并且没有登录账号时,拉不起应用。

聊天室屏蔽规则


聊天屏蔽規則:

1,对指定等级做屏蔽字限制;
2,对等级高于我们设定值的玩家不做限制;
3,被屏蔽发言自己客户端可正常显示,无特殊提示;
4,被屏蔽的发言整句都不显示在聊天频道,触发屏蔽的玩家和其他玩家都无法得知是哪句话触发了屏蔽


舉例:
我們限制等級15級以下的玩家,聊天有限制

15級以下的玩家,如果聊天中包涵:line,LINE,=,元寶,等字眼,他所發的消息,就只有他自己看得到,其他玩家是收不到了。

						

社区

facebook,twitter,navercafe App内打开网页

// Facebook  社区
String url = "https://facebook.com/UltimateSchoolXDG";
PackageManager pm = this.getPackageManager();
Uri uri = Uri.parse(url);

try {
	ApplicationInfo applicationInfo = pm.getApplicationInfo("com.facebook.katana", 0);
	if (applicationInfo.enabled) {
		uri = Uri.parse("fb://facewebmodal/f?href=" + Uri.encode(url));
	}
}
catch (PackageManager.NameNotFoundException ignored) {
}
startActivity(new Intent(Intent.ACTION_VIEW, uri));

// Twitter 社区
String url = "https://twitter.com/UltimateXDG";
PackageManager pm = this.getPackageManager();
Uri uri = Uri.parse(url);

startActivity(new Intent(Intent.ACTION_VIEW, uri));

// Café 社区
String url = "https://cafe.naver.com/UltimateSchool";
PackageManager pm = this.getPackageManager();
Uri uri = Uri.parse(url);

try {
	ApplicationInfo applicationInfo = pm.getApplicationInfo("com.nhn.android.navercafe", 0);
	if (applicationInfo.enabled) {
		uri = Uri.parse("navercafe://cafe?cafeUrl=" + Uri.encode("UltimateSchool"));
	}
}
catch (PackageManager.NameNotFoundException ignored) {
}
startActivity(new Intent(Intent.ACTION_VIEW, uri));

						

url参数 由平台提供

访问页面

SDK将会在游戏内嵌Webview中访问指定Url


this      Activity 类指针.
url       指定url

SDKTxwyPassport.navigate(this,  url);


						

常见问题

充值项价格查询失败?

  • 首先平台需要先配置好充值项
  • 必须使用真机测试,模拟器取不到价格,也不可测试充值
  • 必须翻墙!!!
  • APK包名必须正确,而且正确签名

游客可以充值吗?

  • YES,游客账号和普通账号是同样的,可以正常充值
  • 游客账号在下次登录通行证时,可以绑定到自选账号,升级未普通账号
  • 游客账号绑定后,uid 不会变化。游戏不用处理绑定后账号的变化.

登录通行证账号出现问题?

  • 显示 游戏错误, 平台没有开放登录,请与运营联系
  • 使用 Facebook 方式登录时,Facebook App显示应用处于开发模式。请使用平台提供的Facebook 测试账号登录,或者请与运营联系
  • 使用 Facebook 方式登录时,登录成功跳回游戏后,SDK界面没有继续操作。系统回调 部分编码没正确完成.

怎样测试Facebook分享和邀请?

  • 手机网络必须翻墙
  • APK包必须正确签名
  • Facebook 账号必须是Facebook App的测试人员账号,邀请的好友也必须是测试人员。
  • 邀请信息只能在手机Facebook App中看到.

Android 9.0崩溃问题&网络请求问题

  • Android 9.0崩溃问题
  • 在android9.0中, Apache HTTP 被弃用并且被移除。会缺少必要的类导致程序崩溃

    解决方法:

    • 在AndroidManifest.xml的中Application里 添加以下内容
    • <uses-library android:name="org.apache.http.legacy" android:required="false"/>

    相关参考链接
    https://developer.android.com/about/versions/pie/android-9.0-changes-28

  • Android9.0 网络请求问题
  • 在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。

    否则会导致:java.io.IOException: Cleartext HTTP traffic to **** not permitted

    有以下两种解决方案

    • APP改用https请求
    • 在 res 下新增一个 xml 目录,然后创建一个名为:network_security_config.xml 文件(名字自定) ,内容如下,大概意思就是允许开启http请求(默认允许所有网址使用非安全连接)
    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config cleartextTrafficPermitted="true" />
    </network-security-config>
    

    然后在APP的AndroidManifest.xml文件下的application标签增加以下属性

    <application
    ...
     android:networkSecurityConfig="@xml/network_security_config"
    ...
        />