目录

1. 基础能力概览

本章节介绍Push的基础能力开发流程,主要是指导开发者实现给华为手机推送Push消息的功能,整个功能流程如下图所示:

2. 客户端开发指导

本章节指导开发者完成Push基础能力开发中的客户端开发,通过本开发指导的学习能够帮助开发者快速掌握客户端开发所需的知识技能,快速体验华为Push服务。基础能力客户端开发主要包括如下4个功能点:

1、开发者集成Push SDK后申请Push Token,请参考申请Push Token章节,同时Push SDK 4.0版本我们提供了自动初始化获取token的能力,请参考自动初始化章节

2、开发者App客户端接收服务端推送的Push透传消息,请参考接收透传消息章节

3、是否允许NC(Notification Center)显示通知栏消息,请参考设置是否显示通知栏消息章节

4、自定义点击消息的动作以及向App传递数据可以参考点击通知消息章节

2.1 申请Push Token

2.1.1 场景介绍

每个设备上的每个应用的Token都是唯一存在的,客户端调用HmsInstanceId.getToken接口向服务端请求应用的唯一标识:Push Token,开发者的服务器根据这个Token发送推送消息。开发者可以将Token上报到自己的应用服务器维护这些已申请的Token列表,然后调用Push服务器的推送消息接口,根据Token批量推送消息,服务器发送消息请参考服务端开发指导章节。某些情况下getToken返回为空,请开发者参考HmsMessageService.onNewToken接口说明,通过onNewToken接口接收Token值。

开发者如果想通过Push SDK的自动初始化能力来获取token,可以使用SDK提供的自动初始化能力,具体说明请参考自动初始化部分。

Token会在包括但不限于下述场景中发生变化:

1、App卸载重装;

2、App调用注销Token接口;

3、用户恢复出厂设置;

4、清除应用数据;

说明:
  • HUAWEI Push为了提升安全性,在2018年3月10日升级了Token生成规则和变更机制,称之为Token格式2.0:

    Token长度为不定长,当前为130字节,包含字符0-9、a-z、A-Z及特殊字符-和_。

  • 建议的最佳使用实践

    1、应用不要固定判断Push Token长度,因为后续长度可变。

    2、应用的Push Token要定期更新(建议应用每次启动的时候都获取Token,如果发现和上次取到的不同,则上报到自己的服务器)。

    3、请勿用Token跟踪标记用户。


2.1.2 注意事项

1、getToken接口只有在AppGallery Connect平台开通服务后申请token才会返回成功。

2、EMUI10.0及以上版本的华为设备上,getToken接口直接返回token。如果当次调用失败Push会缓存申请,之后会自动重试申请,成功后则以onNewToken接口返回。

3、低于EMUI10.0的华为设备上,getToken接口如果返回为空,确保Push服务开通的情况下,结果后续以onNewToken接口返回。

4、服务端识别token过期后刷新token,以onNewToken接口返回。

5、集成HMS SDK最新版本需要满足HMS Core(APK)的版本不低于3.0.0。如果低于3.0.0版本,当应用有前台界面时HMS SDK会提示用户升级HMS Core(APK)为最新版本,此时HmsInstanceId.getInstance的入参必须传入Activity类实例;当应用是后台应用时,如果开发者不想弹出引导升级的页面时,可以传入非Activity类型的Context类实例。

6、设置了自动初始化能力的应用,不需要显式的调用getToken申请token,Push SDK会自动申请token,并通过onNewToken回调方法返回。

2.1.3 开发步骤

建议在App启动后首个 activity 的 onCreate 方法调用 getToken 方法。

1、调用getToken方法获取Token

  1. private void getToken() {
  2.    new Thread() {
  3.        @Override
  4.        public void run() {
  5.            try {
  6.                // read from agconnect-services.json
  7.                String appId = AGConnectServicesConfig.fromContext(this).getString("client/app_id");
  8.                String token = HmsInstanceId.getInstance(this).getToken(appId, "HCM");
  9.                Log.i(TAG, "get token:" + token);
  10.                if(!TextUtils.isEmpty(token)) {
  11.                    sendRegTokenToServer(token);
  12.                }
  13.            } catch (ApiException e) {
  14.                Log.e(TAG, "get token failed, " + e);
  15.            }
  16.        }
  17.    }.start();
  18. }
  19. private void sendRegTokenToServer(String token) {
  20.    Log.i(TAG, "sending token to server. token:" + token);
  21. }
注意:

1. 调用getToken方法后获得的token一定要做判空处理。

2. 在调用getToken方法外一定要增加异常捕获处理。

2、Token发生变化时或者EMUI版本低于10.0以 onNewToken 方法返回

  1. // This method callback must be completed in 10 seconds. Otherwise, you need to start a new Job for callback processing.
  2. @Override
  3. public void onNewToken(String token) {
  4.    Log.i(TAG, "received refresh token:" + token);
  5.    // send the token to your app server.
  6.    if (!TextUtils.isEmpty(token)) {
  7.        refreshedTokenToServer(token);
  8.    }
  9. }
  10. private void refreshedTokenToServer(String token) {
  11.    Log.i(TAG, "sending token to server. token:" + token);
  12. }

2.2 注销Push Token

2.2.1 场景介绍

开发者如果不希望应用接收Push消息,可以调用HmsInstanceId.deleteToken接口注销token,注销成功后,客户端就不会再接收到Push消息,包括订阅的主题消息。

但是建议开发者不要依赖deleteToken接口,如果开发者要实现不接收Push消息,可以自行将开发者应用服务器端的Token状态设置为失效状态,并且在推送消息时判断Token失效就不进行推送,避免频繁的向服务器申请token。

注意:
HMS Core(APK)的版本不低于3.0.0。

2.2.2 开发步骤

调用注销Token接口。

  1. private void deleteToken() {
  2.    new Thread() {
  3.        @Override
  4.        public void run() {
  5.            try {
  6.                // read from agconnect-services.json
  7.                String appId = AGConnectServicesConfig.fromContext(this).getString("client/app_id");
  8.                HmsInstanceId.getInstance(this).deleteToken(appId, "HCM");
  9.                Log.i(TAG, "deleteToken success.");
  10.            } catch (ApiException e) {
  11.                Log.e(TAG, "deleteToken failed." + e);
  12.            }
  13.        }
  14.    }.start();
  15. }

2.3 接收透传消息

2.3.1 场景介绍

App如果订阅了主题消息或者服务器主动推送的透传消息(包括通知消息前台展示功能中设置通知消息由应用自己处理),都需要App通过实现onMessageReceived回调方法来接收消息,收到消息的后续行为由应用自己处理。

2.3.2 开发步骤

重写onMessageReceived方法获取消息数据。

  1. @Override
  2. public void onMessageReceived(RemoteMessage message) {
  3.    Log.i(TAG, "onMessageReceived is called");
  4.    if (message == null) {
  5.        Log.e(TAG, "Received message entity is null!");
  6.        return;
  7.    }
  8.    Log.i(TAG, "getCollapseKey: " + message.getCollapseKey()
  9.            + "\n getData: " + message.getData()
  10.            + "\n getFrom: " + message.getFrom()
  11.            + "\n getTo: " + message.getTo()
  12.            + "\n getMessageId: " + message.getMessageId()
  13.            + "\n getOriginalUrgency: " + message.getOriginalUrgency()
  14.            + "\n getUrgency: " + message.getUrgency()
  15.            + "\n getSendTime: " + message.getSentTime()
  16.            + "\n getMessageType: " + message.getMessageType()
  17.            + "\n getTtl: " + message.getTtl());
  18.    RemoteMessage.Notification notification = message.getNotification();
  19.    if (notification != null) {
  20.        Log.i(TAG, "\n getImageUrl: " + notification.getImageUrl()
  21.                + "\n getTitle: " + notification.getTitle()
  22.                + "\n getTitleLocalizationKey: " + notification.getTitleLocalizationKey()
  23.                + "\n getTitleLocalizationArgs: " + Arrays.toString(notification.getTitleLocalizationArgs())
  24.                + "\n getBody: " + notification.getBody()
  25.                + "\n getBodyLocalizationKey: " + notification.getBodyLocalizationKey()
  26.                + "\n getBodyLocalizationArgs: " + Arrays.toString(notification.getBodyLocalizationArgs())
  27.                + "\n getIcon: " + notification.getIcon()
  28.                + "\n getSound: " + notification.getSound()
  29.                + "\n getTag: " + notification.getTag()
  30.                + "\n getColor: " + notification.getColor()
  31.                + "\n getClickAction: " + notification.getClickAction()
  32.                + "\n getChannelId: " + notification.getChannelId()
  33.                + "\n getLink: " + notification.getLink()
  34.                + "\n getNotifyId: " + notification.getNotifyId());
  35.    }
  36.    Boolean judgeWhetherIn10s = false;
  37.    // If the messages are not processed in 10 seconds, the app needs to use WorkManager for processing.
  38.    if (judgeWhetherIn10s) {
  39.        startWorkManagerJob(message);
  40.    } else {
  41.        // Process message within 10s
  42.        processWithin10s(message);
  43.    }
  44. }
  45. private void startWorkManagerJob(RemoteMessage message) {
  46.    Log.d(TAG, "Start new job processing.");
  47. }
  48. private void processWithin10s(RemoteMessage message) {
  49.    Log.d(TAG, "Processing now.");
  50. }

2.4 设置是否显示通知栏消息

2.4.1 场景介绍

通知消息是由系统直接在通知中心下拉列表呈现的即时消息。开发者如果想控制应用是否允许显示通知栏消息,可以调用HmsMessaging.turnOnPush或者HmsMessaging.turnOffPush接口。如果开发者不调用此接口,系统默认是允许显示通知栏消息。

注意:

1、华为设备上该功能支持的EMUI版本不低于EMUI 5.1。

2、集成HMS SDK最新版本需要满足HMS Core(APK)的版本不低于3.0.0。如果低于3.0.0版本,当应用有前台界面时HMS SDK会提示用户升级HMS Core(APK)为最新版本,此时HmsMessaging.getInstance的入参必须传入Activity类实例;当应用是后台应用时,如果开发者不想弹出引导升级的页面时,可以传入非Activity类型的Context类实例。

2.4.2 开发步骤

设置是否接收Push通知栏消息调用示例。

  1. HmsMessaging.getInstance(context).turnOnPush().addOnCompleteListener(new OnCompleteListener<Void>() {
  2.    @Override
  3.    public void onComplete(Task<Void> task) {
  4.        if (task.isSuccessful()) {
  5.            Log.i(TAG, "turnOnPush Complete");
  6.        } else {
  7.            Log.e(TAG, "turnOnPush failed: ret=" + task.getException().getMessage());
  8.        }
  9.    }
  10. });

2.5 点击通知消息

关于通知消息,开发者可以自定义点击消息的动作,包括:打开App首页、打开特定URL、打开自定义富媒体消息、打开自定义App页面。其中打开App首页与自定义App页面需要开发者端云协同开发来完成,下面我们讲下如何实现这两种动作并从服务端接收数据。

注意:

1、点击动作触发打开应用首页或者自定义App页面,都是通过跨应用启动Activity的方式来实现,需要保证要拉起的目标Activity的exported属性为true,即目标Activity要对外界公开,并且无权限保护,这样才能启动成功。

2、通过通知消息传递数据给应用需要推送服务App版本为10.0.0及以上。

2.5.1 打开自定义App页面

打开自定义App页面有两种方式,一种是通过服务端REST API指定intent参数,另一种是指定action参数。如果同时指定了两个参数,会优先选取使用intent参数。

指定intent参数

跨应用启动Activity必须使用显式intent方式,通过intent可以携带额外的数据给客户端App。主要通过如下4步来完成。

1、intent参数生成

在AndroidStudio工程中参照如下代码生成intent。

  1. Intent intent = new Intent(Intent.ACTION_VIEW);
  2. // Scheme协议(pushscheme://com.huawei.codelabpush/deeplink?)需要开发者自定义
  3. intent.setData(Uri.parse("pushscheme://com.huawei.codelabpush/deeplink?"));
  4. // 往intent中添加参数,用户可以根据自己的需求进行添加参数
  5. intent.putExtra("name", "abc");
  6. intent.putExtra("age", 180);
  7. // 必须带上该Flag
  8. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  9. String intentUri = intent.toUri(Intent.URI_INTENT_SCHEME);
  10. // 打印出的intentUri值就是设置到推送消息中intent字段的值
  11. Log.d("intentUri", intentUri);

开发者传参有两种方法:

方法1:参数之间用“&”相连,例如:

  1. intent.setData(Uri.parse("pushscheme://com.huawei.codelabpush/deeplink?name=abc&age=180"));

其中参数name=abc与age=180两个参数之间用“&”相连。

方法2:intent直接添加参数:

  1. intent.setData(Uri.parse("pushscheme://com.huawei.codelabpush/deeplink?"));
  2. intent.putExtra("name", "abc");
  3. intent.putExtra("age", 180);

2、App服务端消息体中指定intent值

服务端如何发送消息请参考服务端开发指导。消息体样例:

  1. {
  2.    "message": {
  3.        "notification": {
  4.            "title": "message title",
  5.            "body": "message body"
  6.        },
  7.        "android": {
  8.            "notification": {
  9.                "click_action": {
  10.                    "type": 1,
  11.                    "intent": "intent://com.huawei.codelabpush/deeplink?#Intent;scheme=pushscheme;launchFlags=0x4000000;i.age=180;S.name=abc;end"
  12.                }
  13.            }
  14.        },
  15.        "token": [
  16.            "pushtoken1"
  17.        ]
  18.    }
  19. }

3、客户端AndroidManifest.xml文件注册待启动的Activity类

参照如下配置注册自定义的Activity,其中host、path、scheme需与步骤1中协议的配置相对应,否则不能跳转到指定的界面。

  1. <activity android:name=".DeeplinkActivity">
  2.    <intent-filter>
  3.        <action android:name="android.intent.action.VIEW" />
  4.        <category android:name="android.intent.category.DEFAULT" />
  5.        <category android:name="android.intent.category.BROWSABLE" />
  6.        <data
  7.            android:host="com.huawei.codelabpush"
  8.            android:path="/deeplink"
  9.            android:scheme="pushscheme" />
  10.    </intent-filter>
  11. </activity>

4、在自定义Activity类中接收数据

  1. public class DeeplinkActivity extends Activity {
  2.    @Override
  3.    public void onCreate(Bundle savedInstanceState) {
  4.        super.onCreate(savedInstanceState);
  5.        setContentView(R.layout.activity_deeplink);
  6.        Intent intent = getIntent();
  7.        if (null != intent) {
  8.            // 方法1设置的数据通过如下方式获取
  9.            String name1 = intent.getData().getQueryParameter("name");
  10.            int age1 = Integer.parseInt(intent.getData().getQueryParameter("age"));
  11.            // 方法2设置的数据通过如下方式获取
  12.            String name2 = intent.getStringExtra("name");
  13.            int age2 = intent.getIntExtra("age", -1);
  14.            Toast.makeText(this, "name " + name1 + ",age " + age1, Toast.LENGTH_SHORT).show();
  15.        }
  16.    }
  17. }

指定action参数

指定的action参数需要与客户端AndroidManifest.xml文件中注册启动的Activity类中intent-filter标签中设置的action名一致。

1、客户端AndroidManifest.xml文件注册待启动的Activity类

  1. <activity android:name=".YourActivity">
  2.    <intent-filter>
  3.        <action android:name="com.huawei.codelabpush.intent.action.test" />
  4.        <category android:name="android.intent.category.DEFAULT" />
  5.    </intent-filter>
  6. </activity>

2、服务端消息体中指定action值

  1. {
  2.    "message": {
  3.        "data": "{'score': '7','time': '16:42'}",
  4.        "notification": {
  5.            "title": "message title",
  6.            "body": "message body"
  7.        },
  8.        "android": {
  9.            "data": "{'androidData': '7','time': '16:42'}",
  10.            "notification": {
  11.                "click_action": {
  12.                    "type": 1,
  13.                    "action": "com.huawei.codelabpush.intent.action.test"
  14.                }
  15.            }
  16.        },
  17.        "token": [
  18.            "pushtoken1"
  19.        ]
  20.    }
  21. }

3、在自定义Activity类中接收数据

具体实现参考打开App首页中的接收数据示例代码。

注意:

1、支持action参数需要EMUI 10.0.0以上,推送服务App版本为10.1.0及以上。

2、Push SDK 4.0版本支持将action参数与intent参数通过SDK提供的RemoteMessage.Notification.getClickAction()、RemoteMessage.Notification.getIntentUri()方法直接透传给应用,由应用自己处理。

2.5.2 打开App首页

通过如下3步实现打开App首页并携带数据给App。

1、服务端发送通知消息

通过Push服务端提供的REST API中Message结构体的data字段携带数据,click_action指定点击行为。如果message.android.data存在会覆盖message.data字段值。服务端如何发送消息请参考服务端开发指导。

消息体样例:

  1. {
  2.    "message": {
  3.        "data": "{'score': '7','time': '16:42'}",
  4.        "notification": {
  5.            "title": "message title",
  6.            "body": "message body"
  7.        },
  8.        "android": {
  9.            "data": "{'androidData': '7', 'time': '16:42'}",
  10.            "notification": {
  11.                "click_action": {
  12.                    "type": 3
  13.                }
  14.            }
  15.        },
  16.        "token": [
  17.            "pushtoken1"
  18.        ]
  19.    }
  20. }

2、客户端AndroidManifest.xml文件注册主Activity

AndroidManifest.xml文件中主Activity声明参考如下,点击后确保能拉起主界面。
  1. <activity android:name=".MainActivity">
  2.    <intent-filter>
  3.        <action android:name="android.intent.action.MAIN" />
  4.        <category android:name="android.intent.category.LAUNCHER" />
  5.    </intent-filter>
  6. </activity>
3、客户端App主Activity中接收数据

在MainActivity类的onCreate方法中实现数据读取。

  1. public class MainActivity extends Activity {
  2.    @Override
  3.    public void onCreate(Bundle savedInstanceState) {
  4.        super.onCreate(savedInstanceState);
  5.        setContentView(R.layout.activity_main);
  6.        getIntentData(getIntent());
  7.    }
  8.    private void getIntentData(Intent intent) {
  9.        if (null != intent) {
  10.            // 开发者可以通过如下三行代码获取的值做打点统计
  11.            String msgid = intent.getStringExtra("_push_msgid");
  12.            String cmdType = intent.getStringExtra("_push_cmd_type");
  13.            int notifyId = intent.getIntExtra("_push_notifyid", -1);
  14.            Bundle bundle = intent.getExtras();
  15.            if (bundle != null) {
  16.                for (String key : bundle.keySet()) {
  17.                    String content = bundle.getString(key);
  18.                    Log.i(TAG, "receive data from push, key = " + key + ", content = " + content);
  19.                }
  20.            }
  21.            Log.i(TAG, "receive data from push, msgId = " + msgid + ", cmd = " + cmdType + ", notifyId = " + notifyId);
  22.        } else {
  23.            Log.i(TAG, "intent is null");
  24.        }
  25.    }
  26. }

2.6 自动初始化

Push SDK 4.0版本对开发者提供了自动生成AAID,自动申请token的能力。配置本能力后,申请的token以onNewToken()回调的方式返回。我们提供了两种配置自动初始化的方式。方式1通过在AndroidManifest.xml中添加meta-data元数据来实现(推荐使用),方式2通过HmsMessaging类提供的setAutoInitEnabled(boolean enable)方法来实现。

方式1

在application标签添加<meta-data>元数据,参考如下,meta-data元数据name不可改变,值为true即为启用了自动初始化。值为false即代表未启用自动初始化。

  1. <application    
  2.    <meta-data      
  3.        android:name="push_kit_auto_init_enabled"        
  4.        android:value="true" />
  5. </application>

方式2

应用显式调用HmsMessaging.setAutoInitEnabled(boolean enable)方法,设置为true即为启用了自动初始化,反之为未启用。设置后值会保存在应用本地的shared_prefs目录下的文件中。

如果两种方式同时设置会不会有冲突?

Push SDK自身判断是否能自动初始化的依据是先看应用本地shared_prefs目录下的文件中是否保存了对应key(push_kit_auto_init_enabled)的配置信息,如果存在,那么会直接跳过meta-data元数据的读取。如果应用本地文件中没有对应key的配置信息,才进行meta-data元数据的读取。

SDK判断处理逻辑如下图:

2.7 客户端测试

至此开发者已经完成了Push功能客户端的基础功能开发,开发者可以尝试将自己的android工程打包成apk安装到手机中进行测试,打包的时候请确保使用了正确的证书指纹(请参考生成签名证书指纹)。

开发者可以参考Push控制台发送消息来确认Push消息能否正确送达客户端。如需实现服务端自定义发送Push消息,请参考服务端开发指导章节。

3. 服务端开发指导

我们支持HTTPS协议接入Push服务端,HTTPS协议通道提供上行消息和下行消息能力,上行消息能力请参考发送上行消息章节。本章节我们介绍下发送下行消息的能力。

注意:
应用服务器在发送消息之前需要先获取应用级AT(Access Token),获取AT请参考:开放平台鉴权-客户端密码模式。

基于HTTPS协议的下行消息交互流程图如下:

主要包含如下几个开发步骤:

1、开发者服务器获取接入令牌Access Token,详细请参考开放平台鉴权-客户端密码模式。

2、开发者服务器调用API发送Push消息,详细请参考服务端发送Push消息。

典型通知栏消息示例报文:

  1. {
  2.    "validate_only": false,
  3.    "message": {
  4.        "notification": {
  5.            "title": "title",
  6.            "body": "body"
  7.        },
  8.        "android": {
  9.            "notification": {
  10.                "title": "android title",
  11.                "body": "android body",
  12.                "click_action": {
  13.                    "type": 1,
  14.                    "intent": "#Intent;compo=com.rvr/.Activity;S.W=U;end"
  15.                }
  16.            }
  17.        },
  18.        "token": [
  19.            "pushtoken1",
  20.            "pushtoken2"
  21.        ]
  22.    }
  23. }
说明:
通知栏消息中,如果设置了message.android.notification.title和message.android.notification.body,则会分别覆盖message.notification.title和message.notification.body。

典型透传消息示例报文:

  1. {
  2.    "validate_only": false,
  3.    "message": {
  4.        "data": "{'param1':'value1','param2':'value2'}",
  5.        "token": [
  6.            "pushtoken1",
  7.            "pushtoken2"
  8.        ]
  9.    }
  10. }

3、开发者应用服务器接收Push消息回执,详细请参考消息回执章节。