Home - GrouthDownload/TDownloadSDK GitHub Wiki

腾讯下载SDK简介

  整合应用宝的下载、App洗包监控和App省流量升级能力,提供App分发相关的基础能力组件。

基础能力

目前该项目包括以下4项基础能力:

   1) SDK内下载: 基于哈雷下载SDK,提供App内稳定可靠的下载能力,而且本地下载器对APK下载场景进行深度定制,提供下载至安装全链路的数据报表服务。

   2) 应用宝下载: 基于应用宝APP已有的外部跳转协议(Tmast协议),提供跳转至应用宝APP内进行下载和数据回传的能力,以此输出应用宝在厂商攻防方面所具备的能力(应用宝是工信部认可的应用商店,相比其他App,有更高的安装成功率)。

   3) 洗包监控能力: 提供一套从下载至安装全链路上的Apk文件变化的监控服务,无论是下载过程中Apk被替换、下载完成后被替换、安装时被厂商安装器导流还是安装完成后被静默替换,该组件都能监控到,同时提供相应的报表服务,方便快捷分析各个厂商的拦截情况。

   4) 应用省流量升级: 输出应用宝已有的增量更新能力,为所有在应用宝已上架的APP提供省流量更新服务。

目录


1. 腾讯下载SDK使用前须知

  本文默认读者掌握与Android相关的基本IDE使用以及基础编程知识,以Android Studio为例,详细说明腾讯下载SDK的接入以及使用方法。

申请接入方标识

  使用SDK功能前,需要申请appKey和appSecret。appKey和appSecret用来标识接入方,识别接入方的身份,用于数据传输的校验,使SDK正常工作。 申请接入方标识过程如下:

  1) 解压release版本APK包,在META-INF文件夹中获取CERT.RSA文件(META-INF/CERT.RSA)。

  2) 使用签名计算工具sig.jar(附件提供)计算签名信息(jar -jar sig.jar CERT.RSA),截取计算后签名前半部分作为签名信息(前后以|分割)。image.png

  3) 将包名以及签名信息通过邮件发送 [email protected][email protected] ,后续会通过邮件的方式告知appKey和appSecret信息。  邮件主题注明:产品名 + 省流量更新SDK appKey/appSecret申请

SDK组件结构说明

组件名称 说明 aar文件
基础模块(base) sdk工具类/基础类 bssdk_base_*.aar
网络模块(network) 网络通信模块 bssdk_network_*.aar
统计模块(statistic) 统计模块 bssdk_statistic_*.aar
腾讯下载SDK模块(download) 下载模块 bssdk_download_*.aar
洗包监控模块(apk_replace_monitor) 洗包监控模块 bssdk_apk_replace_*.aar
外call模块(yyb_open) 跳应用宝下载的模块 bssdk_yyb_open_sdk_*.aar
省流量更新模块(patch_update) 省流量更新模块 bssdk_patch_update_*.aar
   腾讯下载SDK主要由四个功能模块构成,分别为:
    1)腾讯下载;
    2)跳应用宝下载;
    3)洗包监控;
    4)省流量更新;
  腾讯下载SDK以组件形式开发,由基础组件(base、network、statistic)和功能组件(download、yyb_open_sdk、apk_replace_monitor、patch_update)组成,使用腾讯下载SDK必须依赖基础组件,接入时可根据实际所需功能选择性接入功能组件,组合接入对应的aar架包。基础组件network模块中,提供了多种ABI(Application Binary Interface)的so文件,其中包括x86_64、armeabi-v7a、armeabi、armeabi-v8a,如不需要适配太多架构机型,可删减不需要的ABI架构下so文件。

2. 接入说明

Android Studio接入方可以跳过该文档,本页文档只适用于使用Eclipse的接入方。

添加混淆配置

  如果项目构建是按照Gradle脚本统一构建,未侵入修改默认的混淆Task,由于aar架包已包含所必需的混淆配置,无需再进行手动配置。如果对打包过程有特殊处理,接入方应根据接入的模块选择混淆配置,务必保证以下类不被混淆。

#网络通信模块
-keep class com.tencent.bs.network.sec.SecNative {*;}
-keep class com.tencent.bs.network.jce.** {*;}
-keep class com.qq.taf.jce.JceStruct {*;}
-keep public class com.tencent.bs.statistic.jce.** {*;}

-dontwarn android.os.SystemProperties
#哈雷下载模块
-keep public class com.tencent.bs.dl.jce.**{*;}
#opensdk模块
-keep public class com.tencent.assistant.sdk.remote.** {*;}
-keep class com.tencent.assistant.sdk.remote.SDKActionCallback {*;}
-keep public class com.tencent.bs.opensdk.aidl.** {*;}
-keep public class com.tencent.bs.statistic.jce.** {*;}
-keep public class com.tencent.bs.opensdk.jce.** {*;}
-keep public class com.tencent.bs.opensdk.ipc.jce.** {*;}
#洗包监控模块
-keep public class * extends com.qq.taf.jce.JceStruct{*;}
-keep public class com.qq.**{*;}
-keep public class * extends android.content.BroadcastReceiver
#省流量更新模块
-keep public class com.tencent.bs.update.jce.**{*;}

AndroidManifest文件配置

  所需权限以及Provider相关的配置已经包含在aar架包中,仅需在项目的build.gradle文件中配置包名参数。   image.png   PKG_NAME后面添加的包名是applicationId对应的包名,即接入Apk的包名,不要误填例子中的包名,否则在使用SDK时会出现问题。

  如果选择aar方式依赖腾讯下载SDK,本文剩余部分可以跳过。如果选择jar方式依赖,参考本文接下来部分完成AndroidManifest配置。

配置SDK所需权限

  SDK正常运行需要获得一些通用权限,详细列表如下:

<!-- 网络通信所需基础权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

<!--通用数据存储所需权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<!-- 通过SDK触发安装必备权限-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

安装适配

  注意:如果应用targetSdkVersion大于或等于24,即Android 7.0及以上系统,请仔细阅读此小节,否则可以跳过本节。   Android7.0及以上系统对应用间数据共享增加了限制,当App的targetSdkVersion>=24时,需要适配安装intent中的Uri数据。接入方需要在AndroidManifest文件内注册对应的fileprovider,否则SDK安装接口无法正常拉起应用安装界面。具体配置如下:

<provider
    android:name="com.tencent.bs.provider.FileProvider"
    android:authorities="${包名}.InstallFileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/forward_paths"/>
</provider>

  在android:authorities属性 ${包名} 为导入SDK包的Apk实际包名。

  在res资源目录下的xml资源文件夹中添加forward_paths.xml(路径:res/xml/forward_paths.xml),forward_paths.xml文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths>
        <files-path name="files_path" path="/"/>
        <external-path name="external_path" path="/" />
        <cache-path name="cache" path="/"/>
    </paths>
</resources>

  腾讯下载SDK基于halley下载器进行接口封装,提供基础的下载接口、下载任务持久化以及下载日志上报等服务。   下载SDK的使用分为三个步骤,分别为初始化、接口使用和注销。各模块执行时机,建议如下: image.png


3. 下载SDK接入

添加maven仓库

maven {
   url "http://maven.oa.com/nexus/content/repositories/thirdparty"
}

在模块中添加依赖

   implementation "com.tencent.basicservices:download:1.0.2-release102-WithJce-HideHalley-4001"

初始化

  下载SDK使用前,需要进行初始化。初始化时需要将App的Context(SDK内部会主动获取ApplicationContext)以及将appKey和appSecret传递给SDK,具体示例代码如下:

private String mAppKey    = "YOUR APPKEY";
private String mAppSecret = "YOUR APPSECRET";

private void initDownloadSDK(){
    Map<String, String> initParamMap = new HashMap<>();
    initParamMap.put(InitParam.APP_KEY, mAppKey);
    initParamMap.put(InitParam.APP_SECRET, mAppSecret);
    TDownloader.init(getApplicationContext(),initParamMap);
}

SDK普通下载接口的使用

  TDownloader提供下载相关的接口,通过TDownloader进行下载相关的操作。SDK下载具体使用操作包括注册下载监听器和调用下载接口,本文将分小节详细介绍。

1 注册监听器

  下载过程需要监听下载任务的状态,因此需要注册一个回调监听器给SDK。SDK内部持有监听器的弱引用,因此使用方需要持有监听器的强引用,防止监听器被GC回收,导致无法接收到回调信息。   SDK的下载监听器TaskListener回调接口以及注册示例代码如下:

private TaskListener mDownloadStateListener = new TaskListener() {
    @Override
    public void onTaskStateChanged(DownloadInfo downloadInfo) {
        //任务状态变化
    }

    @Override
    public void onTaskProgressChanged(DownloadInfo downloadInfo) {
        //任务进度变化
    }

    @Override
    public void onInstalled(DownloadInfo downloadInfo) {
        //任务安装成功回调
    }
};

// 注册监听器
TDownloader.getDownloader().addTaskListener(mDownloadStateListener);

2 调用下载接口

  下载任务相关的操作包括:开始、暂停、删除、查询和安装。各接口使用示例代码如下:

DownloadInfo info = new DownloadInfo();
// 下载URL
info.downloadURL = "https://imtt.dd.qq.com/16891/ACBE213904903B19E431351FEA6E7207.apk";
// 下载Apk包名
info.packageName = "com.qiyi.video";

//开始下载任务
TDownloader.getDownloader().startDownload(info);

//暂停下载任务
TDownloader.getDownloader().pauseDownload(info.downloadURL);

//查询下载任务状态
DownloadInfo infoFromSDK = TDownloader.getDownloader().getDownloadTaskInfo(info.downloadURL);

//删除下载任务
TDownloader.getDownloader().deleteDownload(info.downloadURL);

//触发已完成任务的安装
TDownloader.getDownloader().install(info);

   注意: 示例中DownloadInfo只填写了url和包名参数,实际使用中应该根据业务场景的实际情况,完善DownloadInfo结构体的字段数据,在后续的统计日志内容也会更加完善。   DownloadInfo常用外部传入字段说明:

字段名称 含义
downloadURL 下载链接,必须填
packageName Apk任务包名信息
versionCode Apk任务版本号信息
fileSize 文件长度
fileMd5 文件md5
via 业务VIA,标识来源
fileType 文件类型
appId 数据源返回的appid
apkId 数据源返回的apkid
recommendId 数据源返回的recommendid
channelId 数据源返回的channelId
scene 外部自定场景信息
autoInstall 是否由SDK自动触发安装
pauseOnMobile 是否在移动网络下自动暂停
extraInfo 额外信息,预留给外部使用

3 注销

  应用关闭时需要调用注销接口,防止资源泄漏。具体调用代码如下:

@Override
protected void onDestroy() {
    super.onDestroy();
    TDownloader.getDownloader().destroy();
}

共享下载(接力下载)

SDK的1.0.3及以上版本开始支持共享下载能力,本小节主要介绍下载SDK模块内共享下载相关的接口和使用说明。

I.共享下载概念

当两个或者多个App同时接入该SDK时,可以通过共享下载任务的临时文件实现同一任务在不同时刻被不同的App触发继续下载,以此提高下载任务的完成率。整个下载过程是不同App之间接力完成下载,因此被称为接力下载能力或共享下载能力。 整个过程示意图如下: 共享下载示意图

II.共享下载相关接口使用

为了支持共享下载能力,SDK接口IDownloader内新增以下两个接口:

    /**
     * 开始接力下载
     */
    void startRelayDown();

    /**
     * 暂停所有接力下载任务
     */
    void pauseAllRelayTasks();
  • App在合适的时机(比如连上wifi时)调用startRelayDown,尝试触发接力下载任务的下载(SDK内部会分析是否有合适的接力下载任务可被下载);
  • 当希望暂停接力下载任务时(比如网络从wifi切到4G)调用pauseAllRelayTasks,实现接力下载任务的暂停操作。

注意:对于使用方而言只需要触发下载和暂停动作即可,不需要关系接力下载任务的具体细节,SDK内部会记录该任务的操作记录,跟随下载日志一起上报到后台,用于分析任务的下载过程。

另外,SDK的DownloadInfo内新增字段:isRelayDownloadTask,用于标记任务是否可被接力下载(只有可被接力下载的任务才可能会被其他接入该SDK的App拿到并触发下载):

    /**
     * 1-允许接力下载, 0-不允许
     */
    public int isRelayDownloadTask = 0;

使用方在新建下载任务时,可以通过主动把DownloadInfoisRelayDownloadTask字段置为1,从而将任务设置为可被接力下载的任务。

  洗包监控SDK提供相应的工具类自动将下载模块与洗包监控模块绑定,因此洗包监控SDK与下载SDK一起搭配接入使用更为合适。洗包监控SDK也可以单独接入。本文将对一起接入使用和单独接入两种情况分开说明。


4. 洗包监控SDK接入

添加maven仓库

maven {
   url "http://maven.oa.com/nexus/content/repositories/thirdparty"
}

在模块中添加依赖

   implementation "com.tencent.basicservices:apk_replace_monitor:1.0.2-release102-WithJce-HideHalley-4001"
   // 如需单独接入洗包监控模块,可去除下载SDK依赖语句
   implementation "com.tencent.basicservices:download:1.0.2-release102-WithJce-HideHalley-4001"

初始化

  洗包监控SDK使用前,需要进行初始化。初始化时需要将App的Context(SDK内部会主动获取ApplicationContext)以及将appKey和appSecret传递给SDK,具体示例代码如下:

private String mAppKey    = "SuperDownTestDemo2";
private String mAppSecret = "dd40b776992873be708858471220d5b3";

private void initReplaceMonitor() {
    Map<String, String> initMap = new HashMap<>();

    initMap.put(InitParam.APP_KEY, mAppKey);
    initMap.put(InitParam.APP_SECRET, mAppSecret);
    initMap.put(UpdateConst.INIT_PARAM.YYB_APK_CHANNEL_ID, "990483");
    initMap.put(UpdateConst.INIT_PARAM.HOST_APP_CHANNEL_ID, "000116083735373738333534");
    initMap.put(UpdateConst.INIT_PARAM.VIA, "ANDROIDQQ.YYB.SELFUPDATE");
    ReplaceMonitor.get().init(this, initMap);
}

与下载SDK一起接入

  与下载SDK一起接入时,只需使用洗包监控SDK提供的ReplaceMonitorBinder类将下载模块与洗包监控模块绑定即可,参考代码如下:

ReplaceMonitorBinder.getInstance().bindDownloader(ReplaceMonitor.get(), TDownloader.getDownloader());

单独接入洗包监控

  单独接入洗包监控时,需要在下载开始、下载完成、安装开始三个环节调用相应的接口触发洗包监控的任务执行,具体说明如下:

  ReplaceMonitor接口说明:

/**
 * 添加一个监控任务
 * @param task 任务对象
 * @return 返回添加成功时的任务id,如果失败则返回-1
 */
long addTask(MonitorTask task);
/**
 * 删除一个监控任务
 * @param task 任务对象
 */
void deleteTask(MonitorTask task);
/**
 * 通过任务id获取一个已添加的任务
 * @param taskId 任务ID
 * @return 任务ID对应的任务
 */
MonitorTask getTask(long taskId);
/**
 *  通过附加Id获取一个已添加任务,譬如将一个下任务的id作为additionalId,这样使用方不需要存储监控任务id
 * @param additionalId 附加id
 * @return
 */
MonitorTask getTask(String additionalId);
/**
 * 异步执行一个监控任务
 * @param task 任务对象
 * @param step 执行的步骤,可以为下载中DOWNLOADING,安装前BEFORE_INSTALL,安装中INSTALLING,安装后AFTER_INSTALL
 */
void execSync(MonitorTask task, MonitorStep step);
/**
 * 同步执行一个监控任务(暂不支持)
 * @param task 任务对象
 * @param step 执行的步骤,可以为下载中DOWNLOADING,安装前BEFORE_INSTALL,安装中INSTALLING,安装后AFTER_INSTALL
 * @return 监控结果
 */
MonitorResult execAsync(MonitorTask task, MonitorStep step);
/**
 * 注册一个监听Listener,用于监听监控结果
 * @param listener Listener对象
 */
void register(MonitorListener listener);

添加监控任务: 下载开始时,调用ReplaceMonitor.addTask方法将一个MonitorTask加入到洗包监控任务中

private MonitorTask createTask(DownloadInfo downloadInfo) {
    MonitorTask task = new MonitorTask();
    task.packageName = downloadInfo.packageName;
    task.versionCode = downloadInfo.versionCode;
    task.fileMd5 = downloadInfo.fileMd5;
    task.yybApkId = downloadInfo.apkId;
    task.yybAppId = downloadInfo.appId;
    task.downloadUrl = downloadInfo.downloadURL;
    task.additionalId = downloadInfo.getDownloadTicket();
    task.filePath = downloadInfo.fileAbsPath;
    mMonitor.addTask(task);
    return task;
}

MonitorTask字段说明:

/**
 * 任务的ID,监控任务添加成功后自动产生
 */
public long id;
/**
 * ★必须字段,apk文件的的包名
 */
public String packageName;
/**
 * ★必须字段,apk文件的的版本号
 */
public int versionCode;
/**
 * ★必须字段,apk文件的应用名称
 */
public String appName;
/**
 * ★必须字段,apk文件包大小
 */
public long fileSize;
/**
 * apk文件的MD5值(该值只有使用腾讯下载数据源时才会有)
 */
public String fileMd5;
/**
 * 监控应用apk文件中的渠道号
 */
public String cpChannelId;
/**
 * ★必须字段,apk文件的绝对存储路径,如果是下载中,则是下载完成时绝对路径
 */
public String filePath;
/**
 * ★必须字段,apk文件的下载地址
 */
public String downloadUrl;
/**
 *apk文件的对应的应用在应用宝的appId(该值只在使用腾讯下载数据源时才有效)
 */
public long yybAppId;
/**
 * apk文件的在应用宝的apkId(该值只在使用腾讯下载数据源时才有效)
 */
public long yybApkId;
/**
 * 安装路径,apk文件安装完成时会自动获取
 */
public String installDir;
/**
 * 文件的最后修改时间
 */
public long lastModifedTime;
/**
 * 接入方设置的ID,可以用于标识一个监控任务,比如下载任务的ID
 */
public String additionalId;
/**
 * 唯一跟踪ID,接入方传入,用于将下载安装日志与洗包监控日志关联起来
 */
public String traceId;
/**
 * 应用类型,添加任务时,洗包监控模块会自动从后台获取
 */
public int appType;
/**
 * 上一部执行的洗包监控步骤,洗包监控模块会自动记录
 */
public MonitorStep lastStep;

  由于同一个应用可能会重复下载,所以在调用addTask时先调用getTask方法能否查询获取到之前添加的监控任务,比如:

public void onTaskStart(DownloadInfo downloadInfo) {
    MonitorTask task = mMonitor.getTask(downloadInfo.getDownloadTicket());
    if (task == null) {
        createTask(downloadInfo);
    }
}

执行安装中洗包监控: 在下载完成时,执行如下调用即可

public void onTaskStateChanged(DownloadInfo downloadInfo) {
    if (downloadInfo.downloadState == DownloadConst.DownloadState.SUCC) {
        //先尝试从监控模块获取之前添加的任务
        MonitorTask task = mMonitor.getTask(downloadInfo.getDownloadTicket());
        if(task == null) {
            //如果没有则创建新任务
            task = createTask(downloadInfo);
        }
        if (task != null) {
            mMonitor.execSync(task, MonitorStep.DOWNLOADING);
        }
    }//下载任务被删除时,监控任务也应该删除
    else if(downloadInfo.downloadState == DownloadConst.DownloadState.DELETED) {
        MonitorTask task = mMonitor.getTask(downloadInfo.getDownloadTicket());
        if(task != null) {
            mMonitor.deleteTask(task);
        }
    }
}

执行安装前洗包监控: 安装开始时执行如下调用即可

public void onInstallStart(DownloadInfo downloadInfo) {
    if (downloadInfo != null) {
        MonitorTask task = mMonitor.getTask(downloadInfo.getDownloadTicket());
        //先尝试从监控模块获取之前添加的任务
        if(task == null) {
            //如果没有则创建新任务
            task = createTask(downloadInfo);
        }
        if (task != null) {
            mMonitor.execSync(task, MonitorStep.BEFORE_INSTALL);
        }
    }
}

安装中和安装后洗包监控会在监控模块内自动完成,无需模块自己调用

监听洗包监控结果

  调用ReplaceMonitor.register()方法注册一个MonitorListener监听器监听洗包监控结果,接入方如果只关注被洗包的结果时,则只需关注MonitorResult的resultCode为2时的结果。

   MonitorListener说明

public interface MonitorListener {
    /**
     * 洗包监控结果回调
     * @param task 监控的任务
     * @param result 监控的结果
     */
    void onMonitorFinish(MonitorTask task, MonitorResult result);
}

   MonitorResult字段说明

/**
 * 监控结果的监控步骤,下载中DOWNLOADING,安装前BEFORE_INSTALL,安装中INSTALLING,安装后AFTER_INSTALL
 */
public MonitorStep step;
/**
 * 监控结果码,0表示监控通过,1表示发现洗包
 */
public int resultCode;
/**
 * 监控结果信息
 */
public String resultMsg;
/**
 * 监控到洗包时,发现洗包的监控方式
 */
public MonitorType monitorType;
/**
 * 监控到洗包时,被洗后的包名
 */
public String replacedPkgName;
/**
 * 监控到洗包时,被洗后的包的版本号
 */
public long replacedVersionCode;
/**
 * 监控到洗包时,被洗后的文件大小
 */
public long replacedFileSize;
/**
 * 监控到洗包时,被洗后的渠道号(大多数时候为空,取决于能否读到相应的渠道号)
 */
public String replaceChannelId;
/**
 * 监控到洗包时,被洗后的文件MD5(只有通过MD5监控时才会赋值)
 */
public String replaceFileMD5;
/**
 * 监控到洗包时,被洗后的文件的LastModifyTime
 */
public long replaceTime;

  应用宝外call SDK主要提供基于应用宝客户端的App下载、Apk文件链接下载等服务。在安装应用宝的情况下,仅需要通过外call SDK的接口把需要下载的应用包名或者Apk文件的链接等信息传递给应用宝,即可在应用宝内实现App下载,使用应用宝提供的防厂商拦截等能力。   外call SDK的能力主要分为App下载和文件下载两类。不同的任务类型所需传递的下载信息不相同,将在本文接下来小节中单独介绍。


5. 外call SDK接入

添加maven仓库

maven {
   url "http://maven.oa.com/nexus/content/repositories/thirdparty"
}

在模块中添加依赖

   implementation "com.tencent.basicservices:yyb_open_sdk:1.0.2-release102-WithJce-HideHalley-4001"

初始化

  外call SDK使用前,需要进行初始化。初始化时需要将App的Context(SDK内部会主动获取ApplicationContext)以及将appKey和appSecret传递给SDK,初始化示例代码如下:

private String mAppKey    = "YOUR APPKEY";
private String mAppSecret = "YOUR APPSECRET";

private void initYYBOpenSDK() {
    Map<String, String> initParamMap = new HashMap<>();
    initParamMap.put(InitParam.APP_KEY, mAppKey);
    initParamMap.put(InitParam.APP_SECRET, mAppSecret);
    OpenSDK.init(getApplicationContext(), initParamMap);
}

注册监听器

  与下载SDK类似,外call SDK下载任务监听任务状态也需注册监听器。SDK内部只持有监听器的弱引用,因此使用方需持有监听器强引用,防止监听器被GC回收,导致无法接收回调信息。   外call SDK的监听器为YYBDownloadTaskListener,具体说明如下:

private YYBDownloadTaskListener mYYBCallbackListener = new YYBDownloadTaskListener() {
    @Override
    public void onTaskDownloadStateChanged(TaskInfo taskInfo) {
        //下载任务状态变化
    }

    @Override
    public void onTaskProgressChanged(TaskInfo taskInfo) {
        //下载任务进度变化
    }

    @Override
    public void onTaskInstallStateChanged(TaskInfo taskInfo, int installState) {
        //应用宝触发了任务的安装、应用宝完成了任务安装回调
    }

    @Override
    public void onQQDownloaderInvalid() {
 	//应用宝进程被杀回调(或者应用宝进程未能被唤起)
    }

    @Override
    public void onServiceFree() {
         //应用宝下载服务空闲回调
    }
};

App下载任务

  App下载任务是指:通过传递包名、版本号、渠道号等参数给应用宝,由应用宝拉取对应的App下载信息,最终实现下载。适用于App的数据源来自于应用宝的场景。   App下载任务必须传递包名、版本号信息,否则应用宝终端不会把任务进度回传给SDK,下载指定的渠道包的情况可以通过设置任务的渠道号参数实现。

App下载任务相关的接口包括开始下载、继续下载(新版本应用宝支持)、暂停下载和查询下载任务状态,各接口详细说明如下:

/**
 * 通过应用宝下载App。
 * 调用方需要提供:
 * 包名
 * 版本号(版本号与应用宝内数据源不一致会导致没有下载进度)
 *
 * @param taskInfo 下载参数
 * @return 返回码,参见{@link com.tencent.bs.opensdk.model.OpenSDKConst.TaskCode}
 */
int startAppDownload(TaskInfo taskInfo);

/**
 * 在不出现页面显示跳转的方式实现通过应用宝下载app,
 * 仅当{@link #isYYBSupportWithoutUIDownload()}反回true是可用。
 * (首次新建下载任务时,还是会有UI页面跳转)
 *
 * @param taskInfo
 * @return
 */
int continueAppDownload(TaskInfo taskInfo);

/**
 * 暂停应用宝App下载任务,通过包名和版本号定位唯一任务。
 *
 * @param pkgName     任务App包名
 * @param versionCode 任务App版本号
 */
void pauseAppDownload(String pkgName, int versionCode);

/**
 * 获取应用宝App下载任务信息,通过包名和版本号定位唯一任务。
 *
 * @param pkgName     任务App包名
 * @param versionCode 任务App版本号
 * @return 任务对应的状态
 */
TaskInfo getAppDownloadTaskInfoFromYYB(String pkgName, int versionCode);

使用示例代码如下:

TaskInfo info = new TaskInfo();
info.packageName = "ctrip.android.view";
info.versionCode = 1200;

//开始下载
OpenSDK.getYYBDownloader().startAppDownload(info);

//暂停下载
OpenSDK.getYYBDownloader().pauseAppDownload(info.packageName,info.versionCode);

//继续下载
if(OpenSDK.getYYBDownloader().isYYBSupportWithoutUIDownload()){
    OpenSDK.getYYBDownloader().continueFileDownload(info);
}

//查询下载任务进度
TaskInfo infoFromSDK = OpenSDK.getYYBDownloader()
        .getAppDownloadTaskInfoFromYYB(info.packageName, info.versionCode);

文件下载任务

  文件下载任务是指:传递下载任务的下载链接给应用宝APP,应用宝APP完成该任务的下载。此场景适用于Apk数据源为非应用宝数据源,通过Url定位下载资源的场景。在应用宝端内的体验与App下载任务相似。   文件下载任务必须把下载任务的下载链接传递给应用宝,否则无法实现下载。除此之外,建议使用方把Url对应的App信息,包括包名、版本号、iconURL以及App名称等信息传递给应用宝,提升在应用宝内的展示体验。

文件下载任务接口说明如下:

/**
 * 开始应用宝文件下载任务
 * 文件下载需要提供:
 * 文件url
 *
 * @param taskInfo 下载参数
 * @return 返回码
 */
int startFileDownload(TaskInfo taskInfo);

/**
 * 在不出现页面显示跳转的方式实现通过应用宝下载文件,
 * 仅当{@link #isYYBSupportWithoutUIDownload()}反回true是可用。
 * (首次新建下载任务时,还是会有UI页面跳转)
 *
 * @param taskInfo
 * @return
 */
int continueFileDownload(TaskInfo taskInfo);

/**
 * 暂停应用宝文件下载任务
 *
 * @param downloadUrl 下载任务URL
 */
void pauseFileDownload(String downloadUrl);

/**
 * 获取应用宝文件下载任务
 *
 * @param downloadUrl 下载任务URL
 * @return 任务对应的状态
 */
TaskInfo getFileDownloadTaskInfoFromYYB(String downloadUrl);

接口使用示例代码:

TaskInfo info = new TaskInfo();
info.downloadUrl = "https://imtt.dd.qq.com/16891/70FB315B53E55A58CC63DB2D686BDB71.apk";
info.via = "TEST_VIA";
info.packageName = "com.sdu.didi.psnger";
info.versionCode = 514;
info.fileType = "apk";
info.iconUrl = "https://pp.myapp.com/ma_icon/0/icon_12165022_1548378216/256";
info.appName = "滴滴";

//开始下载
OpenSDK.getYYBDownloader().startFileDownload(info);

//暂停下载
OpenSDK.getYYBDownloader().pauseFileDownload(info.downloadUrl);

//继续下载
if(OpenSDK.getYYBDownloader().isYYBSupportWithoutUIDownload()){
    OpenSDK.getYYBDownloader().continueFileDownload(info);
}
//查询下载任务进度
TaskInfo infoFromSDK = OpenSDK.getYYBDownloader()
        .getFileDownloadTaskInfoFromYYB(info.downloadUrl);

TaskInfo字段说明

  文件下载任务参数均通过TaskInfo结构体传递给SDK,外部传入字段中有些字段只对App下载任务有效,有些字段是只对文件下载任务有效,有些是公共字段,具体字段信息描述如下:

字段名 说明 是否对文件下载有效 是否对App下载有效
packageName 包名
appName 应用名
iconUrl 应用图标链接
appId appid
downloadUrl 下载链接
fileSuffix 文件后缀(比如apk)
via via参数
channelId 渠道号
recommendId 广告信息
extraInfo 预留字段

  应用宝省流量更新SDK提供应用更新升级服务:应用的新版本在应用宝开发平台上架之后,便可通过SDK获取到应用的升级信息。应用升级信息通常分为以下两类:     1) 普通更新: 下载新版本应用的全量Apk文件,完成升级;     2) 增量更新: 下载新版本应用与旧版本应用的差分文件,下载完成之后利用本地已安装的旧版Apk文件和所下载的差分文件合成新版本Apk。   普通更新逻辑简单,适合apk包文件体积小的应用;增量更新只下载新旧版本Apk文件的差异文件(差异文件的大小因新旧版本Apk而异,新版本与旧版本差异小的情况下,差异文件的文件大小会非常小),节省用户流量,提高升级效率。   由于增量更新涉及比较复杂的合成逻辑,所以此类升级方式只能依赖应用宝App才能完成;普通更新仅需要下载Apk文件,可以在SDK内独立完成,不需要应用宝App介入。


 6. 省流量更新SDK接入

添加maven仓库

maven {
   url "http://maven.oa.com/nexus/content/repositories/thirdparty"
}

在模块中添加依赖

   implementation "com.tencent.basicservices:patch_update:1.0.2-release102-WithJce-HideHalley-4001"

 省流量更新SDK初始化

  在调用省流量更新SDK提供的接口前,需要对SDK进行初始化。省流量更新SDK初始化涉及参数代码如下所示:

private String mAppKey    = "SuperDownTestDemo2";
private String mAppSecret = "dd40b776992873be708858471220d5b3";

private void initSelfUpdateSDK() {
    Map<String, String> initMap = new HashMap<>();

    initMap.put(InitParam.APP_KEY, mAppKey);
    initMap.put(InitParam.APP_SECRET, mAppSecret);
    initMap.put(UpdateConst.INIT_PARAM.YYB_APK_CHANNEL_ID, "990483");
    initMap.put(UpdateConst.INIT_PARAM.HOST_APP_CHANNEL_ID, "000116083735373738333534");
    initMap.put(UpdateConst.INIT_PARAM.VIA, "ANDROIDQQ.YYB.SELFUPDATE");
    SelfUpdateSDK.init(this, initMap);
}

初始化参数中:   1) appKey/appSecret属于公共参数,接入sdk时分配。   2) YYB_APK_CHANNEL_ID是指拉新应用宝时,下载的应用宝Apk的渠道号,由应用宝产品同学分配,接入时可联系产品同学提供。   3) HOST_APP_CHANNEL_ID是指App自身在应用宝数据源的渠道信息,对应于qms上的channleID字段信息,用于跳转应用宝时下载指定渠道的Apk,如若不需指定渠道,不填即可,切忌填写错误的渠道号。   4) VIA是指用于应用宝识别来源方的字段信息,由应用宝产品同学分配,接入时可联系产品同学提供。

 应用更新检查

检查应用更新首先需注册接收检查更新结果的监听器,监听器定义具体代码如下:

private ISelfUpdateListener mSelfUpdateListener = new ISelfUpdateListener() {
    @Override
    public void onUpdateStateChanged(TMAppUpdateInfo tmAppUpdateInfo) {
        Log.i(TAG, ">onUpdateStateChanged " + tmAppUpdateInfo);
        //执行更新时,下载任务状态回调
    }

    @Override
    public void onUpdateProgressChanged(TMAppUpdateInfo tmAppUpdateInfo) {
        Log.i(TAG, ">onUpdateProgressChanged " + tmAppUpdateInfo);
        //执行更新时,下载任务进度回调
    }

    @Override
    public void onCheckUpdateSucc(TMAppUpdateInfo tmAppUpdateInfo) {
        Log.i(TAG, ">onCheckUpdateSucc " + tmAppUpdateInfo);
        //检查更新协议成功,接收到更新数据
    }

    @Override
    public void onCheckUpdateFailed(int errorCode) {
        //检查更新协议请求失败
    }
};

  监听器中包括两部分:下载任务相关的回调、检查更新相关的回调。检查更新时只涉及检查更新相关的回调。   注册监听器,并触发检查更新动作:

SelfUpdateSDK.get().addSelfUpdateListener(mSelfUpdateListener);
SelfUpdateSDK.get().checkSelfUpdate();

  当SDK接收到后台返回的更新数据后,通过已注册的监听器告知调用方。

 使用SDK进行自升级

  调用检查更新接口成功接收到更新信息(TMAppUpdateInfo 结构体)后,可通过updateMethod字段判断更新类型,具体类型描述如下代码所述:

private TMAppUpdateInfo mAppUpdateInfo;
private void checkUpdateType() {
    if (mAppUpdateInfo.updateMethod == UpdateConst.TYPE.NO_UPDATE) {
        //无更新
    } else if (mAppUpdateInfo.updateMethod == UpdateConst.TYPE.NORMAL_UPDATE) {
        //普通更新
    } else if (mAppUpdateInfo.updateMethod == UpdateConst.TYPE.PATCH_UPDATE) {
        //增量更新
    }
}

  当更新类型为普通更新(后台未返回增量更新数据,无法进行增量更新)时,建议使用SDK进行自升级。   第2节已注册更新过程的回调监听器,只需调用SDK更新接口即可开始执行更新流程:

SelfUpdateSDK.get().startSelfUpdateBySDK(mAppUpdateInfo);

  任务的下载进度和状态变化会通过监听器回调给调用方。暂停下载任务的接口如下:

SelfUpdateSDK.get().pauseSelfUpdateBySDK(mAppUpdateInfo);

  自升级任务的状态用TMAppUpdateInfo 结构体的updateState字段区分,updateState字段的值对应的含义如下代码所示:

private String getShowText(int updateState) {
    String text = "";
    switch (updateState) {
        case UpdateConst.UPDATE_STATE.WATING:
            text = "等待开始下载...";
            break;
        case UpdateConst.UPDATE_STATE.DOWNLOADING:
            text = "下载中";
            break;
        case UpdateConst.UPDATE_STATE.DOWNLOAD_PAUSED:
            text = "已暂停";
            break;
        case UpdateConst.UPDATE_STATE.DOWNLOAD_FAILED:
            text = "下载失败";
            break;
        case UpdateConst.UPDATE_STATE.DOWNLOAD_DELETED:
            text = "下载删除";
            break;
        case UpdateConst.UPDATE_STATE.DOWNLOAD_SUCC:
            text = "自更新任务下载成功";
            break;
        case UpdateConst.UPDATE_STATE.SELF_UPDATE_FAILED:
            text = "自更新任务失败";
            break;
        case UpdateConst.UPDATE_STATE.INSTALL_SUCC:
            text = "自更新任务安装成功";
            break;
        default:
            text = "UNKNOWN";
            break;
    }
    return text;
}

 使用YYB进行自升级

  类似于SDK升级流程,当检查更新的结果为增量更新时,建议使用YYB进行自更新(否则使用SDK升级即普通更新效果)。   开始自更新和暂停自更新接口调用如下:

SelfUpdateSDK.get().startSelfUpdateByYYB(mAppUpdateInfo);
SelfUpdateSDK.get().pauseSelfUpdateByYYB(mAppUpdateInfo);

  使用YYB进行自更新时,前提条件是需要用户已安装应用宝App。腾讯下载SDK提供判断应用宝是否安装的接口。另外,也提供下载应用宝Apk相关服务的接口。

监听下载应用宝APK任务的状态

初始化监听器:

private TaskListener mYYBDownloadListener = new TaskListener() {
    @Override
    public void onTaskStart(DownloadInfo downloadInfo) {
    }

    @Override
    public void onTaskStateChanged(DownloadInfo downloadInfo) {
        Log.i(TAG, ">onTaskStateChanged " + downloadInfo);
    }

    @Override
    public void onTaskProgressChanged(DownloadInfo downloadInfo) {
        Log.i(TAG, ">onTaskProgressChanged " + downloadInfo);
    }

    @Override
    public void onInstallStart(DownloadInfo downloadInfo) {

    }

    @Override
    public void onInstalled(DownloadInfo downloadInfo) {
        Log.i(TAG, ">onInstalled " + downloadInfo);
    }
};

注册监听器:

SelfUpdateSDK.get().registerDownloadYYBApkListener(mYYBDownloadListener);

  触发应用宝Apk下载任务的方式有两种:     1.静默预下载;     2.调用YYB自升级接口自动触发。   两者的区别是:预下载不会主动拉起安装,而调用YYB自升级接口自动触发会主动拉起应用宝安装。

   预下载应用宝调用:

int ret = SelfUpdateSDK.get().preDownloadYYB();
if (ret == DownloadConst.ALREADY_SUCC_ERROR) {
    Log.e(TAG, "应用宝已经预下载完成了");
}

 TMAppUpdateInfo字段说明

  TMAppUpdateInfo结构体贯穿整个自升级过程,它的主要字段及含义如下:

字段名称 含义
updateMethod 更新类型,值含义参考UpdateConst#TYPE
newApkSize 新版本apk全量包大小
patchSize 增量包大小,仅在增量更新类型时值才有效
newFeature 新版本特性文案
updateDownloadUrl 新版本下载链接
packageName 包名
versionName 版本名
versionCode 版本号
fileMd5 新版本文件MD5
apkId 新版本apkId
appId 新版本appId
updateState 任务所处状态
downloaderType 下载器类型标识,值含义参考UpdateConst#DownloaderType
apkPath 最终apk路径
receivedLength 已接收到的任务大小
totalLength 任务总大小
updateErrorCode 任务出错时的错误码
updateErrorMsg 任务出错时的错误信息
downloadErrorCode 下载过程的错误码,由下载模块定义
pauseOnMobile 任务是否需要在移动网络下自动暂停
hostChannelId 任务包名对应的APP在应用宝数据源的渠道信息
via 业务VIA,标识来源

 省流量更新SDK交互补充

image.png

 判断应用宝是否安装,显示不同界面

if(SelfUpdateSDK.get().isYYBInstalled()) {
    // TODO : 应用宝安装,只显示极速更新按钮
} else {
    // TODO : 应用宝未安装,显示极速更新以及普通更新两个按钮
    // 注册应用宝安装包下载情况监听器,注册监听器可挪至其它更早的位置,具体参考省流量更新文档
    SelfUpdateSDK.get().registerDownloadYYBApkListener(taskListener);
    // 基础服务SDK中base库提供网络环境查询
    // 当前连接WIFI
    if(NetworkUtil.isWifi()) {
        // Wifi状态下后台开始静默下载
        SelfUpdateSDK.get().preDownloadYYB();
    }
}

  通过SelfUpdateSDK.get().isYYBInstalled()来判断应用宝是否安装。   应用宝已安装返回true,此时只显示极速更新按钮。   应用宝未安装返回false,此时显示两个按钮,分别为极速更新按钮以及普通更新按钮。   应用宝未安装时,注册一个应用宝下载状况监听器(TaskListener),用来获取应用宝的下载状况信息。应用宝未安装时当前网络状态为Wifi连接时(这里在腾讯下载sdk的base组件中提供网络环境查询函数),后台对应用宝进行静默下载,静默下载调用SelfUpdateSDK.get().preDownloadYYB()。

 点击极速更新按钮

/**
 * 极速更新点击时响应伪代码
 */
public void onClick_SpeedUpdate() {
    if(SelfUpdateSDK.get().isYYBInstalled()) {
        // 应用宝已安装, 拉起应用宝极速更新
        // tmAppUdateInfo为更新应用的信息,类型TMAppUpdateInfo
        // tmAppUdateInfo获取可通过检查更新回调获取,如何进行检查更新参考省流量更新文档
        SelfUpdateSDK.get().startSelfUpdateByYYB(tmAppUpdateInfo);
    } else {
        int ret = SelfUpdateSDK.get().preDownloadYYB();
        // 应用宝未安装但是静默下载完成
        if(ret == DownloadConst.ALREADY_SUCC_ERROR) {
            // 弹出应用宝安装界面,tmYYBUpdateInfo为应用宝应用的信息,类型TMAppUpdateInfo
            SelfUpdateSDK.get().startSelfUpdateByYYB(tmYYBUpdateInfo);
        } else {
            // TODO : 应用宝未安装并且下载未完成,极速更新变为应用宝安装包的下载进度
            // TODO : 下载进度的更新获取可通过应用宝安装包下载情况监听器获取,监听onTaskProgressChanged
        }
    }
}

  当点击极速更新按钮,存在两种情况,一种是已安装应用宝(只存在极速更新按钮),另一种是未安装应用宝(同时存在极速更新和普通更新按钮)。   1)  已安装应用宝     点击极速更新按钮,拉起应用宝并进行更新应用,此时调用     SelfUpdateSDK.get().startSelfUpdateByYYB(tmAppUpdateInfo)     tmAppUpdateInfo为更新应用的更新信息,具体类型为TMAppUpdateInfo。可通过检查更新回调监听器(ISelfUpdateListener)获取应用的TMAppUpdateInfo。   2)  未安装应用宝     点击极速更新按钮,如果应用宝静默下载完成,则直接拉起应用宝安装,拉起应用宝调用应用宝的自升级接口自动触发。此时调用     SelfUpdateSDK.get().startSelfUpdateByYYB(tmYYBUpdateInfo)     tmYYBUpdateInfo为应用宝的更新信息,具体类型为TMAppUpdateInfo。     如果应用宝静默下载未完成,则通过应用宝安装包下载安装监听器(TaskListener)中回调函数onTaskProgressChanged、onTaskStateChanged等获取进度变化以及状态变化。

 点击普通更新按钮

/**
 * 普通更新点击时响应
 */
public void onClick_GeneralUpdate() {
    // 使用SDK自升级
    SelfUpdateSDK.get().startSelfUpdateBySDK(tmAppUpdateInfo);
    // TODO : 显示普通下载进度变化条
}

  当点击普通更新按钮,直接通过SDK进行下载。此时调用   SelfUpdateSDK.get().startSelfUpdateBySDK(tmAppUpdateInfo)   tmAppUpdateInfo为更新应用的更新信息,具体类型为TMAppUpdateInfo。SDK下载进度以及下载状态可通过设置SDK下载监听器(ISelfUpdateListener)获取到。

 接入测试用例

用例名称 用例描述 前提条件 操作步骤 期待结果
未安装应用宝进行省流量更新 未安装应用宝时点击省流量更新按钮 检测到新版本且未安装应用宝 1.启动应用,显示省流量更新按钮和普通更新按钮。
2.点击省流量更新按钮。
1.后台静默下载应用宝安装包。
2.点击省流量更新后应用宝安装包下载完成则拉起安装,下载未完成则提示应用宝的下载进度。
3.应用宝安装包拉起安装,但是用户拒绝安装,给出提示省流量更新失败。
4.应用宝安装成功,跳转应用宝并省流量更新应用。
未安装应用宝进行普通更新 未安装应用宝时点击普通更新按钮 检测到新版本且未安装应用宝 1.启动应用,显示省流量更新按钮和普通更新按钮。
2.点击普通更新按钮。
1.后台静默下载应用宝安装包。
2.开始下载应用最新安装包,完成后拉起安装。
安装应用宝进行省流量更新 已安装应用宝点击省流量更新按钮 检测到新版本且安装应用宝 1.启动应用,只显示省流量更新按钮。
2.点击省流量更新按钮。
跳转到应用宝界面并省流量更新应用。

  应用宝省流量更新SDK提供应用更新升级服务:应用的新版本在应用宝开发平台上架之后,便可通过SDK获取到应用的升级信息。应用升级信息通常分为以下两类:     1) 普通更新: 下载新版本应用的全量Apk文件,完成升级;     2) 增量更新: 下载新版本应用与旧版本应用的差分文件,下载完成之后利用本地已安装的旧版Apk文件和所下载的差分文件合成新版本Apk。   普通更新逻辑简单,适合apk包文件体积小的应用;增量更新只下载新旧版本Apk文件的差异文件(差异文件的大小因新旧版本Apk而异,新版本与旧版本差异小的情况下,差异文件的文件大小会非常小),节省用户流量,提高升级效率。   由于增量更新涉及比较复杂的合成逻辑,所以此类升级方式只能依赖应用宝App才能完成;普通更新仅需要下载Apk文件,可以在SDK内独立完成,不需要应用宝App介入。


7. 接入常见问题

 如何触发省流量更新

  测试包接入时,获取的更新为全量更新,在接入测试要触发省流量更新,则需要将测试包发给产品同学,由产品同学将测试包上传到后台进行增量计算,才能够获取到省流量更新信息,并且上传的调试测试包需要调低版本,比线上版本低,每次对测试包的更改都需要重新上传进行增量计算。

 下载成功是否会拉起安装?取消安装如何再次拉起安装界面?

  下载成功后会自动拉起安装,如果用户取消安装再次调用省流量下载接口即可。

 点击更新跳转应用宝问题

  省流量更新会拉起应用宝进行更新,这是正常现象。但是如果用户没有安装应用宝,下载应用宝的过程是不会进入应用宝的,会在应用内部下载,然后点击省流量更新的时候将拉起应用宝安装。除此之外,点击普通更新也是不会跳转应用宝的,会直接在应用内下载全量包,然后拉起安装。上述具体过程和应用如何提示用户可以参考后续小节省流量SDK交互补充。

 更新异常问题(超时/下载失败)

  下载失败或超时可以在onUpdateStateChanged这个回调进行判断,获取相应的任务状态,详细描述更新的当前状态(TMAppUpdateInfo 结构体的updateState),这其中就包括下载失败/连接超时等错误状态。SDK已经做了错误的相关处理,如果失败了,多半网络问题导致,在应用上层判断重试意义不大,可以直接给UI提示用户。

 如果线上版本有问题,如何阻止应用省流量更新。

  具体有如下解决方案,推荐使用第1种:

    1) 应用宝线上包有问题的话,可以回滚下线。

    2) 通知应用宝关闭省流量更新的,具体需告知后台修改下配置文件强行过滤掉应用,此时更新信息返回的是全量包更新的信息,即普通更新。

    3) 应用可以自身控制省流量更新过程,自行判断应用宝线上版本是否错误,不进行省流更新,而采用应用内部更新。


8. 接入异常解决方案

 调试签名问题

  导入调试的过程中,在debug模式下有可能采用系统默认的调试签名信息,此时检查更新会出现errorCode为-109(网络异常),需要在debug测试的过程中,需要更改debug下默认签名与申请的appKey和appSecret所使用的签名一致。 image.png

 Manifest冲突问题

  SDK包在RDM构建过程会在download模块包中的manifest注入uuid来上报监控异常。 image.png   当两个SDK包同时注入uuid就会造成meta-data#com.tencent.rdm.uuid冲突。   解决方案:RDM编译过程添加参数UUID_INJECT=None。 RDM常见问题案例库:http://km.oa.com/group/11880/articles/show/368225?kmref=search&from_page=1&no=7

 类冲突问题

  遇到com.qq.taf.jce.*包下的类冲突时下载去除jce冲突类的省流量更新SDK包。   遇到com.tencent.assistant.*包下的类冲突时下载去除remote冲突类的省流量更新SDK包。

 so加载问题

   由于AndroidStudio在编译工程时可能会对so做“strip”操作,会导致SDK对应的so出现运行异常,示例堆栈如下: image.png

image.png

  解决方案:在build.gradle下android包里添加打包选项,防止so文件压缩。

android {
    …
    packagingOptions {
        doNotStrip "**/libbs-sec.so"
    }
}

9. 附件

附件
签名MD5获取工具
SDK包

接入案例

   1) 手Q: 洗包监控    2) 微视: 洗包监控、SDK内下载、应用宝下载


接口人

  产品:emmashi   后台/报表:nicknie/qiyyang   终端:careyjwang/nemoqjzhang/yuminghuang

⚠️ **GitHub.com Fallback** ⚠️