Android SDK 接入手册
海外版本
请在文档的引导下,进行必要的编码。
请在文档的引导下,进行必要的编码。
APK包必须签名,提交Google Play商店以后,签名不可更改
APK包必须 zipalign 优化
必须保证测试手机的Google框架完整
minSdk 24
compileSdk 35 (34升级到35常见问题)
targetSdk 35
buildToolsVersion = "35.0.0"
Gradle JDK 不能低于17
SDK支持AndroidX (检查AndroidX配置项)
android.useAndroidX=true
android.enableJetifier=true
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
...
}
}
allprojects {
...
repositories {
// ...
google()
jcenter()
mavenCentral()
maven {//PnSDK
allowInsecureProtocol = true
url 'http://git-maven.17995api.net:8888/api/v4/projects/112/packages/maven'
}
}
}
dependencyResolutionManagement {
...
repositories {
google()
mavenCentral()
// 配置PnSDK的Maven仓地址。
maven {//PnSDK
allowInsecureProtocol = true
url 'http://git-maven.17995api.net:8888/api/v4/projects/112/packages/maven'
}
}
}
android {
//...
//解决编译时的重复问题。
packagingOptions {
exclude 'META-INF/INDEX.LIST'
exclude 'META-INF/DEPENDENCIES'
}
}
//建议配置动态版本的缓存策略,保证能够及时更新sdk的版本
configurations.all {
resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
}
dependencies {
// ...
implementation 'com.pn:pnsdk:+'
implementation 'com.pn.pnsdk:res-[APPID]:+'
//注意:[APPID]请替换为实际参数,AppID参数可以在提供的说明文档中找到。
//例如:implementation 'com.pn.pnsdk:res-9000001:+'
}
# ==================== PnSDK 核心防混淆规则 ====================
# 保护PnSDK核心包
-keep class com.pn.sdk.** {*;}
-keep interface com.pn.sdk.** {*;}
# ==================== 第三方SDK防混淆规则 ====================
# TapTap SDK
-keep class com.taptap.**{*;}
-keep class com.tapsdk.**{*;}
-keep class com.tapdb.**{*;}
# TikTok SDK
-keep class com.tiktok.** { *; }
# Google相关SDK
-keep class com.android.billingclient.api.** { *; }
-keep class com.google.android.gms.** { *; }
-keep class com.google.android.play.** { *; }
-keep class com.google.firebase.** { *; }
-keep class com.google.api.** { *; }
-keep class com.google.android.libraries.identity.** { *; }
# Google Credential Manager
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
# Facebook SDK
-keep class com.facebook.** { *; }
# AppLovin SDK
-keep class com.applovin.** { *; }
-keep class com.unity3d.** { *; }
-keep class androidx.credentials.** { *; }
# AppsFlyer SDK
-keep class com.appsflyer.** { *; }
# ==================== 通用防混淆规则 ====================
# 保护WebView
-keepclassmembers class * {
@android.webkit.JavascriptInterface ;
}
# 保护native方法
-keepclasseswithmembernames class * {
native ;
}
# 保护注解
-keepclassmembers class * {
@android.support.annotation.Keep ;
@android.support.annotation.Keep ;
@androidx.annotation.Keep ;
@androidx.annotation.Keep ;
}
-keep @android.support.annotation.Keep class * {*;}
-keep @androidx.annotation.Keep class * {*;}
# 保护序列化相关
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保护枚举
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保护反射
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Deprecated
-keepattributes SourceFile,LineNumberTable
# 保护Parcelable
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 保护R文件
-keep class **.R$* {
public static ;
}
# 保护BuildConfig
-keep class **.BuildConfig {
public static final *;
}
请确保在 Launcher Activity OnCreate 函数中初始化SDK
protected void onCreate(Bundle savedInstanceState) {
String appID = "APPID";
String appKey = "APPKEY";
String channel = "渠道标识";
String langID = "语言字符";// 查看多语言标签
Map extraMap = new HashMap();
extraMap.put("termAgreed", true或者false);
//termAgreed为true,表明游戏在调用startWithAppID之前用户已经同意过条款,该条款由游戏自行处理。
//termAgreed为false,使用SDK条款页面。
//extraMap为null,默认使用SDK条款页面。
PnSDK.startWithAppID(activity , appId, appKey, channel, langID, extraMap);
}
所需要的参数,可以在对接说明文档中找到.
<实现广播接收器>
public class MyBroadcastReceiver extends BroadcastReceiver {
final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "PnSDKPassportNotification")) {
// 通行证状态发生变化, 已登录或者已登出
Log.v(TAG, "游戏接收到广播:PnSDKPassportNotification");
Map accountInfoMap = PnSDK.getAccountInfo();
if (null == accountInfoMap) {
Log.d(TAG, "游戏接收到PnSDKPassportNotification广播,账户没登录不向下执行代码");
//TODO 没有登录
return;
}
String uid = (String) accountInfoMap.get("uid");// 获取用户唯一ID
String jwt = (String) accountInfoMap.get("jwt");// 登录凭证
int age = (int) accountInfoMap.get("age"); // 实名制用户年龄, 未登记返回为0
String idmd5 = (String) accountInfoMap.get("idmd5"); // 实名制用户身份证md5, 未登记返回为“”
String aaStatus = (String) accountInfoMap.get("aa-status");// 防沉迷状态, 返回 "restricted" 禁止继续游戏
int guest = (int) accountInfoMap.get("guest"); // 是否游客,1为游客,0为其它账号或者已绑定游客
Log.d(TAG, "PnAccountInfo ,uid: " + uid + " jwt: " + jwt);
//TODO 客户端需要将 jwt 登录凭证上传至游戏服务器, 游戏服务器再通过SDK服务器 验证接口 进行验证,以确保登录账号合法。
return;
}
if (TextUtils.equals(action, "PnSDKPaymentNotification")) {
//TODO 充值结束(返回充值信息)
Log.v(TAG, "游戏接收到广播:PnSDKPaymentNotification");
String productid = intent.getStringExtra("productid");
String nonce = intent.getStringExtra("nonce");
String orderid = intent.getStringExtra("orderid");
String transactionid = intent.getStringExtra("transactionid");
if (TextUtils.isEmpty(productid)) {
// 用户取消 或者 充值失败 或者 拉取了第三方充值
// 通常情况下,客户端不需要做任何处理,不要弹出充值失败等提示
} else {
Log.d(TAG, "游戏广播接收,充值成功 " + productid);
//TODO 充值成功
}
return;
}
if (TextUtils.equals(action, "PnSDKRequestPaymentNotification")) {
//发起充值 (返回充值项ID),用于完成补单
Log.v(TAG, "游戏接收到广播:PnSDKRequestPaymentNotification");
//当玩家购买掉单或者应用外购买完成时,游戏内会收到此事件通知,客户端需要根据事件通知指定的产品项ID发起一次购买。
//游戏发起新充值请求,与之前充值无关,当前场景无法发起充值时,可以无视此事件通知。
String productId = intent.getStringExtra("productid");
String srvid = "游戏服务器"; //TODO 游戏传入
String nonce = 透传参数; //TODO 游戏传入
Log.d(TAG, "需要发起购买的产品项ID: " + productId);
PnSDK.payWithProductID((Activity) context, productId, srvid, nonce, null);
// 注意: nonce 必须是有效的透传参数,如果在补单时无法获取有效的参数,则传入空字符串。充值平台会自动匹配可用的透传参数.
return;
}
}
}
<在主页面注册广播>
public class MainActivity extends Activity {
private MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myBroadcastReceiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("PnSDKPassportNotification");
intentFilter.addAction("PnSDKRequestPaymentNotification");
intentFilter.addAction("PnSDKPaymentNotification");
intentFilter.addAction("PnSDKAdRewardsNotification");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Android14(34)及以上必须添加标记
registerReceiver(broadcastReceiver, intentFilter , Context.RECEIVER_NOT_EXPORTED);
}else {
registerReceiver(broadcastReceiver, intentFilter);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//...
PnSDK.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myBroadcastReceiver);
}
}
PnSDK.signIn(activity);
// 首先登出当前账号
PnSDK.signOut(activity);
// 紧接着调登入接口
PnSDK.signIn(activity);
游戏必须接入SDK的客服中心,建议在游戏内设置界面和登录界面放置客服中心按钮,详情需求请查阅对接说明文档。
若未登录游戏服务器,服务器ID 请传入 "none"
若游戏角色未登录,角色昵称 请传入 "not logged in"
extraMap 扩展参数 需增加roleid(角色ID), level(角色等级)参数,角色未登录时可为NULL
Map extraMap = new HashMap();
extraMap.put("roleid", 角色ID);
extraMap.put("level", 角色等级);
...
PnSDK.bugReport(activity, "服务器ID", "角色昵称", "游戏客户端版本号", extraMap);
//帐号管理主要提供 账号绑定, 重置游客 等功能, 建议在游戏设置界面中放置 帐号管理 按钮
extraMap 扩展参数 需增加roleid(角色ID), level(角色等级)参数,角色未登录时可为NULL
Map extraMap = new HashMap();
extraMap.put("roleid", 角色ID);
extraMap.put("level", 角色等级);
...
PnSDK.userCenter(activity, "服务器ID", "角色昵称", "游戏客户端版本号", extraMap);
// 事件标识 详细说明请查阅对接说明文档
PnSDK.evtTrack(activity,"eventName");
//用户评分接口的使用时机,请参阅对接说明文档
若未登录游戏服务器,服务器ID 请传入 "none"
若游戏角色未登录,角色昵称 请传入 "not logged in"
extraMap 扩展参数 需增加roleid(角色ID), level(角色等级)参数
Map extraMap = new HashMap();
extraMap.put("roleid", 角色ID);
extraMap.put("level", 角色等级);
...
PnSDK.appReview(activity, "服务器ID", "角色昵称", "游戏客户端版本号", extraMap);
Android 6.0及更高版本中,用户与需要用到危险权限(下图)的功能进行互动时需要动态请求权限。
Android中的危险权限分为9组24种
只需请求用到的危险权限。
/**
* @param activity
* @param listener 请求监听(所请求的权限全部允许回调onSuccess)
* @param isNecessary 是否必要权限
* @param permissions 权限名称(可以传递多个,用逗号隔开):
* Manifest.permission.WRITE_EXTERNAL_STORAGE
* ...
*/
PnSDK.requestPermissions(this, new IRequestPermissionsListener() {
@Override
public void onSuccess() {
Log.d(TAG, "获取授权成功。");
}
@Override
public void onFailed() {
Log.d(TAG, "获取授权失败。");
}
}, isNecessary ,Manifest.permission.WRITE_EXTERNAL_STORAGE
);
调用该接口前,确认应用清单(AndroidManifest.xml)中已经声明需要请求的权限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- other permissions go here -->
<application ...>
...
</application>
</manifest>
如需自行实现动态权限申请,参考以下流程图。以及官方说明:
官方参考:https://developer.android.com/training/permissions/requesting?hl=zh-cn (需要翻墙)
实现系统回调,否则SDK无法正常工作
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//...
PnSDK.onActivityResult(requestCode, resultCode, data);
}
充值界面由游戏开发方提供.
充值项的设定,请参阅对接说明文档.
支付产品的价格需要通过SDK提供的接口查询.
请在游戏启动过程中,后台异步查询价格.
避免
在显示商店时同步查询价格,这会导致打开商店界面有明显的延迟.
List products = new ArrayList<>();
products.add("game_001");
products.add("game_002");
PnSDK.queryWithProducts(activity, products, new IProductDetailsResponseListener() {
@Override
public void onPnProductDetailsResponse(List<com.pn.sdk.billing.ProductDetails> productDetailsList) {
//查询失败
if (null == productDetailsList)
return;
for (ProductDetails product : productDetailsList) {
product.getProductId(); //-- 产品ID
product.getDisplayPrice();//格式化价格,包含货币符号,可直接用于价格显示
product.getPrice();//价格
product.getPriceAmountMicros();//微单位价格,其中1,000,000微单位等于货币的一单位。long 类型
String priceCurrencyCode = product.getPriceCurrencyCode();//货币代码
Currency.getInstance(priceCurrencyCode).getSymbol();//货币符号
}
}
});
如未使用格式化价格, 则需要将小数部分为0的情况,化为整数去掉小数部分.
e.g. $100.00 => $100
$99.99 => 不变 $99.99
某些情况下 queryWithProducts
会失败,返回空的产品列表,请显示默认的产品价格,默认价格请查阅对接说明文档
product 充值产品ID
srvid 服务器ID
nonce 透传参数 充值透传参数,SDK充值平台会原样传回到游戏服务器,,详见支付服务器对接
extraMap 扩展参数 需增加roleid(角色ID), level(角色等级)参数
Map extraMap = new HashMap();
extraMap.put("roleid", 角色ID);
extraMap.put("level", 角色等级);
...
PnSDK.payWithProductID(activity, "产品ID", "服务器ID", "nonce 透传参数", extraMap);
// 充值完成后, SDK会发送广播PnSDKPaymentNotification, 查看 广播监听 说明
收到 PnSDKPaymentNotification
回调不代表充值已到账,以游戏服务端收到SDK充值平台回调为准.
充值流程图
玩家完成游戏角色登录后,游戏需要主动查询是否有未完成的订单
PnSDK.queryPendingTransaction(activity);
//如果查询到存在未完成订单, SDK会发送广播"PnSDKRequestPaymentNotification", 查看广播监听说明
用于查询当前生效的订阅项, 如游戏没有可续订充值项则无需处理
String subscriptionId = "com.subs.01";
PnSDK.querySubscription(this, subscriptionId, new ISpeificPurchaseListener() {
@Override
public void onPurchaseResponse(com.android.billingclient.api.Purchase purchase) {
if(null == purchase){
return;
}
Log.d(TAG , "purchase originalJson: " + purchase.getOriginalJson());
/*
{
"orderId": "GPA.3304-7399-****-21711", ---商店订单ID
"packageName": "com.xxx.demo", ---包名
"productId": "com.subs.01", ---订阅项ID
"purchaseTime": 1637899846435, ---订阅时间
"purchaseState": 0, ---订阅状态
"purchaseToken": "alajnicmlhiip******Fo0LQVaJTw", ---订阅token
"autoRenewing": true, ---是否自动续订
"acknowledged": true ---订阅是否确认
}
*/
}
});
PnSDK.querySubscriptions(activity, new IPurchaseResponseListener() {
@Override
public void onPurchaseResponse(List<Purchase> purchaseList) {
if (null == purchaseList) {
PnLog.d(TAG, "没有已订阅充值项");
return;
}
for (Purchase purchase : purchaseList) {
PnLog.d(TAG , "purchase originalJson:" + purchase.getOriginalJson());
/*
{
"orderId": "GPA.3304-7399-****-21711", ---订单ID
"packageName": "com.xxx.demo", ---包名
"productId": "com.subs.01", ---订阅项ID
"purchaseTime": 1637899846435, ---订阅时间
"purchaseState": 0, ---订阅状态
"purchaseToken": "alajnicmlhiip******Fo0LQVaJTw", ---订阅token
"autoRenewing": true, ---是否自动续订
"acknowledged": true ---订阅是否确认
}
*/
}
}
});
用于测试客户端充值掉单后处理的流程,模拟系统出现掉单时SDK的广播行为
测试方式一:
android代码发送广播测试:
Intent intent = new Intent("PnSDKRequestPaymentNotification");
intent.putExtra("productid", [产品项ID]);
activity.sendBroadcast(intent);
测试方式二:
超链接测试,创建htlm文件,将以下地址放入<a href="链接地址">标签中。浏览器打开创建的html文件,点击该链接。
pnsdk://[Apk PackageName]?action=purchaseIntent&productIdentifier=[产品项ID]
其中 [Apk PackageName], [产品项ID] 请替换为实际参数
实例: <a href="pnsdk://com.pnsdk.demo?action=purchaseIntent&productIdentifier=com.pnsdk.demo.sku.01">点击该链接</a>
请求地址后,浏览器会拉起App,并接收到 "PnSDKRequestPaymentNotification" 广播
游戏应当在已完成登录的情况下拉起指定充值项的充值请求(全新的充值,与未完成订单无关),并正确填写服务器,nonce等参数.
如当前是未登录状态,则忽略事件广播
测试方式三:
使用adb测试,在应用开启的状态下执行下面adb命令:
adb shell am start -W -a android.intent.action.VIEW -d "替换下面的URI地址"
pnsdk://[Apk PackageName]?action=purchaseIntent\&productIdentifier=[产品项ID]
其中 [Apk PackageName], [产品项ID] 请替换为实际参数。
注意:执行的adb命令中,如果有&符号,需要在&符号前面加上\
实例:
adb shell am start -W -a android.intent.action.VIEW -d "pnsdk://com.pnsdk.demo?action=purchaseIntent\&productIdentifier=com.pnsdk.demo.sku.01"
本地推送说明:
1. 原则上无互动性的消息,都应该使用本地推送
2. 服务器推送仅使用在交互性的消息上
// 延迟60秒后弹出推送
PnSDK.setNotification("推送ID", 60, "通知标题", "通知内容", null);
// 特定时间弹出推送
SimpleDateFormat formatter = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
Date date = null;
try {
date = formatter.parse("2021-10-01 12:00:00");
} catch (ParseException e) {
e.printStackTrace();
}
if(null != date){
PnSDK.setNotification("推送ID" , date , "通知标题" , "通知内容" , null);
}
// 移除ID为 @"推送ID1", @"推送ID2" 的本地推送
List idList = new ArrayList<>();
idList.add("推送ID1");
idList.add("推送ID2");
PnSDK.deleteNotifications(idList);
// 移除所有本地推送
PnSDK.deleteNotifications(null);
“向上”按钮用于根据屏幕之间的层级关系在某个应用内部导航。 例如,如果屏幕 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();
}
}
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
以下是经过提炼的需求,一般做到本文档需求即可获得Google Play 商店推荐
<application
...
android:theme="@style/Theme.AppCompat.Light">
在AndroidManifest.xml中appilication层加上主题
可使用Android Studio创建自适应图标,请参阅使用 Image Asset Studio 创建应用图标。
service 服务名
Map extraMap 扩展参数,角色未登录时可为NULL
Map extraMap = new HashMap();
extraMap.put("roleid", "角色ID");
extraMap.put("nickname", "角色昵称");
...
PnSDK.openService(this, "服务名", extraMap);
服务名 | extraMap 参数 | 说明 |
---|---|---|
survey | roleid 角色ID(可选) nickname 角色昵称(可选) |
打开调查问卷 |
terms | 打开服务条款 | |
privacy | 打开隐私条款 |
Facebook 应用由PnSDK平台配置,并提供可以测试分享的Facebook账号.
测试Facebook的相关功能,需要翻墙!
Bitmap Bitmap类型图片对象
Map extraMap 扩展参数,传入null
PnSDK.facebookFeedWithImage(activity, Bitmap, extraMap ,new IFeedListener() {
@Override
public void onError(String error) {
if (TextUtils.isEmpty(error)){
//分享成功
}else{
//分享失敗
}
}
});
Map extraMap 扩展参数,传入null
PnSDK.facebookFeedWithMessage(this, "测试分享链接", "https://www.yourlink.com", extraMap , new IFeedListener() {
@Override
public void onError(String error) {
if(TextUtils.isEmpty(error)){
PnLog.d(TAG, "Facebook分享链接成功");
}else {
PnLog.d(TAG, "Facebook分享链接失败: " + error);
}
}
});
Bitmap Bitmap 类型图片对象
Map extraMap 扩展参数, 传入null
PnSDK.instagramFeedWithImage(activity, Bitmap , extraMap);
聊天屏蔽規則:
1,对指定等级做屏蔽字限制;
2,对等级高于我们设定值的玩家不做限制;
3,被屏蔽发言自己客户端可正常显示,无特殊提示;
4,被屏蔽的发言整句都不显示在聊天频道,触发屏蔽的玩家和其他玩家都无法得知是哪句话触发了屏蔽
舉例:
我們限制等級15級以下的玩家,聊天有限制
15級以下的玩家,如果聊天中包涵:line,LINE,=,元寶,等字眼,他所發的消息,就只有他自己看得到,其他玩家是收不到了。
facebook,twitter,navercafe App内打开网页
//Facebook 社区
String url = "https://facebook.com/UltimateSchool";
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/Ultimate";
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参数 由平台提供
PnSDK.setLanguage("语言字符");
tw | 繁体 | cn | 简体 | en | 英语 | de | 德语 |
---|---|---|---|---|---|---|---|
jp | 日语 | ko | 韩语 | th | 泰国 | id | 印尼语 |
tr | 土耳其语 | es | 西班牙语 | ru | 俄语 | fr | 法语 |
pt | 葡萄牙语 | vi | 越南语 |
在应用级build.gradle
中额外添加依赖项:
dependencies {
// ...
implementation 'com.pn:pnsdk:+'
implementation 'com.pn.pnsdk:res-[APPID]:+'
//其中[APPID]请替换为实际参数,参数可以在提供的对接说明文档中找到
//仅限越南地区:
implementation 'com.pn:vn-float-window:+'
}
添加依赖项的仓库地址配置:
//...
repositories {
//...
maven {//PnSDK
allowInsecureProtocol = true
url 'http://git-maven.17995api.net:8888/api/v4/projects/112/packages/maven'
}
maven {
url "https://android-sdk.is.com"
}
maven {
url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea"
}
maven {
url "https://artifact.bytedance.com/repository/pangle"
}
}
在应用级build.gradle
中添加依赖项:
dependencies {
// ...
implementation 'com.pn:pnsdk:+'
implementation 'com.pn.pnsdk:res-[APPID]:+'
//其中[APPID]请替换为实际参数,参数可以在提供的对接说明文档中找到
implementation ('com.pn:pn-applovin:+'){
exclude module: 'protobuf-kotlin-lite'
}
}
extraMap 扩展参数,传入null
PnSDK.showAdWithExtra(activity , null);
Map map = new HashMap();
map.put("debug" , true);
PnSDK.showAdWithExtra(activity , map);
<实现广播接收器>
public class MyBroadcastReceiver extends BroadcastReceiver {
final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "PnSDKAdRewardsNotification")) {
Log.v(TAG, "游戏接收到广播:" + "PnSDKAdRewardsNotification");
String status = intent.getStringExtra("status");
Log.d(TAG , "广告状态:" + status);
if(TextUtils.equals(status , "cancel")){
Log.d(TAG , "无广告,取消显示");
return;
}
if(TextUtils.equals(status , "start")){
Log.d(TAG , "广告显示开始");
return;
}
if(TextUtils.equals(status , "complete")){
Log.d(TAG , "广告显示结束");
return;
}
if(TextUtils.equals(status , "reward")){
Log.d(TAG , "玩家已观看广告,需发送广告奖励");
String label = intent.getStringExtra("label");
int amount = intent.getIntExtra("amount" , -1);
Log.d(TAG , "label: " + label + " amount: " + amount );
//todo 发放奖励
}
}
}
}
参考代码:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("XXXXXX");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Android14(34)及以上必须添加标记
registerReceiver(broadcastReceiver, intentFilter , Context.RECEIVER_NOT_EXPORTED);
}else {
registerReceiver(broadcastReceiver, intentFilter);
}
SDK在2025-06-16日支持targetSdk 35,游戏在升级targetSdk时需要注意编译工具的版本问题:
1、推荐Android Studio Meerkat | 2024.3.1 (支持Gradle plugin8.8.2)
2、升级Gradle Plugin至8.8.2
3、升级Gradle 至8.10.2 (gradle-8.10.2-all)
(您可以在 Android Studio 的 File > Project Structure > Project 菜单中查看或指定插件版本)
4、SDK Build Tools 35.0.0
5、JDK 17
6、miniSDK Version 24
7、compile Version 35
SDK在2025-03-18日发布了比较大的更新(Google登录方式),在这个日期之后重新做包会发生一些常见的编译错误:
问题1:Failed to transform error_prone_annotations-2.36.0.jar....
解决方法:
1、升级gradle插件至8.1.1
2、升级gradle至8.0
问题2:DuplicateRelativeFileException: 2 files found with path 'META-INF/INDEX.LIST'....
解决方法:
在application级别的build.gradle上增加配置:
android {
...
packagingOptions {
exclude 'META-INF/INDEX.LIST'
exclude 'META-INF/DEPENDENCIES'
}
}