Android SDK接入

前言

本章节介绍移动推送Android SDK的接入方法。

  • 推荐使用Gradle管理依赖的Android Studio项目。

  • 支持Android 4.4及以上版本。

  • 本文档仅适合移动推送Android SDK V3.0.0及以上版本的集成操作。

移动推送Android SDK Demo工程请参见:移动推送Android Demo

Demo工程使用Kotlin + MVVM开发,界面更美观,功能更丰富,开发者可参考Demo更快速了解移动推送Android SDK如何使用。

准备工作

第一步:将SDK添加到您的应用

我们提供了Maven依赖和本地依赖两种集成方式,方便您根据需要将SDK添加到您的应用中。

说明

建议开发者采用Maven依赖方式进行集成,配置简单,不容易出问题,后续更新方便。

1 Maven依赖方式

1.1 配置Maven仓库

下面分别介绍Gradle 7.0及以上推荐的dependencyResolutionManagement配置方式和Gradle 7.0之下推荐的allprojects配置方式。

1.1.1 dependencyResolutionManagement方式

在您的根级(项目级)Gradle 文件(<project>/settings.gradle)中,在dependencyResolutionManagementrepositories中添加Maven仓库地址。

dependencyResolutionManagement {
  repositories {
    maven {
      url 'https://maven.aliyun.com/nexus/content/repositories/releases/'
    }

    // 配置HMS Core SDK的Maven仓地址,集成华为通道需要。
    maven {
      url 'https://developer.huawei.com/repo/'
    }
  }
}
1.1.2 allprojects方式

在您的根级(项目级)Gradle 文件(<project>/build.gradle)中,在allprojectsrepositories中添加Maven仓库地址。

allprojects {
  repositories {
    maven {
      url 'https://maven.aliyun.com/nexus/content/repositories/releases/'
    }

    // 配置HMS Core SDK的Maven仓地址,集成华为通道需要。
    maven {
      url 'https://developer.huawei.com/repo/'
    }
  }
}

1.2 添加SDK依赖

在您的模块(应用级)Gradle 文件(通常是<project>/<app-module>/build.gradl)中,在dependencies中添加SDK依赖。

dependencies {
  implementation 'com.aliyun.ams:alicloud-android-push:{pushVersion}'
}
重要
  • pushVersion请从Android SDK版本说明中获取。

  • 请使用固定版本号集成,不要使用动态版本号。错误示例:3.+或者3.2.+。

2 本地依赖方式

2.1 下载SDK

参考下载和集成SDK,选择移动推送进行下载,将SDK包内所有文件拷贝至您的模块(应用级)的<project>/<app-module>/libs目录下。

2.2 添加SDK依赖

2.2.1 配置本地SDK目录

在您的模块(应用级)Gradle 文件(通常是<project>/<app-module>/build.gradle)中,添加本地SDK文件目录地址。

repositories {
  flatDir {
    dirs 'libs'
  }
}

2.2.2 添加SDK依赖

在您的模块(应用级)Gradle 文件(通常是<project>/<app-module>/build.gradle)中,的dependencies中添加SDK依赖。

dependencies {
  implementation fileTree(include: ['*.jar'], dir: 'libs')
  implementation (name:'alicloud-android-push-3.x.x', ext: 'aar')
  implementation (name:'alicloud-android-accs-4.x.x', ext: 'aar')
  // 把所有的aar都添加上
  ...
  // 华为从5.0.2版本开始不再提供离线包
  implementation 'com.huawei.hms:push:x.x.x.x'
  // 魅族从4.1.4版本开始不再提供离线包
  implementation 'com.meizu.flyme.internet:push-internal:x.x.x'
}
重要
  • 示例依赖中的SDK版本号请根据下载产物的文件名中的版本号为准。

  • 如果编译报类冲突,请确认dependencies下是否已经有implementation fileTree(dir: 'libs', include: ['*.jar'])

第二步:配置使用SDK

1 配置AppKey和AppSecret

配置AppKey和AppSecret有两种方式,AndroidManifest文件配置和代码配置两种方式。请根据您的需求选择其中一种方式进行配置即可。

说明
  • 为避免在日志中泄漏参数appkey/appsecret或App运行过程中产生的数据,建议线上版本关闭SDK调试日志。

  • 为防止恶意反编译获取AppKey和AppSecret参数造成信息泄漏,建议您选择代码配置方式,开启混淆,并进行App加固后再发布上线。

  • 如果您是百川云推送用户,不能直接使用百川平台的AppKey和AppSecret,需要登录EMAS控制台,登录账号为您的百川平台账号,并使用阿里EMAS平台的AppKey和AppSecret。

1.1 AndroidManifest文件配置方式

AndroidManifest.xml文件中配置AppKey、AppSecret。按下面的方式,在application节点添加相应的meta-data

说明
  • com.alibaba.app.appkeycom.alibaba.app.appsecret为您在EMAS平台上的App对应信息。在EMAS控制台的应用管理中或在下载的配置文件中查看AppKey和AppSecret。

  • AppKey和AppSecret请务必写在application标签下,否则SDK会报找不到AppKey的错误。

<application android:name="*****">
    <!-- 请填写你自己的appKey -->
    <meta-data android:name="com.alibaba.app.appkey" android:value="*****"/> 
    <!-- 请填写你自己的appSecret -->
    <meta-data android:name="com.alibaba.app.appsecret" android:value="****"/> 
</application>

1.2 代码配置方式

除了在AndroidManifest中配置AppKey和AppSecret的方式,您也可以在代码中进行配置。

val pushInitConfig = PushInitConfig.Builder()
    .application(application)
    .appKey(appKey)    //请填写你自己的appKey
    .appSecret(appSecret)    //请填写你自己的appSecret
    .build()
PushInitConfig pushInitConfig = new PushInitConfig.Builder()
        .application(application)
        .appKey(appKey)    //请填写你自己的appKey
        .appSecret(appSecret)    //请填写你自己的appSecret
        .build();

更多配置接口请查看基础配置接口高阶配置接口

2 SDK初始化

为了尽量降低对App启动速度的影响,初始化可以分阶段进行。

2.1 必须在Application onCreate中执行的逻辑

此阶段初始化用于初始化一些推送参数,没有启动推送逻辑,必须在Application onCreate中执行。

根据上一节中选择的配置AppKey和AppSecret的方式,这里初始化代码也有两种方式。

AndroidManifest文件配置方式

PushServiceFactory.init(context)
PushServiceFactory.init(context);

代码配置方式

PushServiceFactory.init(pushInitConfig)
PushServiceFactory.init(pushInitConfig);
说明

如果您在AndroidManifest.xml中配置了AppKey和AppSecret,也可以使用PushServiceFactory.init(pushInitConfig),如果在PushInitConfig也配置了AppKey和AppSecret,会优先使用PushInitConfig中的配置。

2.2 可以延迟执行的逻辑

此阶段初始化用于建立推送的长连接,可以根据业务需要和合规要求延迟执行。

val pushService = PushServiceFactory.getCloudPushService()
pushService.register(this, object : com.alibaba.sdk.android.push.CommonCallback {
    override fun onSuccess(success: String) {}
    override fun onFailed(errorCode: String, errorMessage: String) {}
})
CloudPushService pushService = PushServiceFactory.getCloudPushService();
pushService.register(this, new com.alibaba.sdk.android.push.CommonCallback() {
    @Override
    public void onSuccess(String success) {

    }

    @Override
    public void onFailed(String errorCode, String errorMessage) {
        
    }
});
重要

PushServiceFactory.init必须在Application主线程中,不能放到Activity中执行,也不能异步初始化。移动推送在初始化过程中将启动后台进程channel,必须保证应用进程和channel进程都执行到PushServiceFactory.init。

3 消息接收配置

到这一步说明您已经完成了SDK的初始化,推送长连接已经成功建立,但是要接收推送消息,还需要进行一些配置。

我们提供了MessageReceiver/AliyunMessageIntentService两种方式,方便您拦截通知,接收消息,获取推送中的扩展字段。或者在通知打开或删除的时候,切入进行后续处理。

您可以任选一种方式处理推送的消息。具体查看MessageReceiver/AliyunMessageIntentService相关接口

重要
  • 完成这一步,理论上您的App已经可以收到推送消息了。

  • 如果部分手机上收不到推送的通知消息,有可能是系统版本差异性导致,请参考注意事项进行处理。

  • 您仅需要注册您自定义的默认实现的MessageReceiver/AliyunMessageIntentService,即可收到默认样式的通知消息,如果要自定义样式、前台拦截通知消息、处理透传消息等,需要扩展对应的API来处理。

4 NDK配置

在您的模块(应用级)Gradle 文件(通常是<project>/<app-module>/build.gradle)中,在androiddefaultConfig节点下添加NDK配置。

android {
    defaultConfig {
        ndk {
            //选择要添加的对应cpu类型的.so库。此处仅为示意,推送支持所有主流类型,请根据实际需求选择
            abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
        }
    }
}

5 自定义推送通知样式(可选)

如果您需要自定义通知的样式,可以查看自定义通知样式相关接口

6 拦截推送通知(可选)

如果您需要拦截处理推送通知,只要扩展MessageReceiver/AliyunMessageIntentServiceshowNotificationNowonNotificationReceivedInApp即可,下面以MessageReceiver的扩展方式来举例。

class MyMessageReceiver: MessageReceiver(){

    override fun onNotificationReceivedInApp(
        context: Context?,
        title: String?,
        summary: String?,
        map: MutableMap<String, String>?,
        openType: Int,
        openActivity: String?,
        openUrl: String?
    ) {
        //这里可以处理下发的推送通知
    }

    override fun showNotificationNow(p0: Context?, p1: MutableMap<String, String>?): Boolean {
        //false表示拦截,true表示不拦截,请根据进行拦截,拦截后会执行到 onNotificationReceivedInApp
        return false
    }
}
public class MyMessageReceiver extends MessageReceiver {
    @Override
    protected void onNotificationReceivedInApp(Context context, String title, String summary, Map<String, String> map, int openType, String openActivity, String openUrl) {
        //这里可以处理下发的推送通知
    }

    @Override
    public boolean showNotificationNow(Context context, Map<String, String> map) {
        //false表示拦截,true表示不拦截,请根据进行拦截,拦截后会执行到 onNotificationReceivedInApp
        return false;
    }
}

拦截的推送通知,点击和取消事件,需要您自行上报,上报接口见自建通知统计上报接口

7 处理推送消息(建议)

推送支持的消息有两类,其中推送通知,在SDK有默认实现,消息到达手机后会通过通知栏展示;而推送消息需要您扩展API实现。只要扩展MessageReceiver/AliyunMessageIntentServiceonMessage接口即可。下面以MessageReceiver的扩展方式来举例。

class Kk: MessageReceiver() {
    override fun onMessage(context: Context?, cPushMessage: CPushMessage?) {
        val title = cPushMessage?.title
        val content = cPushMessage?.content

        if (isForeground) {
            //App处于前台,弹窗形式显示
        } else {
            //App处于后台,通知形式显示
        }
    }
}
public class MyMessageReceiver extends MessageReceiver {
    @Override
    protected void onMessage(Context context, CPushMessage cPushMessage) {
        String title = cPushMessage.getTitle();
        String content = cPushMessage.getContent();

        if (isForeground) {
            //App处于前台,弹窗形式显示
        } else {
            //App处于后台,通知形式显示
        }
    }
}

对于推送消息的点击和取消事件,需要您自行上报,上报接口见自建通知统计上报接口

8 使用标签进行推送(可选)

除了全量推送,我们也支持使用标签进行批量推送。在通过标签进行推送前,SDK侧需要将设备绑定上标签,相关接口见标签(tag)相关接口。代码示例如下:

PushServiceFactory.getCloudPushService()
    .bindTag(CloudPushService.DEVICE_TARGET, arrayOf(tag), null, object : CommonCallback {
        override fun onSuccess(s: String) {}
        override fun onFailed(errorCode: String, errorMsg: String) {}
    })
PushServiceFactory.getCloudPushService().bindTag(CloudPushService.DEVICE_TARGET, new String[]{tag}, null, new CommonCallback() {
    @Override
    public void onSuccess(String s) {

    }

    @Override
    public void onFailed(String errorCode, String errorMsg) {

    }
});

除了使用标签进行推送外,我们还支持:

9 集成辅助通道(建议)

为了提高App离线状态下的推送到达率,建议集成辅助通道。请参考辅助通道集成进行集成。

重要

辅助通道的版本和推送SDK的版本有对应关系,具体信息请查看辅助通道SDK版本关系说明

10 混淆配置

如果您的项目中使用Proguard等工具做了代码混淆,请保留以下配置:

-keepclasseswithmembernames class ** {
    native <methods>;
}
-keepattributes Signature
-keep class sun.misc.Unsafe { *; }
-keep class com.taobao.** {*;}
-keep class com.alibaba.** {*;}
-keep class com.alipay.** {*;}
-keep class com.ut.** {*;}
-keep class com.ta.** {*;}
-keep class anet.**{*;}
-keep class anetwork.**{*;}
-keep class org.android.spdy.**{*;}
-keep class org.android.agoo.**{*;}
-keep class android.os.**{*;}
-keep class org.json.**{*;}
-dontwarn com.taobao.**
-dontwarn com.alibaba.**
-dontwarn com.alipay.**
-dontwarn anet.**
-dontwarn org.android.spdy.**
-dontwarn org.android.agoo.**
-dontwarn anetwork.**
-dontwarn com.ut.**
-dontwarn com.ta.**

第三步:接入验证

1 打开SDK日志

val pushService = PushServiceFactory.getCloudPushService()
//仅适用于Debug包,正式包不需要此行
pushService.setLogLevel(CloudPushService.LOG_DEBUG)
CloudPushService pushService = PushServiceFactory.getCloudPushService();
//仅适用于Debug包,正式包不需要此行
pushService.setLogLevel(CloudPushService.LOG_DEBUG);      

2 启动正常确认方法

  • 回调方法callback.onSuccess()被调用。在logcat日志中,输入tag:MPS:

2024-07-03 10:34:48.630 14509-9747  [MPS]                   com.aliyun.emas.pocdemo              I  agoo init success.
2024-07-03 10:34:48.631 14509-9749  [MPS]                   com.aliyun.emas.pocdemo              D  register agoo result 错误码:PUSH_00000, 错误:success
2024-07-03 10:34:48.631 14509-9749  MPS:AppRegister         com.aliyun.emas.pocdemo              I  connState=2;estimatedTime=384;response{msg: success, code: PUSH_00000}
2024-07-03 10:34:48.631 14509-9749  MPS:AppRegister         com.aliyun.emas.pocdemo              D  Looping handleMessage: 1
2024-07-03 10:34:48.631 14509-14509 [MPS]                   com.aliyun.emas.pocdemo              I  errorCode:错误码:PUSH_00000, 错误:success
  • 确认cloud channel初始化正常,在logcat日志中,输入awcn关键字:

2024-07-03 10:36:57.464  8890-10129 EMASNAccs_NetworkSdk    com.aliyun.emas.pocdemo              I  [awcn.TnetSpdySession]  statusCode:200
2024-07-03 10:36:57.465  8890-10129 EMASNAccs_NetworkSdk    com.aliyun.emas.pocdemo              I  [awcn.TnetSpdySession]  response headers:{date=[Wed, 03 Jul 2024 02:36:57 GMT], content-length=[0], server=[Tengine/Aserver/3.0.413_20221027005707], s-accs-retcode=[SUCCESS], :status=[200], x-workerid=[360290169770599862], x-at=[ZoS4k5ejQckDADGQGBlEBBUB3347866231719988617]}
2024-07-03 10:36:57.467  8890-10129 EMASNAccs_NetworkSdk    com.aliyun.emas.pocdemo              E  [awcn.Session]|[seq:334786623.AWCN1_1] notifyStatus status:AUTH_SUCC
  • 确认deviceId获取正常:在初始化成功后使用cloudPushService.getDeviceId()可以成功获取deviceId。

  • 如果注册服务器连接失败,则调用callback.onFailed()方法,并且自动进行重新注册,直到onSuccess为止(重试规则会由网络切换等时机自动触发)。在onFailed()方法中,会由相应的错误码返回,可参考错误处理

说明

市场上部分手机,对Log显示做了限制,比如华为手机,屏蔽了Debug和Verbose级别的日志;建议开发时使用Info级别日志。日志级别设置请参考基础配置接口中的“设置日志等级”。

注意事项

1 Android 8+适配

请参考Android 8.0以上设备接收不到推送通知

2 Android 13适配

Android 13新增了POST_NOTIFICATIONS权限,推送3.8.4 SDK已经添加了该权限,如果业务方的APP的targetSdk低于33,只需要升级至3.8.4版本即可,应用启动后系统会自动弹出授权框;如果targetSdk是33,则需要业务方APP在运行时主动申请POST_NOTIFICATIONS权限。

非手机场景说明

在一些特定设备上,使用场景和手机场景不同,可以进行一些特别的配置,主要分为两类:一类指类似手机应用,当用户使用时应用才会运行,用户不使用时,可能会被系统回收,另一类是系统级别应用,会长时间运行。

类手机应用配置

这种场景,可以开启channel进程心跳,提高通道的稳定性。

val pushInitConfig = PushInitConfig.Builder()
    .application(application)
    // 开启channel进程
    .disableChannelProcess(false)
    // 开启channel进程心跳
    .disableChannelProcessHeartbeat(false)
    .build()

PushServiceFactory.init(pushInitConfig)

val pushService = PushServiceFactory.getCloudPushService()
pushService.register(this, object : com.alibaba.sdk.android.push.CommonCallback {
    override fun onSuccess(success: String) {}
    override fun onFailed(errorCode: String, errorMessage: String) {}
})
PushInitConfig pushInitConfig = new PushInitConfig.Builder()
        .application(application)
        // 开启channel进程
        .disableChannelProcess(false)
        // 开启channel进程心跳
        .disableChannelProcessheartbeat(false)
        .build()

PushServiceFactory.init(pushInitConfig);

CloudPushService pushService = PushServiceFactory.getCloudPushService();
pushService.register(applicationContext, new CommonCallback() {
    @Override
    public void onSuccess(String response) {
        
    }
    @Override
    public void onFailed(String errorCode, String errorMessage) {
        
    }
});

如果系统不支持JobService(Android API版本低于21),还需要添加WAKE_LOCK权限。

<!-- 设备 Android API版本低于21 添加WAKE_LOCK权限 -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>

长时间运行的系统应用

此类应用因为是一直运行的,根据系统性能要求,可以考虑不使用channel进程推送通道,完全使用应用内推送通道。

val pushInitConfig = PushInitConfig.Builder()
    .application(application)
    // 根据情况禁止channel进程
    .disableChannelProcess(false)
    // 禁止channel进程心跳
    .disableChannelProcessHeartbeat(false)
    .build()

PushServiceFactory.init(pushInitConfig)

val pushService = PushServiceFactory.getCloudPushService()
pushService.register(this, object : com.alibaba.sdk.android.push.CommonCallback {
    override fun onSuccess(success: String) {}
    override fun onFailed(errorCode: String, errorMessage: String) {}
})
PushInitConfig pushsInitConfig = new PushInitConfig.Builder()
        .application(application)
        // 根据情况禁止channel进程
        .disableChannelProcess(true)
        // 禁止channel进程心跳
        .disableChannelProcessheartbeat(true)
        .build();
PushServiceFactory.init(pushsInitConfig);

CloudPushService pushService = PushServiceFactory.getCloudPushService();
pushService.register(applicationContext, new CommonCallback() {
    @Override
    public void onSuccess(String response) {

    }
    @Override
    public void onFailed(String errorCode, String errorMessage) {

    }
});

长时间运行的应用,需要关注连接是否一直连接,可以注册自己的监听接口,并做一定的检查措施。

val handler = Handler()
val controlService = PushServiceFactory.getPushControlService()
controlService.setConnectionChangeListener(object : ConnectionChangeListener {
    override fun onConnect() {}
    override fun onDisconnect(code: String, msg: String) {
        val isNetworkIssue = !isNetworkConnected()
        // 过一段时间再检查,比如30s
        handler.postDelayed(Runnable {
            if (isNetworkConnected() && !controlService.isConnected()) {
                // 如果网络没有问题,而且连接没有恢复
                // 此时记录埋点 code 和 msg 信息,一定要记录msg信息
                recordDisconnectEvent(code, msg)
                if (isNetworkIssue) {
                    // 刚才没有网络,尝试重连
                    controlService.reconnect()
                } else {
                    // 网络没有问题,或者重连也不行,进行重置,重新进行初始化
                    controlService.reset()
                    initCloudChannel(getContext())
                }
            }
        }, 30 * 1000)
    }
})
// 示意代码,请不要直接使用,请根据具体的业务情况使用api
final Handler handler = new Handler();
PushControlService controlService = PushServiceFactory.getPushControlService();
controlService.setConnectionChangeListener(new PushControlService.ConnectionChangeListener() {
    @Override
    public void onConnect() {

    }

    @Override
    public void onDisconnect(final String code, final String msg) {
        final boolean isNetworkIssue = !isNetworkConnected();
        // 过一段时间再检查,比如30s
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if(isNetworkConnected() && !controlService.isConnected()) {
                    // 如果网络没有问题,而且连接没有恢复
                    // 此时记录埋点 code 和 msg 信息,一定要记录msg信息
                    recordDisconnectEvent(code, msg);
                    if (isNetworkIssue) {
                        // 刚才没有网络,尝试重连
                        controlService.reconnect();
                    } else {
                        // 网络没有问题,或者重连也不行,进行重置,重新进行初始化
                        controlService.reset();
                        initCloudChannel(getContext());
                    }

                }
            }
        }, 30 * 1000);
    }
});

常见集成问题

  1. UTDID冲突,可参考:阿里云-云产品SDK UTDID冲突解决方案

  2. 集成Android SDK后运行App报java.lang.NoClassDefFoundError该如何解决?

  3. 移动推送 Android 端集成失败排查文档

  4. 推送SDK在初始化时报错

  5. Android SDK初始化时出现1105和10207报错怎么解决?

  6. 在Activity中初始化推送SDK,无法接收到推送通知