Current Path:Home » Documents » API Docs » The text

Coraool Android SDK 开发指南

1. 集成流程

1.1 引入SDK

把sdk拷贝到应用的 libs 目录下,在对应模块的 build.gradle 中添加aar包的依赖

implementation files("libs/coraool-android-sdk-1.1.2-beta.aar")
VersionDateRelease NotesSDK
202402201. bp兜底数据更新
2. 正式版本1.0.8
coraool-android-sdk-1.0.8.aar_ 下载
202402281.新增CoraoolJsBridge支持H5调Native埋点
2.规范Tracking数据字典
coraool-android-sdk-1.0.9-beta.aar_ 下载
202404131.增加pageId跟踪
2.Bugfix埋点参数保存的字段位置
3.jsbridge兼容eventType值的大小写
4.jsbridge获取埋点验证信息
coraool-android-sdk-1.1.1-beta.aar_ 下载
202405051.增加zip请求压缩,减少网络流量coraool-android-sdk-1.1.2-beta.aar_ 下载

1.2 配置SDK

1.2.1 权限授予

权限权限用途
ACCESS_NETWORK_STATE检测联网方式,在网络异常状态下避免数据发送,节省流量和电量。
INTERNET允许应用程序联网和发送统计数据的权限,以便提供统计分析服务。
ACCESS_FINE_LOCATION(可选)通过获取位置信息,为开发者提供反作弊功能,剔除作弊设备;同时校正用户的地域分布数据,使报表数据更加准确。
ACCESS_COARSE_LOCATION(可选)通过获取位置信息,为开发者提供反作弊功能,剔除作弊设备;同时校正用户的地域分布数据,使报表数据更加准确。
<uses-sdk android:minSdkVersion="8"></uses-sdk>

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

1.2.2 配置混淆

如果您的应用使用了代码混淆,请添加如下配置,避免SDK被错误混淆

-keep class com.coraool.** { *; }
-keepclassmembers class * {
   public <init> (org.json.JSONObject);
}

1.3 初始化步骤

1.3.1 申请AK

每个app接入前,需要申请 AppId、AppKey、AppSecret

1.3.2 SDK初始化

SDK对外提供的接口类名为 com.coraool.CoraoolLib,所有的方法都通过这个类对外提供服务。

首先在Application的onCreate方法中,使用Coraool分配的Token对SDK进行初始化:

/**
 * Call this method to initialize Coraool SDK when launch the application.
 *
 * @param application   application object
 * @param paramProvider interface
 */
public void initialize(Application application, IParamProvider paramProvider);

初始化化示例:

public class CApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        CoraoolLib.getInstance().initialize(this, new IParamProvider() {
            @Override
            public String getAppId() {
                return "appId";    // 目前传测试值,后续在coraool申请
            }

            @Override
            public String getAppKey() {
                return "appKey";    // 目前传测试值,后续在coraool申请
            }

            @Override
            public String getAppSec() {
                return "appSec";    // 目前传测试值,后续在coraool申请
            }

            @Override
            public boolean isEnableLog() {
                return true;=
            }
        });
        CoraoolLib.getInstance().setPageCollectionMode(CoraoolLibConst.PageMode.Auto);
    }
}

1.4其他API

1.4.1登录用户ID

用户登录、退出的时候,需要实时更新登录信息

/**
 * Call this api to update the login account whenever login, logout or switch account.
 *
 * @param userId login user id
 */
public void setUserId(String userId);

1.4.2 日志开关

开发阶段建议打开日志开关,方便检查SDK的关键流程,以及打点日志的内容。线上发布前请关闭此开关,避免日志打印影响到APP性能

/**
 * Turn on this switch in DEBUG mode to trace sdk logs in different levels.
 * Turn it off before publishing app to ensure better performance in production situation.
 *
 * @param enableLog switch
 */
public void setEnableLog(boolean enableLog);

1.4.3 严格模式

开发阶段的debug模式建议打开,用于检查各类埋点参数使用是否合理。线上发布前请关闭,保证App的稳定性。

/**
 * Enable [StrictMode] will check all api criteria while running and throw errors if wrong.
 * It is recommended to turn on strict mode in DEBUG mode and turn it off before publish.
 * This will make sure all usages are correct and make the data engineering easier.
 *
 * @param strictMode default false
 */
public void setStrictMode(boolean strictMode);

2. 埋点接口

2.1 Tracking埋点

2.2.1 埋点提交

接口定义

public TrackBuilder(String eventName);

public void track(Map<String, Object> eventData);

参数说明:

参数解释说明
logMap埋点数据Map通过TrackBuilder子类构建的埋点数据,不建议手动构造,容易出错

2.1.2 控制埋点

TrackBuilder builder = new TrackControlBuilder(String eventName)
                            .setProperties(Map<String, Object> properties)
                            .setProperty("业务参数key1", 2.01)
                            .setProperty("业务参数key2", "业务参数value2");
CoraoolLib.getInstance().track(builder.build());
字段类型是否必须解释说明
eventNamestring事件名称标识用户在页面上操作的位置
propertiesMap<String, Object>事件参数和当前事件关联的业务参数,用于离线分析
property_keystring事件参数key和当前事件关联的业务参数,用于离线分析
property_valuestring事件参数value和当前事件关联的业务参数,用于离线分析

2.1.3 曝光埋点

TrackBuilder builder = new TrackExposeBuilder(String eventName)
                            .setProperties(Map<String, Object> properties)
                            .setProperty("业务参数key1", "业务参数value1")
                            .setProperty("业务参数key2", "业务参数value2");
CoraoolLib.getInstance().track(builder.build());
字段类型是否必须解释说明
eventNamestring事件名称标识用户在页面上操作的位置
propertiesMap<String, Object>事件参数和当前事件关联的业务参数,用于离线分析
property_keystring事件参数key和当前事件关联的业务参数,用于离线分析
property_valuestring事件参数value和当前事件关联的业务参数,用于离线分析

2.1.4 自定义埋点

TrackBuilder builder = new TrackCustomBuilder(String eventName)
                            .setPageName(String pageName);
                            .setProperties(Map<String, Object> properties)
                            .setProperty("业务参数key1", "业务参数value1")
                            .setProperty("业务参数key2", "业务参数value2");
Coraool.getInstance().track(builder.build());
字段类型是否必须解释说明
eventNamestring事件名称标识用户在页面上操作的位置
eventPagestring事件关联页面自定义埋点也必须指定关联页面参数
propertiesMap<String, Object>事件参数和当前事件关联的业务参数,用于离线分析
property_keystring业务参数key和当前事件关联的业务参数,用于离线分析
property_valuestring业务参数value和当前事件关联的业务参数,用于离线分析

2.1.5 页面埋点

2.1.5.1 事件时序

https://corp.coraoolstatic.com/wp-content/uploads/2024/03/时间时序-1024x630.png

2.1.5.2 采集模式

接口定义

public void setPageCollectionMode(int mode);

参数说明:

参数解释说明
mode页面事件的采集方式CoraoolLibConst.PageMode.Manual 手动(默认方式)CoraoolLibConst.PageMode.AUTO 自动

示例代码

// 自动采集选择:仅支持采集activity,在使用AUTO模式时,可以叠加手动模式,实现方式看#自动埋点API
CoraoolLib.getInstance().setPageCollectionMode(CoraoolLibConst.PageMode.AUTO);

//手动采集选择:支持activity和非activity,默认手动
CoraoolLib.getInstance().setPageCollectionMode(CoraoolLibConst.PageMode.Manual);

2.1.5.3 自动埋点

接口定义

1. 增加页面事件的埋点参数

public void updatePageProperties(Object pageObject, Map<String, Object> props);

2. 自定义页面名称

public void updatePageName(Object pageObject, String name);

3. 跳过当前页面的埋点

public void skipPage(Object pageObject);

参数说明:

方法名称参数解释说明
updatePagePropertiespageObject页面Activity对象
props更新参数只应用于当前页面
updatePageNamepageObject页面Activity对象
name自定义页面名称SDK默认取pageObject的类名,e.g. getSimpleName()
skipPagepageObject页面Activity对象在自动模式下跳过指定页面的页面埋点

示例代码

/**
 * 添加页面事件的参数,仅对当前页面实例生效
 * 页面参数保存在内存Map中,key为页面对象的simpleName+hashCode
 */
CoraoolLib.getInstance().updatePageProperties(Object pageObject, Map<String, Object> props);

/**
 * 默认的页面名称是Class.SimpleName,可以自定义页面名称
 */
CoraoolLib.getInstance().updatePageName(Object pageObject, String name);

/**
 * 个别页面不需要打点的,在Activity.onCreate()函数关掉当前页面打点,而不会影响全局的打点
 * 当前页面关闭自动模式后,仍然可以用下面的手动模式进行打点
 */
CoraoolLib.getInstance().skipTrackPage(Object pageObject)

2.1.5.4 手动埋点

接口定义

1. 页面展现的时候调用(onResume)

public void trackPageStart(Object pageObject, String pageName);

2. 页面退出的时候调用(onPause)

public void trackPageEnd(Object pageObject, String pageName);

参数说明:

方法名称参数解释说明
trackPageStartpageObject页面Activity对象
pageName自定义页面名称
trackPageEndpageObject页面Activity对象
pageName自定义页面名称
/**
 * 页面展现的时候调用(onResume)
 */
protected void onResume() {
    super.onResume();
    CoraoolLib.getInstance().trackPageStart(homeActivity, String pageName);
}

/**
 * 页面退出的时候调用(onPause)
 */
protected void onPause() {
    super.onPause();
    CoraoolLib.getInstance().trackPageEnd(Object pageObjet, String pageName);
}

2.2 GTX埋点协议

2.2.1 GTP/GTC流量追踪

GTX埋点用于页面流量的追踪和分析,可以解决如下关键业务问题:

  • 统计指定页面的PV、UV等基础指标;
  • 追踪页面流量的来源和去向,分析用户路径的流量漏斗;
  • 基于流量和转化评估,告知业务方,每个页面以及页面内坑位的流量效率;

  名词解释:

  • Global Tracking Position(GTP):全局位置跟踪模型,用于追踪位置以及不同位置之间流量的流转;
  • Global Tracking Content(GTC):全局内容跟踪模型,用于跟踪投放内容以及引导转化效率;
  • Global Tracking X (GTX):指代 GTP、GTC 构成的解决方案;

2.2.1.1 GTP参数定义

GTP参数定义:a.b.c.d = ${appId}.${pageId}.${module}.${point},GTP需要严格按照如下规范进行构造和使用,UI层通过结构化的数据构造这4层结构(建议用这种方式),也可以通过手动的方式构造。

GTP含义说明
a位${appId}不同端独立分配,对应SDK初始化参数的appId,全局唯一
b位${pageId}由产品指定并申请,在当前 ${app} 内唯一,b位由接入方维护,通过文档或者系统登记
c位${moduleId}页面的楼层或者区块编号,在当前页面 ${pageId} 内唯一
d位${pointId}楼层内细分的点位编号,在当前模块 ${moduleId} 内唯一

2.2.1.2 GTC参数定义

GTC参数定义:a.b.c.d = ${sysId}.${algoId}.${algoVer}.${audienceId},GTC也包含4层结构,在不同层级上的定义

GTC含义说明
a位${sysId}投放系统 ID,用来标识不同的内容投放方
b位${algoId}投放算法 ID,用来标识投放系统产生不同内容的投放算法
c位${algoVer}投放算法版本 ID,用来标识投放算法的不同版本
d位${audienceId}投放人群 ID,用来标识不同的投放人群

2.2.1.3 原理介绍

按照上面对于GTP和GTC的定义,假设有如下的场景:在某个APP内有三个页面,为了追踪三个页面的流量漏斗和投放引导效果,我们对页面的点击热区按GTP的模型进行编码:

https://corp.coraoolstatic.com/wp-content/uploads/2024/03/原理介绍-1024x243.png
  • Home页面红色区块编码:
    • gtp=a1024.home.top.0:a1024为APP的唯一编码、home为首页名称、top为顶部banner楼层、0表示首图
    • gtc=dly.alg1.v2.r30:dly表示投放系统编码、alg1表示算法1、v2表示算法版本、r30表示新注册30天内用户
  • List页面区块编码:
    • gtp=a1024.list.hot.2:a1024为APP的唯一编码、list为列表页名称、top为热销楼层、2表示第2个资源位
    • gtc=dly.alg2.v1.r30:dly表示投放系统编码、alg2表示算法2、v1表示算法版本、r30表示新注册30天内用户

用户进到Home页面后,点击红色区域跳转到List页,为了让List页面能够统计到当前跳转来源是Home页,就需要Home页在跳转过程,通过gtp和gtc参数把来源信息传给List页,List页在接受到Home页传过来的gtp和gtc参数后,会自动把这两个参数解析出来,并记录到页面事件内。List页面跳转到Detail页面同理。

参数传递的方式需要事先约定好协议,对于Android系统的Activity有两种传参数方式:

  • 方式1:通过Intent#putExtra(key, value) 传递,适用于前端硬编码的组件;
  • 方式2:通过Intent#setData(Uri) 传递,适用于后端下发投放链接;

CoraoolSDK同时这两种传参方式,两者冲突时,从优先级上对比,优先取 Intent#putExtra(key, value),因为我们认为这种方式是接入方充分理解了字段含义和用法后,有意这么设置的;

2.2.2 埋点接入

埋点接入需要理解几个关键的时机:

  • 页面onResume时:需要通过 updatePageProperties 接口把当前页的GTP编码添加到页面事件中,页面事件上报后,就可以知道当前页在GTP协议下的名称。因为标识的当前页面,所以gtp只需要包含a位和b位,c/d位可以置为0或者不传(例:a1024.home.0.0 或 a1024.home)
  • 跳转前准备参数:跳转前需要把点击位置的编码,按GTP和GTC的协议添加到 Intent#Extra 或 Intent#Data 内,以便传给下一个页面;

2.2.2.1 Activity传参

  • 通过Intent#putExtra
/** 
 * 备注:接入方的App通过定义接口 IPageTrack,可以规范各个页面获取名称的方式
 */
public class HomePageActivity extends AppCompatActivity implements IPageTrack {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_track_kwd_page);
        
        findViewById(R.id.goto_page_srp).setOnClickListener(v -> {
            // 跳转前:通过Extra的方式传参  
            Intent intent = new Intent(HomePageActivity.this, ListPageActivity.class);
            intent.putExtra(CoraoolLibConst.KEY_GTP, String.format("a1024.%s.1.0", getPageName()));
            intent.putExtra(CoraoolLibConst.KEY_GTC, "dly.alg1.v2.r30");
            HomePageActivity.this.startActivity(intent);
        });
    
    @Override
    protected void onResume() {
        super.onResume();
        // 页面启动时:把当前页面的 gtp 编码记录到当前页的页面事件中
        CoraoolLib.getInstance().updatePageProperties(this, new HashMap<String, Object>() {{
            put(CoraoolLibConst.KEY_GTP, String.format("a1024.%s.0.0", getPageName()));
        }});
    }
    
    @Override
    public String getPageName() {
        return "home"
    }
}
  • 通过Intent#setData
/** 
 * 备注:接入方的App通过定义接口 IPageTrack,可以规范各个页面获取名称的方式
 */
public class HomePageActivity extends AppCompatActivity implements IPageTrack {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_track_kwd_page);
        
        findViewById(R.id.goto_page_srp).setOnClickListener(v -> {
            // 跳转前:通过Uri的方式传参            
            Uri uri = Uri.parse("protocol://your.uri?k1=v1&k2=v2").buildUpon()
                    .appendQueryParameter(CoraoolLibConst.KEY_GTP, String.format("a1024.%s.1.0", getPageName()))
                    .appendQueryParameter(CoraoolLibConst.KEY_GTC, "dly.alg1.v2.r30")
                    .build();
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            startActivity(intent);
        });
    
    @Override
    protected void onResume() {
        super.onResume();
        // 页面启动时:把当前页面的 gtp 编码记录到当前页的页面事件中
        CoraoolLib.getInstance().updatePageProperties(this, new HashMap<String, Object>() {{
            put(CoraoolLibConst.KEY_GTP, String.format("a1024.%s.0.0", getPageName()));
        }});
    }
    
    @Override
    public String getPageName() {
        return "home"
    }
}

2.2.3 示例说明

2.2.3.1 前进后退

2.2.3.2 前后台切换

2.2.3.3 前后台切换

3. Invoke API接口

Coraool SDK提供了基于API的数据服务,并且定义了一套通用且对调用方非常友好的接口,通过Coraool SDK发起 Invoke 接口调用通常包含以下4个步骤:

3.1 构造Response

com.coraool.CoraoolResponse是对Coraool API协议的封装,包含了3个字段,如果不关心请求的结果,那么直接这个接收请求结果即可;如果有业务数据需要处理,则需要继承这个类,并增加 result 字段,并提供getter和setter方法,按照Coraool API协议的约定,API调用返回的业务参数会保存在这个Map结构内。SDK会自动进行反序列化,方便应用层直接使用。

参数说明:

参数类型解释说明
successboolean本次请求是否成功当且仅当这个字段为true时,返回结果才有效
codeint错误码200表示成功,其他值类似http状态的定义,如果返回负数表示SDK自己发生了异常,具体的错误码由CoraoolResponse的常量进行定义
messagestring请求结果的文本描述仅仅用于协助排查请求过程,不能用于业务逻辑的判断。在请求成功的状态下总是返回 "SUCCESS",如果请求出现错误或者异常,则返回对应的错误描述信息,可以根据错误描述信息进行排查或者反馈给技术支持;
resultany自定义的业务数据类型如果需要关注请求的结果,按照Coraool API的协议约定,数据会保存在以 result 为Key的JSON对象内

示例代码:

public class CoraoolRankingResponse extends CoraoolResponse {

    // RankingData表示具体的业务数据
    public RankingData result;
    
    public RankingData getResult() {
        return result;
    }
    
    public void setResult(RankingData result) {
        this.result = result;
    }
}

public class RankingData {
    public JSONArray ranking;
    public JSONObject track;
    public String version;
}

3.2 构造Request

通过创建 CoraoolRequest 类型的对象,构造符合 Coraool API 协议的请求参数

参数说明:

参数类型解释说明
apiNamestring接口名称在Coraool open API 上注册的业务接口,例如open.coraool.bingoplus.home.game.ranking
apiVersionstring接口版本在Coraool open API 上注册的业务版本,例如1.0.0
dataany请求参数具体的请求参数对象
connectTimeoutint链接超时,单位毫秒网络连接超时
readTimeoutint读超时,单位毫秒返回数据流读取超时
callbackOnMainThreadboolean回调主线程的开关是否回调到UI主线程,默认是true,对于非直接显示的数据,建议设置为false,让请求回调到后台线程,方便对数据进行二次处理

示例代码:

CoraoolRequest request = new CoraoolRequest();
request.setApiName("open.coraool.bingoplus.home.game.ranking");
request.setApiVersion("1.0.0");
request.setCallbackOnMainThread(true);
request.setReadTimeout(3 * 1000);
request.setData(new HashMap<String, Object>(){{
    put("userId", "登录用户ID");
    put("afId", "appsFlyer的ID");
    put("deviceId", "设备ID");
}});

3.3 发起请求

SDK的请求API分为同步请求和异步请求,统一封装在 CoraoolLib 的接口类下

3.3.1 同步请求

接口定义:

/**
 * Send a coraool request synchronously.
 *
 * @param clz Response class type
 * @param request request complies with coraool api protocol
 * @return response complies with coraool api protocol
 * @param <T> { body }
 */
public <T extends CoraoolResponse> T syncInvoke(Class<T> clz, 
                                                CoraoolRequest request)

/**
 * Send a request synchronously. If the callback interface is provided,
 * the interface will be invoked before this method returns.
 *
 * @param clz Response class type
 * @param request request complies with coraool api protocol
 * @param callback callback when done
 * @return response complies with coraool api protocol
 * @param <T> { body }
 */
public <T extends CoraoolResponse> T syncInvoke(Class<T> clz, 
                                                CoraoolRequest request, 
                                                CoraoolNetCallback<T> callback)

参数说明:

参数名类型解释说明
clzClass<T>返回对象类的Class对应第一步Reponse对象类的Class
requestCoraoolRequest请求参数对应第二步请求参数对象
callbackCoraoolNetCallback<T>回调Callback如果提供了Callback接口参数,则本次请求总是会回调到Callback接口中的一个方法,接口方法的定义看下一步

返回值说明:

返回值类型解释说明
response<T extends CoraoolResponse>返回对象类对应第一步Reponse的对象实例,仅当对象无法被反射实例化的时候,才会出现null,所以一定要保留无参构造器

示例说明:

private void syncRequest() {
    CoraoolRequest request = new CoraoolRequest();
    request.setApiName("open.coraool.bingoplus.home.game.ranking");
    request.setApiVersion("1.0.0");
    request.setData(new HashMap<String, Object>(){{
        put("userId", "123");
        put("afId", "afIdx");
        put("deviceId", "devicedevice");
    }});
    
    CoraoolRankingResponse response = CoraoolLib.getInstance()
            .syncInvoke(CoraoolRankingResponse.class, request);
}

3.3.2 异步请求

接口定义:

/**
 * Send a request asynchronously. If the callback interface is provided,
 * the interface will be invoked before this method returns.
 *
 * @param clz Response class type
 * @param request request complies with coraool api protocol
 * @param <T> { body }
 */
public <T extends CoraoolResponse> void asyncInvoke(Class<T> clz,
                                                    CoraoolRequest request)
                                              
/**
 * Send a request asynchronously. If the callback interface is provided,
 * the interface will be invoked before this method returns.
 *
 * @param clz Response class type
 * @param request request complies with coraool api protocol
 * @param callback callback when done
 * @param <T> { body }
 */
public <T extends CoraoolResponse> void asyncInvoke(Class<T> clz, 
                                                    CoraoolRequest request, 
                                                    CoraoolNetCallback<T> callback)

参数说明:

参数名类型解释说明
clzClass<T>返回对象类的Class对应第一步Reponse对象类的Class
requestCoraoolRequest请求参数对应第二步请求参数对象
callbackCoraoolNetCallback<T>回调Callback如果提供了Callback接口参数,则本次请求总是会回调到Callback接口中的一个方法,接口方法的定义看下一步

示例说明:

private void asyncRequest() {
    CoraoolRequest request = new CoraoolRequest();
    request.setApiName("open.coraool.bingoplus.home.game.ranking");
    request.setApiVersion("1.0.0");
    request.setCallbackOnMainThread(true);
    request.setReadTimeout(5 * 1000);
    request.setData(new HashMap<String, Object>(){{
        put("userId", "123");
        put("afId", "afIdx");
        put("deviceId", "devicedevice");
    }});
    
    CoraoolLib.getInstance().asyncInvoke(CoraoolRankingResponse.class, request, new CoraoolNetCallback<CoraoolRankingResponse>() {
        @Override
        public void onSuccess(CoraoolRankingResponse response) {
            Log.e("TAG", "Success: " + JSON.toJSONString(response));
        }

        @Override
        public void onFailed(int code, String message) {
            Log.e("TAG", String.format("Failed: code=%d, message=%s", code, message));
        }

        @Override
        public void onSystemError(int code, String message) {
            Log.e("TAG", String.format("Error: code=%d, message=%s", code, message));
        }
    });
}

3.4 接口回调

接口回调用于接收正确和异常的结果

接口参数类型解释说明
onSuccessresponseT返回对象的类型请求被正确处理的时候返回,对应 success=true
onFailedcodeint请求错误码对应success=false的场景,如果签名错误、无效token等
messagestring错误描述
onSystemErrorcodeint请求错误码各类系统错误,如网络未连接、授权错误、数据解析异常等
messagestring错误描述
public interface CoraoolNetCallback<T> {

    /**
     * Totally success including network and business
     *
     * @param response response body {@link CoraoolResponse}
     */
    void onSuccess(T response);

    /**
     * Send request successfully but the response data contains business error, like
     * invalid appKey, signature mismatch, params illegal and so on.
     *
     * @param code error reason code leverages http status definition
     * @param message plaintext explaining the error reason.
     */
    void onFailed(int code, String message);

    /**
     * Errors like network not connected, auth error, protocol error and so on
     *
     * @param code {@link CoraoolLibConst}
     * @param message plaintext explaining the error reason.
     */
    void onSystemError(int code, String message);
}
Not allow to forward/copy without permission:CORAOOL » Coraool Android SDK 开发指南
Share to
1 Likes

Recomm

Comments (0)

Contact us