11/08/2024 16:35:55

MSDK Policy 接入和升级指引

一、 Policy 组件说明

MSDK Policy 组件是 MSDK 团队为业务提供的启动时,弹窗通用解决方案,主要用于解决合规要求中的启动时,用户协议及权限处理相关内容。 参考 UI 界面如下

二、 Android 接入指引

2.1 Policy 接入升级指引

2.1.1 基础环境配置

2.1.1.1 引入 SDK 插件包

MSDK V5 默认携带了 MSDK Policy 组件,业务无需处理

2.1.1.2 请求 MSDK 配置服务器地址

在 MSDK 的配置文件中,添加或确认 MSDK_POP_URL 配置。该配置为 MSDK 弹窗服务器地址,用于下发弹窗规则。
配置文件一般在 assets 目录下,文件名:

  • MSDKConfig.ini

当前 MSDK 有四个环境公有集群环节:

业务需要跟进安装包类型进行配置,如国内正式环境,配置信息如下:

MSDK_POP_URL = https://pop-release.itop.qq.com

如果有其他独立部署需求,请与我们联系 !

2.1.2 配置 Policy 主 Activity

2.1.2.1 版本升级

如果之前有接入过旧版本 MSDKPolicy,升级到新版本 MSDK 时,仅需修改 AndroidManifest.xml 中的 Policy 的 Activity 配置

V1 版本 V2 版本
com.tencent.gcloud.msdk.core.policy.MSDKPolicyActivity com.tencent.gcloud.msdk.core.policy.MSDKPolicyV2Activity

其他配置保存不变即可

2.1.2.2 新接入
注意:该方案需要修改游戏的主 Activity,项目需要跟进自身情况进行对应的主 Activity 逻辑调整。

对于新接入业务,则需在 AndroidManifest.xml 添加一个 Activity 配置:

<activity android:name="com.tencent.gcloud.msdk.core.policy.MSDKPolicyV2Activity"
    android:configChanges = "keyboard|keyboardHidden|screenLayout|screenSize"
    android:launchMode="singleTask"
    android:theme="@style/MSDKPolicyTheme"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
    </intent-filter>
    <!--  MSDK_POLICY_TARGET_ACTIVITY 用于指定要跳转到的【应用或者游戏的首个 Activity】-->
    <meta-data
        android:name="MSDK_POLICY_TARGET_ACTIVITY"
        android:value="com.tencent.msdk.example.StartupActivity" />
    <meta-data android:name="MSDK_POLICY_DEBUG" android:value="true"/>
    <!-- 下面这个 MSDK_RESULT_FILE_NAME 配置从 MSDKCore V5.17 版本, V3-3.3.19 版本开始默认关闭 -->
    <!-- 去掉此配置后,如果用户之前未同意过协议,覆盖安装的情况下都必须弹协议-->
    <!-- <meta-data android:name="MSDK_RESULT_FILE_NAME" android:value="itop_login.txt"/> -->
    <!-- 如需关闭 TDM 上报,需要将下面的 MSDK_POLICY_TDM_REPORT_DISABLE 开关设置为 true-->
    <!-- <meta-data android:name="MSDK_POLICY_TDM_REPORT_DISABLE" android:value="false"/> -->
</activity>
  • name 固定为 com.tencent.gcloud.msdk.core.policy.MSDKPolicyV2Activity
  • configChanges 建议保持,如果有其他需求也可以根据业务需求调整
  • launchMode 一般为 singleTask。更多信息请参考:https://developer.android.com/guide/topics/manifest/activity-element
  • theme 可以调整为业务自己的主题
  • intent-filter 示例中的为配置为启动 Activity,业务可根据自身情况进行确认后调整。注意:一个 App 只能有一个主 Activity
  • MSDK_POLICY_TARGET_ACTIVITY 游戏的原主 Activity,Policy 组件会在完成逻辑后进行跳转
  • MSDK_RESULT_FILE_NAME 建议保持默认值,如有变更请与我们进行确认
  • MSDK_POLICY_TDM_REPORT_DISABLE 上报用户开启、关闭日志数据,一般用于监控和排查问题,视情况自行关闭

2.1.3 自定义 Policy 内容

2.1.3.1 调整协议内容

如需替换 Policy 展示的文字内容,则可按下面步骤处理:
1、自行开发一个 离线 HTML 用于展示 Policy 内容,然后将其放在 assets 目录下
2、调整 MSDK Core 包下的 assets/popup 目录下 MSDKPopupLocal.config 配置文件
文件格式:JSON
调整文件:将 scene = MSDKPolicy 节点为中的 uri 指向需要替换的业务自定义的 HTML 文件路径
调整文件签名:计算自定义的 HTML 文件的 MD5 值,将其填入到 sig 字段中
注意: 这里的路径是从 assets 开始,如 HTML 文件放在 assets 目录下,则 uri 为 xxx.html;文件放在assets/popup目录下,则 uri 为 popup/MSDKPopup{scene_type}{poptype}{contenttype}{sig}.html,其中{scene_type}、{pop_type}、{content_type}、{sig}分别替换为config 配置文件中对应弹窗配置的值scene_type、pop_type、content_type、sig。

至此,就完成了policy内容的替换,完成后的配置参考如下,字段解释可参考附录:

另外,MSDK 支持在管理端上配置并下发(请联系我们开发和确认)
2.1.3.2 【可选】调整权限内容

同上,如果需要使用 MSDK 的权限弹窗,也可以参考上述配置自行进行调整。

三、 iOS 接入指引

3.1 Policy 接入指引

3.1.1 基础环境配置

3.1.1.1 引入 SDK 插件包

MSDK V5 版本已经默认携带了 MSDK Policy 组件,业务无需单独处理

3.1.1.2 请求 MSDK 配置服务器地址

在 MSDK 的配置文件中,添加或确认 MSDK_POP_URL 配置。该配置为 MSDK 弹窗服务器地址,用于下发弹窗规则。
配置文件名如下:

  • MSDK V5 版本:MSDKConfig.ini (在 MSDKAppSetting.bundle 中)

当前 MSDK 有四个环境公有集群环节:

业务需要跟进安装包类型进行配置,如国内正式环境,配置信息如下:

MSDK_POP_URL = https://pop-release.itop.qq.com
如果有其他独立部署需求,请与我们联系 !

3.1.2 MSDKV5 接入 policy

3.1.2.1 unity 接入 policy

MSDKPolicy对相关联应用启动的各个生命周期方法进行了拦截,优先执行MSDKPolicy的弹窗逻辑,等到点击弹窗“同意”按钮后,恢复执行原有的生命周期函数,保障用户同意隐私协议前应用不进行初始化 。
接入步骤:

  • 将MSDKPolicyDelegate.h 及 MSDKPolicyDelegate.mm放入Assets/Plugins/iOS目录下(注:MSDKPolicyDelegate文件见附录示例);
  • 在GCloudSDK/Editor/XUPorter/XCodePostProcess.cs文件中,将代码“EditorCode4StopLaunching(pathToBuiltProject);”注释放开。
  • 在Info.plist中配置游戏对应的MSDK_POP_URL、MSDK_GAME_ID 以及 MSDK_SDK_KEY。
3.1.2.2 ue 接入 Policy:

接入步骤:

  • 在Info.plist中配置游戏对应的MSDK_POP_URL、MSDK_GAME_ID 以及 MSDK_SDK_KEY。
  • 在IOSAppDelegate的分类中,仿照IOSAppDelegate+MSDK.mm的逻辑进行相关生命周期的拦截与处理:

  • hook以下关键生命周期方法:
    application:didFinishLaunchingWithOptions:
    application:openURL:options:
    application:openURL:sourceApplication:annotation:
    application:continueUserActivity:restorationHandler:

    调用showPolicyPopup方法,并在用户完成授权的回调中,通过反射回调主类 IOSAppDelegate 的对应生命周期方法,例如:

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    showPolicyPopup(^(id param){
        dispatch_async(dispatch_get_main_queue(), ^() {
        //这里反射调用主类的对应方法,注意传参中的函数名要与当前所在函数名一致
            invokeOriginalMethod([IOSAppDelegate class], @"application:didFinishLaunchingWithOptions:", ^(IMP imp, SEL sel){
                ((BOOL(*)(id, SEL, id, id))imp)(self, sel, application, launchOptions);
            });
            if (!isPolicyProcessed) {
                invokeOriginalMethod([IOSAppDelegate class], @"applicationDidBecomeActive:", ^(IMP imp, SEL sel){
                    ((void(*)(id, SEL, id))imp)(self, sel, application);
                });
                isPolicyProcessed = true;
            }
        });
    });
     return YES;
}

2.反射的方式可仿照以下函数实现

static void invokeOriginalMethod(Class clazz, NSString* methodName, void(^callback)(IMP imp, SEL sel)) {
    if (clazz) {
        unsigned int methodCount;
        Method *methodList = class_copyMethodList(clazz, &methodCount);
        IMP currentImp = NULL;
        SEL currentSel = NULL;
        int index = 0;
        for (NSInteger i = 0; i < methodCount; i++) {
            Method method = methodList[i];
            NSString *currentMethodName = [NSString stringWithCString:sel_getName(method_getName(method))
                                            encoding:NSUTF8StringEncoding];
            if ([methodName isEqualToString:currentMethodName]) {
                currentImp = method_getImplementation(method);
                if (currentImp != NULL && index != 0) {
                    currentSel = method_getName(method);
                    callback(currentImp, currentSel);
                    break;
                }
                index++;
            }
        }
        free(methodList);
    }
}

3.在下列生命周期方法中拦截业务的处理逻辑:
applicationDidBecomeActive
applicationWillResignActive
applicationDidEnterBackground
applicationWillEnterForeground
applicationWillTerminate

例如:

- (void)applicationWillResignActive:(UIApplication*)application
{
    if (isPolicyProcessed) {
        invokeOriginalMethod([IOSAppDelegate class], @"applicationWillResignActive:", ^(IMP imp, SEL sel){
            ((void(*)(id, SEL, id))imp)(self, sel, application);
        });
    }
}

注意:需要配置编译规则,保证处理后的分类在IOSAppDelegate所有分类中最后一个参与编译。如果只有当前这一个分类,则无需保证其编译的先后顺序。
如下图中,在 xcode 中将 IOSAppDelegate+MSDK.mm 放在列表最后

3.1.3 自定义 Policy 内容

3.1.3.1 调整协议内容

如需替换 Policy 展示的文字内容,则可按下面步骤处理:

1、自行开发一个 离线 HTML 用于展示 Policy 内容,然后将其放在 assets 目录下
2、调整 MSDK Core 包下的 ThirdSDK/MSDKPopupResource.bundle 目录下 MSDKPopupLocal.config 配置文件
文件格式:JSON
调整文件:将 scene = MSDKPolicy 节点为中的 uri 指向需要替换的业务自定义的 HTML 文件路径
调整文件签名:计算自定义的 HTML 文件的 MD5 值,将其填入到 sig 字段中
注意: 这里的路径是从 MSDKPopupResource.bundle 的根目录开始,如 HTML 文件放在根目录下,则 uri 为 MSDKPopup{scene_type}{poptype}{contenttype}{sig}.html,其中{scene_type}、{pop_type}、{content_type}、{sig}分别替换为config 配置文件中对应弹窗配置的值scene_type、pop_type、content_type、sig。
至此,就完成了policy内容的替换,完成后的配置参考如下:

另外,MSDK 支持在管理端上配置并下发(请联系我们开发和确认)

3.1.4 其他注意事项

以上修改针对绝大部分游戏的使用,请注意若游戏对ViewController进行了自定义修改,并且在ViewController加载时进行了某些功能的初始化,需要考虑在调用showPolicy接口的同时拦截ViewController的加载,以达到用户点击同意前不进行相应功能的初始化,可考虑参考以下代码:

UIViewController* rootViewController = self.window.rootViewController;
UIViewController* tempViewController = [[UIViewController alloc] init];
self.window.rootViewController = tempViewController;
[MSDKPolicy showPolicy:launchOptions complete:^(id param) {
    self.window.rootViewController = rootViewController;
    NSLog(@"Show Policy complete, continue didFinishLaunchingWithOptions:%@", param);
}];

附录

MSDKPolicyDelegate示例代码:

MSDKPolicyDelegate.h:

#import <Foundation/Foundation.h>
#include "UnityAppController.h"

NS_ASSUME_NONNULL_BEGIN

@interface MSDKPolicyDelegate : UnityAppController

@end

NS_ASSUME_NONNULL_END

MSDKPolicyDelegate.mm:

#import "MSDKPolicyDelegate.h"

typedef void(^MSDKPolicyLifecycleEventCallback)(void);
static NSMutableArray<MSDKPolicyLifecycleEventCallback>* events;
static NSCondition *eventsLock;
static bool isPolicyProcessed = false;

static void addLifecycleEvent(void(^callback)()) {
    [eventsLock lock];
    [events addObject:callback];
    [eventsLock unlock];
}

static void invokeLifecycleEvent() {
    if (events) {
        NSUInteger count = [events count];
        if (count > 0 && eventsLock) {
            [eventsLock lock];
            for (MSDKPolicyLifecycleEventCallback callback in events) {
                callback();
            }
            [events removeAllObjects];
            [eventsLock unlock];
        }
    }
}

static void showPolicyPopup(void(^callback)(id param))
{
  Class policyClass = NSClassFromString(@"MSDKPolicy");
  if (policyClass) {
      SEL selector = NSSelectorFromString(@"showPolicy:complete:");
      if (selector) {
          NSMethodSignature *signature = [policyClass methodSignatureForSelector:selector];
          if (signature) {
              NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
              invocation.target = policyClass;
              invocation.selector = selector;
              void(^policyCallback)(id) = ^(id param) {
                  if (callback) {
                    callback(param);
                  }
              };
              id firstParams = nil;
              [invocation setArgument:&firstParams atIndex:2];
              [invocation setArgument:&policyCallback atIndex:3];
              [invocation invoke];
              return;
          }
      }
  }
  if (callback) {
    callback(nil);
  }
}

@implementation MSDKPolicyDelegate

+(void)load {
    eventsLock = [[NSCondition alloc] init];
    events = [NSMutableArray new];
}

- (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    return YES;
}

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    showPolicyPopup(^(id param){
        dispatch_async(dispatch_get_main_queue(), ^() {
            [super application:application willFinishLaunchingWithOptions:launchOptions];
            [super application:application didFinishLaunchingWithOptions:launchOptions];
            invokeLifecycleEvent();
            isPolicyProcessed = true;
        });
    });
     return YES;
}

- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation
{
    if (isPolicyProcessed) {
        return [super application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
    } else {
        addLifecycleEvent(^() {
            [super application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
        });
    }
    return YES;
}

// 这里加宏的原因:由于unity5.6.4版本的UnityAppController没有application:openURL:options:方法,因此需要隔离。
// 注意:unity5.x系列版本,业务需要自行确认,并添加宏来隔离。
#ifndef UNITY_5_6_4

- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url options:(NSDictionary<NSString*, id>*)options
{
    if (isPolicyProcessed) {
        return [super application:application openURL:url options:options];
    } else {
        addLifecycleEvent(^() {
            [super application:application openURL:url options:options];
        });
    }
    return YES;
}

#endif

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler
{
    if (isPolicyProcessed) {
        return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
    } else {
        addLifecycleEvent(^() {
            [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
        });
    }
    return YES;
}

- (void)applicationDidBecomeActive:(UIApplication*)application
{
    if (isPolicyProcessed) {
        [super applicationDidBecomeActive:application];
    } else {
        addLifecycleEvent(^() {
            [super applicationDidBecomeActive:application];
        });
    }
}

- (void)applicationWillResignActive:(UIApplication*)application
{
    if (isPolicyProcessed) {
        [super applicationWillResignActive:application];
    }
}

- (void)applicationDidEnterBackground:(UIApplication*)application
{
    if (isPolicyProcessed) {
        [super applicationDidEnterBackground:application];
    }
}

- (void)applicationWillEnterForeground:(UIApplication*)application
{
    if (isPolicyProcessed) {
        [super applicationWillEnterForeground:application];
    }
}

- (void)applicationWillTerminate:(UIApplication*)application
{
    if (isPolicyProcessed) {
        [super applicationWillTerminate:application];
    }
}

@end



Copyright © 2024 MSDK.
All rights reserved.

results matching ""

    No results matching ""