阿里云首页 移动推送

阿里云移动推送+ReactNative最佳实践

React Native(简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的UI框架React在原生移动应用平台的衍生产物,目前支持Android和iOS两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域。

近年来Web化已成为移动端开发的一大趋势,越来越多的开发者倾向于使用ReactNative等web框架来开发App,而阿里云移动推送同样支持ReactNative开发模式,接下来为大家介绍如何在ReactNative工程中集成阿里云移动推送服务。

1. 构建您的ReactNative

  • 如果您已有构建好的ReactNative工程,可直接跳过本节。

  • 如果您是第一次接触ReactNative,可以参考官方教程创建自己的第一个ReactNative应用。

2. 接入移动推送Android SDK

本节以ReactNative实例工程AwesomeProject作为实例工程为大家介绍移动推送Android SDK的接入步骤。

可先下载Demo示例工程,再结合本教程效果更佳。

2.1 接入推送SDK

ReactNative模式下接入移动推送SDK的方式和传统Android开发模式下接入SDK一样,相关接入方式可以参考:移动推送Android SDK接入指南,SDK版本号以接入指南为准,这里介绍Maven远程依赖具体写法,手动依赖请参见接入指南。

2.1.1 配置Maven库

在Android工程根目录(比如\AwesomeProject\android\)下build.gradle文件中配置maven库URL:

allprojects {
    repositories {
        jcenter()
        maven {
            url 'http://maven.aliyun.com/nexus/content/repositories/releases/'
}
}
}

2.1.2 添加依赖

在Android工程对应的module(比如\AwesomeProject\android\app\)下的build.gradle文件中添加对应依赖:

android {
......
    defaultConfig {
        applicationId "com.xxx.xxx"//包名,与控制台包名一直
......
        ndk {
//选择要添加的对应cpu类型的.so库。
//推送支持'arm64-v8a', 'armeabi', 'armeabi-v7a', 'x86', 'x86_64', 'mips', 'mips64'七种。
            abiFilters 'armeabi','x86'
}
......
}
......
}
dependencies {
......
    compile 'com.aliyun.ams:alicloud-android-push:3.1.12@aar'
    compile 'com.aliyun.ams:alicloud-android-utdid:2.5.1-proguard'
    compile 'com.aliyun.ams:alicloud-android-utils:1.1.6.4'
    compile 'com.aliyun.ams:alicloud-android-ut:5.4.3'
    compile 'com.aliyun.ams:alicloud-android-beacon:1.0.4.3'
// 或
    compile 'com.aliyun.ams:alicloud-android-push:3.1.12'
......
}

注意:如果在添加以上abiFilter配置之后编译或其他场景出现以下提示:

NDK integration is deprecated in the current plugin.Consider trying the new experimental plugin.

则在Android工程根目录(比如\AwesomeProject\android\)的gradle.properties文件中添加:

android.useDeprecatedNdk=true

2.1.3 AppKeyhe AppSecret配置

在Android工程的对应module目录(比如\AwesomeProject\android\app\src\main\)的AndroidManifest.xml中,application标签下设置appKey,appSecret:

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

2.2 JavaScript调用推送SDK Native接口

ReactNative模式下常常需要通过JavaScript调用推送SDK native接口,完整调用过程包含以下三个步骤。

2.2.1 创建PushModule模块

在Android工程目录的java代码目录中(比如\AwesomeProject\android\app\src\main\java\com\awesomeproject\),创建PushModule,继承自ReactContextBaseJavaModule,可参考以下代码:

publicclassPushModuleextendsReactContextBaseJavaModule{
privatestaticReactContext context;
publicPushModule(ReactApplicationContext reactContext){
super(reactContext);
        context = reactContext;
}

publicstaticReactContext getContext(){
return context;
}

//模块名,在JavaScript中调用相关方法时需要首先引入MPush模块
@Override
publicString getName(){
return"MPush";
}

@ReactMethod
publicvoid getDeviceId(Callback callback){
        callback.invoke(PushServiceFactory.getCloudPushService().getDeviceId());
}

@ReactMethod
publicvoid bindAccount(String account,finalCallback callback){
PushServiceFactory.getCloudPushService().bindAccount(account,newCommonCallback(){
@Override
publicvoid onSuccess(String s){
                callback.invoke("bind account success");
}

@Override
publicvoid onFailed(String s,String s1){
                callback.invoke("bind account failed. errorCode:"+ s +", errorMsg:"+ s1);
}
});
}
......

}
  • getName(): 这个方法用于在JavaScript端标记这个模块。这里我们把这个模块命名为MPush,这样就可以在JavaScript中通过NativeModules.MPush访问到这个模块。

  • @ReactMethod:要导出一个方法给JavaScript使用,Java方法需要使用注解 @ReactMethod,方法的返回类型必须为void。React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件。

2.2.2 注册模块

在Android工程目录的java代码目录中(比如\AwesomeProject\android\app\src\main\java\com\awesomeproject\),创建一个Package类 PushPackage并实现ReactPackage,在createNativeModules方法中添加这个模块。

publicclassPushPackageimplementsReactPackage{
@Override
publicList<NativeModule> createNativeModules(ReactApplicationContext reactContext){
List<NativeModule> modules =newArrayList<>();
        modules.add(newPushModule(reactContext));
return modules;
}

@Override
publicList<Class<?extendsJavaScriptModule>> createJSModules(){
returnCollections.emptyList();
}

@Override
publicList<ViewManager> createViewManagers(ReactApplicationContext reactContext){
returnCollections.emptyList();
}
}

2.2.3 添加模块

在Android工程目录的java代码目录(比如\AwesomeProject\android\app\src\main\java\com\awesomeproject),Application(比如默认创建的是MainApplication extends Application implements ReactApplication)中的ReactNativeHost实例里getPackages方法添加PushPackage实例。

@Override
protectedList<ReactPackage> getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage(),
newPushPackage()
);
}

2.2.4 在JavaScript中调用Native方法

//引入MPush模块
var{NativeModules}=require('react-native');
var mPush =NativeModules.MPush;
exportdefaultclassAwesomeProjectextendsComponent{
......
//调用Native方法
  getDeviceId(){
var that =this;
      mPush.getDeviceId(function(args){
          that.setState({
              deviceIdBtnTitle: args
});
});
}
......
}

2.3 将推送消息传递到JavaScript

当终端接收到推送消息时需要将对应的消息发送到JavaScript,此时需要用到ReactNative的事件机制。

2.3.1 消息接收Receiver改造

在消息接收回调函数中添加事件发送逻辑,先在Android工程目录的java代码目录中(比如\AwesomeProject\android\app\src\main\java\com\awesomeproject\),创建自定义接收器,比如MyMessageReceiver继承com.alibaba.sdk.android.push.MessageReceiver。

这里以通知类型接收到时触发的onNotification,以及消息类型接收到时触发的onMessage为例,全部回调接口参见SDK API介绍

publicclassMyMessageReceiverextendsMessageReceiver{
publicMyMessageReceiver(){
super();
}

@Override
protectedvoid onMessage(Context context,CPushMessage cPushMessage){
super.onMessage(context, cPushMessage);
WritableMap params =Arguments.createMap();
        params.putString("messageId", cPushMessage.getMessageId());
        params.putString("content", cPushMessage.getContent());
        params.putString("title", cPushMessage.getTitle());
PushModule.sendEvent(getReactContext(),"onMessage", params);

}

@Override
protectedvoid onNotification(Context context,String s,String s1,Map<String,String> map){
super.onNotification(context, s, s1, map);
WritableMap params =Arguments.createMap();
        params.putString("content", s1);
        params.putString("title", s);
for(Map.Entry<String,String> entry: map.entrySet()){
            params.putString(entry.getKey(), entry.getValue());
}
        sendEvent(getReactContext(),"onNotification", params);
}

privatevoid sendEvent(ReactContext context,String eventName,@NullableWritableMap params){
if(context ==null){
Log.i(TAG,"reactContext==null");
}else{
            context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}

}

将该receiver添加到Android工程中对应module目录(比如\AwesomeProject\android\app\src\main\)的AndroidManifest.xml中:

<!-- 消息接收监听器(用户可自主扩展)-->
<receiver
android:name=".MyMessageReceiver"
android:exported="false"><!-- 为保证receiver安全,建议设置不可导出,如需对其他应用开放可通过android:permission进行限制 -->
<intent-filter>
<actionandroid:name="com.alibaba.push2.action.NOTIFICATION_OPENED"/>
</intent-filter>
<intent-filter>
<actionandroid:name="com.alibaba.push2.action.NOTIFICATION_REMOVED"/>
</intent-filter>
<intent-filter>
<actionandroid:name="com.alibaba.sdk.android.push.RECEIVE"/>
</intent-filter>
</receiver>

2.3.2 JavaScript监听相关事件

//导入相关模块
import{
AppRegistry,
MPush,
DeviceEventEmitter,
......
}from'react-native';

exportdefaultclassAwesomeProjectextendsComponent{
......
//绑定事件
    componentDidMount(){
DeviceEventEmitter.addListener('onMessage',this.onMessage);
DeviceEventEmitter.addListener('onNotification',this.onNotification);
}
//解绑事件
    componentWillUnmount(){
DeviceEventEmitter.removeListener('onMessage',this.onMessage);
DeviceEventEmitter.removeListener('onNotification',this.onNotification);
}

//事件处理逻辑
    onMessage(e){
        alert("Message Received. Title:"+ e.title +", Content:"+ e.content);
}
    onNotification(e){
        alert("Notification Received.Title:"+ e.title +", Content:"+ e.content);
}
}

2.4 初始化

2.4.1 在“2.2.3 添加模块”步骤的Application中初始化推送SDK:

publicclassMainApplicationextendsApplicationimplementsReactApplication{
privatestaticfinalString TAG =MainApplication.class.getName();
privatefinalReactNativeHost mReactNativeHost =newReactNativeHost(this){
@Override
publicboolean getUseDeveloperSupport(){
returnBuildConfig.DEBUG;
}

@Override
protectedList<ReactPackage> getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage()
newPushPackage()
);
}
};

@Override
publicReactNativeHost getReactNativeHost(){
return mReactNativeHost;
}

@Override
publicvoid onCreate(){
super.onCreate();
SoLoader.init(this,/* native exopackage */false);
this.initCloudChannel();
}

privatevoid initCloudChannel(){
PushServiceFactory.init(this.getApplicationContext());
CloudPushService pushService =PushServiceFactory.getCloudPushService();
    pushService.register(this.getApplicationContext(),newCommonCallback(){
@Override
publicvoid onSuccess(String s){
Log.e(TAG,"init cloudchannel success");
}

@Override
publicvoid onFailed(String s,String s1){
Log.e(TAG,"init cloudchannel failed. errorCode:"+ s +". errorMsg:"+ s1);
}
});
}
}

2.4.2 Android 8.0及以上NotificationChannel机制

Android 8+系统需要单独进行适配,客户端和服务端都有设备,详情请参见常见问题:Android 8.0以上设备通知接收不到

2.4.3 Android 9.0及以上需要设置允许http请求

详情请参见常见问题:只在Android9+系统报errorCode为10109的错误

3 接入移动推送iOS SDK

本节以ReactNative实例工程AwesomeProject作为实例工程为大家介绍iOS推送手动集成 SDK的步骤。

3.1 控制台下载SDK

参考:移动研发平台 EMAS > 快速入门的下载SDK章节。

3.2 SDK集成

  • 打开AwesomeProject/ios/AwesomeProject.xcodeproj,将推送iOS SDK拖进工程中。

  • Build Phases -> Link Binary With Libraries中,引入下列的公共包:

    • libz.tbd

    • libresolv.tbd

    • CoreTelephony.framework

    • SystemConfiguration.framework

    • libsqlite3.tbd(阿里云平台下载的SDK无需依赖,百川平台下载的SDK需要依赖)

说明

Targets -> Build Settings -> Linking -> Other Linker Flags,请加上-ObjC这个属性,否则推送服务无法正常使用。如果之前已经设置了force_load,需要设置-force_load <framework_path>/CloudPushSDK.framework/CloudPushSDK。

3.3 Xcode 设置

Xcode 8打开推送开关,TARGETS > Capabilitie > Push Notifications,并会自动在项目中生成.entitlement文件。

3.4 SDK配置

-(void)initCloudPush {
[CloudPushSDK asyncInit:@"*****" appSecret:@"*****" callback:^(CloudPushCallbackResult*res){
if(res.success){
NSLog(@"Push SDK init success, deviceId: %@.",[CloudPushSDK getDeviceId]);
}else{
NSLog(@"Push SDK init failed, error: %@", res.error);
}
}];
}
  • 向苹果APNs注册获取deviceToken并上报到阿里云推送服务器

/**
 *    注册苹果推送,获取deviceToken用于推送
 *
 *    @param     application
 */
-(void)registerAPNS:(UIApplication*)application {
if([[[UIDevice currentDevice] systemVersion] floatValue]>=8.0){
// iOS 8 Notifications
[application registerUserNotificationSettings:
[UIUserNotificationSettings settingsForTypes:
(UIUserNotificationTypeSound|UIUserNotificationTypeAlert|UIUserNotificationTypeBadge)
                                           categories:nil]];
[application registerForRemoteNotifications];
}
else{
// iOS < 8 Notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound)];
}
}
/*
 *  苹果推送注册成功回调,将苹果返回的deviceToken上传到CloudPush服务器
 */
-(void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
[CloudPushSDK registerDevice:deviceToken withCallback:^(CloudPushCallbackResult*res){
if(res.success){
NSLog(@"Register deviceToken success.");
}else{
NSLog(@"Register deviceToken failed, error: %@", res.error);
}
}];
}
/*
 *  苹果推送注册失败回调
 */
-(void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
NSLog(@"didFailToRegisterForRemoteNotificationsWithError %@", error);
}
  • 推送消息到来监听

/**
 * 注册推送消息到来监听
 */

-(void)registerMessageReceive {

[[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(onMessageReceived:) name:@"CCPDidReceiveMessageNotification"object:nil];
}

/**
 * 处理到来推送消息
 *
 * @param notification
 */
-(void)onMessageReceived:(NSNotification*)notification {
CCPSysMessage*message =[notification object];
NSString*title =[[NSString alloc] initWithData:message.title encoding:NSUTF8StringEncoding];
NSString*body =[[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding];
NSLog(@"Receive message title: %@, content: %@.", title, body);
}
  • 通知打开监听

-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
// 点击通知将App从关闭状态启动时,将通知打开回执上报
// [CloudPushSDK handleLaunching:launchOptions];(Deprecated from v1.8.1)
[CloudPushSDK sendNotificationAck:launchOptions];
return YES;
}

/*
 * App处于启动状态时,通知打开回调
 */

-(void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo {
NSLog(@"Receive one notification.");
// 取得APNS通知内容
NSDictionary*aps =[userInfo valueForKey:@"aps"];
// 内容
NSString*content =[aps valueForKey:@"alert"];
// badge数量
NSInteger badge =[[aps valueForKey:@"badge"] integerValue];
// 播放声音
NSString*sound =[aps valueForKey:@"sound"];
// 取得Extras字段内容
NSString*Extras=[userInfo valueForKey:@"Extras"];//服务端中Extras字段,key是自己定义的
NSLog(@"content = [%@], badge = [%ld], sound = [%@], Extras = [%@]", content,(long)badge, sound,Extras);
// iOS badge 清0
     application.applicationIconBadgeNumber =0;
// 通知打开回执上报
// [CloudPushSDK handleReceiveRemoteNotification:userInfo];(Deprecated from v1.8.1)
[CloudPushSDK sendNotificationAck:userInfo];
}

3.5 将推送消息传递到JavaScript

// 新创建 一个继承 RCTEventEmitter 的类
// SREventEmitter.h 文件
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
NS_ASSUME_NONNULL_BEGIN
@interfaceSREventEmitter:RCTEventEmitter<RCTBridgeModule>
+(void)postNotiToReactNative:(NSString*)str withDic:(NSDictionary*)dic;
@end
NS_ASSUME_NONNULL_END

// SREventEmitter.m 文件
#import "SREventEmitter.h"
@implementationSREventEmitter
RCT_EXPORT_MODULE();
-(NSArray<NSString*>*)supportedEvents {
return@[@"AliyunPush"];// 这里返回的将是你要发送的消息名的数组
}
-(void)startObserving {
[[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(emitEventInternal:)
                                                 name:@"event-emitted"
object:nil];
}
-(void)stopObserving {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void)emitEventInternal:(NSNotification*)notification {
NSLog(@"\n ===== notification  %@",notification);
  dispatch_async(dispatch_get_main_queue(),^{
[self sendEventWithName:@"AliyunPush"
                       body:notification.object];
});
}
+(void)postNotiToReactNative:(NSString*)str withDic:(NSDictionary*)dic {
NSMutableDictionary*tempDic =[NSMutableDictionary dictionary];
    tempDic[@"str"]= str;
    tempDic[@"dic"]= dic;
[[NSNotificationCenter defaultCenter] postNotificationName:@"event-emitted"object:tempDic];
}
@end

// AppDelegate.m 文件  
-(void)onMessageReceived:(NSNotification*)notification {
CCPSysMessage*message =[notification object];
NSString*title =[[NSString alloc] initWithData:message.title encoding:NSUTF8StringEncoding];
NSString*body =[[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding];
[SREventEmitter postNotiToReactNative:@"MessageReminder" withDic:@{@"title": title,@"body": body }];
}

// index.ios.js 文件
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
Text,
View,
Alert,
NativeModules,
NativeEventEmitter
}from'react-native';

var nativeBridge =NativeModules.SREventEmitter;
constNativeModule=newNativeEventEmitter(nativeBridge);

exportdefaultclassMyAppextendsComponent{
  render(){
return(
<View style={styles.container}>
<Text style={styles.welcome}>
ThisisPush iOS Demo!
</Text>
<Text style={styles.instructions}>
Toget started, edit index.js
</Text>
<Text style={styles.instructions}>
PressCmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
</View>
);
}
  componentDidMount(){
    console.log('here2')
this.subscription =NativeModule.addListener(
'AliyunPush',
(reminder)=>{
        console.log('here1')
        alert(reminder.str)
}
);
}
  componentWillUnmount(){
this.subscription.remove()
}
}
首页 移动推送 最佳实践 阿里云移动推送+ReactNative最佳实践