Android Notes | 集成推送那點事(友盟/Mob(Flutter)/FCM)

咱們都曾羨慕別人,卻忘了,咱們也曾是別人羨慕的咱們。java

在這裏插入圖片描述

前言

最近的任務吶,真是讓人蛋碎一地,各類被錘。android

不過比較 nice 的是,推送湊齊了,能夠整理一篇咯~json

點滴積累吧。緩存

跟着老大~bash

前期調研

移動端發展到如今,各類推送 SDK 真的琳琅滿目,讓人看花了眼。app

這裏就挑我用過的幾個來作一個簡單對比,畢竟雞老大說了,連基本的論證對比都沒,你還玩個錘子(我瞎編的)。maven

下面從我我的關注的幾個維度進行簡單的對比 (❌:表明不支持,✅:表明支持。特殊狀況單獨註明)ide

類別 極光 友盟 Mob
離線(廠商通道) 免費服務不支持,高級版以及私有云支持(華爲、小米、vivo、OPPO、魅族、FCM) 支持小米、華爲、魅族、OPPO、vivo系統級下發通道 支持華爲、小米、魅族、OPPO、vivo廠商級通道以及 FCM
是否支持設置別名
Flutter 支持

對於小司而言,價格是一個重點,真的賊羨慕動不動就開通 VIP 或者 Pro 的小夥伴,酸了。測試

針對以上三種,我的以爲:gradle

  • 若是當前應用僅僅在線推送,極光、Mob、友盟均可以;
  • 若是當前應用僅僅支持國內並且還要支持離線推送,那麼友盟以及 Mob 不二人選;
  • 若是土豪級別應用,又支持國外,那麼直接極光 VIP/Pro 走起,一鍵式支持國內廠商以及 FCM 海外推送;
  • 反之,想支持海外,老老實實接入 FCM 吧。

有點茫然,明明看着 Mob 支持 FCM,可是官網上卻沒看到寫。

對了,今天偶然看到小夥伴對極光推送的一些討論:

好壞很少說,純技術分享,不涉及其餘東西,自行選擇吧。

沒轍,一分錢,難倒英雄漢!還好,哈哈哈。

因爲項目私密性,這裏就不放置對應的效果圖了。PS:其實我仍是蠻喜歡放個效果圖的,至少一上來就能看到效果,But 涉密,阿哦~

1、友盟廠商申請對應 key

因爲我司帳號問題,沒法集入全部廠商,尷尬啊。

畢竟雞老大也說過,不對未接觸的事物發表任何意見。

因此這裏按照友盟所須要對應廠商資料進行依次註冊填入便可:

推薦幾個不錯的廠商推送指南:

圖忒多了,並且沒啥難點,按照集成對應平臺提供資料進行對應廠商註冊吧。

2、Android 原生集成 - 友盟 v6.0.5

當初採用友盟緣由以下:

  • 支持廠商推送
  • 方便運營小夥伴直接友盟查看全部數據

1.1 添加友盟依賴

前期在友盟平臺建立當前應用之類的就不談了,注意推送 Android 版須要綁定包名。

關於友盟推送須要離線廠商推送,因此涉及到大部分的配置項,這裏我直接提取一個 gradle,避免主 gradle 各類雜亂不堪。

首先咱們在項目根目錄下添加友盟的遠程庫:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()
        // 這裏
        maven { url 'https://dl.bintray.com/umsdk/release' }
    }
    dependencies {
        // 。。。 
    }
}

allprojects {
    repositories {
        flatDir {
            dirs 'libs'
        }
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
        // 這裏
        maven { url 'https://dl.bintray.com/umsdk/release' }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
複製代碼

隨後在 app 下建立友盟依賴的 gradle,這裏注意因爲我司開發者帳號緣由,並沒能集成全部廠商:

dependencies {
    // 友盟推送

    // 基礎組件庫依賴(必須) Push 605 版本必須升級新版本 Common SDK
    implementation 'com.umeng.umsdk:common:2.2.2'
    implementation 'com.umeng.umsdk:utdid:1.5.2'

    // 友盟 push 相關依賴(必須)
    implementation 'com.umeng.umsdk:push:6.0.5'

    implementation 'com.umeng.umsdk:alicloud-httpdns:1.2.5'
    implementation 'com.umeng.umsdk:alicloud-utils:1.1.5'
    implementation 'com.umeng.umsdk:alicloud_beacon:1.0.1'

    implementation 'com.umeng.umsdk:agoo-accs:3.3.8.8-open-fix2'
    implementation 'com.umeng.umsdk:agoo_networksdk:3.5.5'
    implementation 'com.umeng.umsdk:agoo_tlog:3.0.0.17'
    implementation 'com.umeng.umsdk:agoo_tnet4android:3.1.14.9'

    // ====== 廠商集成 Start

    // 小米 Push 通道
    implementation 'com.umeng.umsdk:xiaomi-push:3.7.0'
    implementation 'com.umeng.umsdk:xiaomi-umengaccs:1.1.4'

    // 華爲 Push 通道
    implementation 'com.umeng.umsdk:huawei-basetb:2.6.3.306'
    implementation 'com.umeng.umsdk:huawei-pushtb:2.6.3.306'
    implementation 'com.umeng.umsdk:huawei-umengaccs:1.2.4'

    // Oppo Push 通道
    implementation 'com.umeng.umsdk:oppo-push:2.0.2'
    implementation 'com.umeng.umsdk:oppo-umengaccs:1.0.6'

    // vivo Push 通道
    implementation 'com.umeng.umsdk:vivo-push:2.3.5'
    implementation 'com.umeng.umsdk:vivo-umengaccs:1.1.0'
}
複製代碼

最後在主 gradle 也就是 app 下的 gradle 添加對此依賴:

// 友盟推送
apply from: 'UMeng_Push.gradle'
複製代碼

1.2 初始化友盟推送並設置通知欄點擊動做

在 Application 中進行友盟推送的初始化以及點擊通知欄後操做:

private fun initUMengSettings() {
        // 初始化 SDK
        mContext?.let {
            UMConfigure.init(
                mContext,
                K_RELEASE_UMENG,
                it.getString(R.string.code_umeng),
                UMConfigure.DEVICE_TYPE_PHONE,
                K_UMENG_SECRET
            )
            initUMengPush(it)
        }
    }

    /**
     * 初始化友盟消息推送
     */
    private fun initUMengPush(context: Context) {
        // 獲取消息推送代理示例
        val pushAgent = PushAgent.getInstance(context)
        // 註冊推送服務,每次調用 register 方法都會回調該接口
        pushAgent.register(mIUmengRegisterCallback)
        // 設置點擊通知欄打開操做
        pushAgent.notificationClickHandler = mNotificationClickHandler

        initUMengPushSettings(pushAgent)
    }

    /**
     * 註冊推送服務
     */
    private val mIUmengRegisterCallback = object : IUmengRegisterCallback {
        override fun onSuccess(deviceToken: String?) {
            pwcLog("-------> 註冊成功:deviceToken:--------> $deviceToken")
        }

        override fun onFailure(s: String?, s1: String?) {
            pwcLog("-------> 註冊失敗:s ---> $s ||| s1 ---> $s1")
        }
    }

    /**
     * 點擊通知欄 後續操做
     */
    private val mNotificationClickHandler = object : UmengNotificationClickHandler() {

        /**
         * 處理用戶點擊通知欄消息
         */
        override fun dealWithCustomAction(context: Context?, uMessage: UMessage?) {
            super.dealWithCustomAction(context, uMessage)
            context ?: return
            uMessage ?: return
            // 後臺接口傳遞過來的參數都在 map 中
            val extraMap = uMessage.extra
            // 這裏演示下獲取倆個值
            val openType = extraMap["type"]
            val openID = extraMap["id"]
            // 。。。
        }

    }

    /**
     * 推送基礎信息配置
     */
    private fun initUMengPushSettings(pushAgent: PushAgent) {
        // 設置最多顯示通知條數 參數 number 能夠設置爲 0~10 之間任意整數。當參數爲 0 時,表示不合並通知;
        pushAgent.displayNotificationNumber = 0
        // 設置客戶端容許聲音提醒
        pushAgent.notificationPlaySound = MsgConstant.NOTIFICATION_PLAY_SDK_ENABLE
        // 設置客戶端容許呼吸燈點亮
        pushAgent.notificationPlayLights = MsgConstant.NOTIFICATION_PLAY_SDK_ENABLE
        // 設置客戶端容許震動
        pushAgent.notificationPlayVibrate = MsgConstant.NOTIFICATION_PLAY_SDK_ENABLE
        // 通知免打擾 SDK默認在「23:00」到「7:00」之間收到通知消息時不響鈴,不振動,不閃燈
        pushAgent.setNoDisturbMode(23, 0, 7, 0)
        // 設置冷卻時間 避免一分鐘內出現多條通知而被替換
        pushAgent.muteDurationSeconds = 600
    }
複製代碼

1.3 離線推送支持

在 Application 中對應初始化廠商通道便可:

/**
 * @author:heliquan
 * @date:2020-05-07
 * @desc:廠商推送
 */
class PushSDKBizImpl : AppInterface {

    private var mContext: Context? = null

    override fun onCreate(knowledgeCore: KnowledgeCore) {
        if (mContext == null) {
            mContext = knowledgeCore.applicationContext
        }
        registerPush(knowledgeCore)
    }

    private fun registerPush(knowledgeCore: KnowledgeCore) {
        // 小米 Push register
        MiPushRegistar.register(mContext, K_XIAOMI_ID, K_XIAOMI_KEY)

        // 華爲 Push register
        HuaWeiRegister.register(knowledgeCore)

        // OPPO Push register
        OppoRegister.register(mContext, K_OPPO_KEY, K_OPPO_SECRET)

        // Vivo Push register
        VivoRegister.register(mContext)
    }

    // 。。。
}
複製代碼

不少時候咱們都但願,即便用戶當前未使用 App,或者說當前的 App 處於被殺死的狀態,後臺推送消息依然想被前臺接收。

那麼若是想實現離線推送,咱們還要完善下面的一步:

import com.umeng.message.UmengNotifyClickActivity
import org.android.agoo.common.AgooConstants

/**
 * @author HLQ_Struggle
 * @date 2020/5/7
 * @desc
 * 小米、華爲等對後臺進程作了諸多限制。若使用一鍵清理,應用的channel進程被清除,將接收不到推送。經過接入托管彈窗功能,可有效防止以上狀況,增長推送消息的送達率。 
 */
class PushActivity : UmengNotifyClickActivity() {

    private val mSelfActivity = PushActivity@ this

    override fun onMessage(intent: Intent?) {
        super.onMessage(intent)
        // 拿到數據 AgooConstants.MESSAGE_BODY 進行對應後續操做
        val offlinePushBean =
            intent?.getStringExtra(AgooConstants.MESSAGE_BODY)?.let {
                GsonUtil.fromJson(
                    it,  // 這裏須要將獲取到的json再次進行格式化
                    object : TypeToken<OfflinePushBean>() {
                    })
            }
        // 例如我這裏直接跳轉啓動頁,而後慢慢跳轉目標頁
        offlinePushBean?.extra?.let {
            val intent = Intent(mSelfActivity, SplashActivity::class.java)
            intent.putExtra(K_OFFLINE_PUSH_ID, it.id)
            intent.putExtra(K_OFFLINE_PUSH_TYPE, it.type)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            startActivity(intent)
            finish()
        }
    }

}
複製代碼

固然 AndroidManifest 此 Activity 內容附上:

<activity
            android:name=".ui.activity.push.PushActivity"
            android:exported="true"
            android:launchMode="singleTask"
            android:theme="@style/FullScreenTransparentTheme" />
複製代碼

到此,友盟 Android 集成推送已完成~

3、Android 原生集成 - FCM

Google 爸爸 GCM 集成的真的是賊貼心,業界楷模啊。

我不吹,你本身瞧~

附上 FCM 地址:

須要注意的幾點:

  • 記得測試的時候,ke xue 上網,我以前就遇到這麼一個狀況,顯示發送了,結果 App 收不到,最後才反應過來,沒有 ke xue 上網。
  • 國內的手機通常沒有 Google 全家桶,或者說 Google 服務,須要去豌豆莢中下載。

3.1 FCM 前期配置

首先不可避免的,建立項目:

這裏須要注意建立項目的一個規則:

  • 項目名稱必須至少包含 4 個字符只能包含字母、數字、空格和如下字符:-!'"

按照要求輸入項目名稱,勾選接收條款:

添加 Google Analytics 分析:

第三步勾選對應的條款,完成項目建立:

建立期間還有個小進度,賊好看:

建立的速度很快:

3.2 FCM 集成

進入首頁後,點擊 Android 圖標,開始 Android 接入/集成相關工做:

一共有以下四步:

  • 第一步填寫對應包名以及 SHA-1,反之我是都填了。

  • 第二步下載配置文件並拷貝到 app 目錄下:

  • 第三步添加對應的 SDK

  • 第四步運行驗證,可忽略

固然 Google 也爲咱們提供了一鍵式的配置,可是尷尬的是,我嘗試失敗了,不過也算是一種方式,具體文章內容以下:

這裏爲了偷個懶,直接一張圖展現了:

3.3 FCM 消息處理

在 app build 中完善下依賴:

// FCM
    implementation 'com.google.firebase:firebase-analytics:17.4.4'
    // FCM Message 處理
    implementation 'com.google.firebase:firebase-messaging:20.2.2'
    // FCM Message 後臺處理
    implementation 'com.google.firebase:firebase-messaging-directboot:20.2.2'
複製代碼

隨後建立一個 Service 用於處理 FCM 消息,這裏我直接採用了接收到 Google FCM 消息後手動建立一個通知:

/**
 * @author HLQ_Struggle
 * @date 2020/7/8
 * @desc
 */
class MyFirebaseMessagingService : FirebaseMessagingService() {

    /**
     * 處理 FCM 消息
     */
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        Log.d(TAG, "From: ${remoteMessage.from}")

        // Check if message contains a data payload.
        if (remoteMessage.data.isNotEmpty()) {
            Log.e(TAG, "Message data payload: ${remoteMessage.data}")
            // 這裏包含後臺傳遞自定義的值
            val remoteMessageMap = remoteMessage.data

            sendNotification(
                remoteMessageMap["type"].toString(),
                remoteMessageMap["title"].toString(),
                remoteMessageMap["content"].toString()
            )
        }

        remoteMessage.notification?.let {
            Log.e(TAG, "Message Notification Body: ${it.body}")
        }

    }

    /**
     * 令牌更新回調 FCM 沒有設置別名這麼一說 因此須要咱們經過令牌的方式去指定推送
     */
    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")
        sendRegistrationToServer(token)
    }

    /**
     * 保存令牌
     */
    private fun sendRegistrationToServer(token: String?) {
        Log.d(TAG, "sendRegistrationTokenToServer($token)")
    }

    /**
     * Create and show a simple notification containing the received FCM message.
     *
     * @param messageBody FCM message body received.
     */
    private fun sendNotification(type: String, title: String, content: String) {

        val intent = Intent(this, SplashActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        val pendingIntent = PendingIntent.getActivity(
            this, 0 /* Request code */, intent,
            PendingIntent.FLAG_ONE_SHOT
        )

        val channelId = getString(R.string.default_notification_channel_id)
        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setContentTitle(title)
            .setContentText(content)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setContentIntent(pendingIntent)

        val notificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "Channel human readable title",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build())
    }

    companion object {

        private const val TAG = "MyFirebaseMsgService"
    }
}
複製代碼

在 AndroidManifest 中 Service:

<service
    android:name=".service.MyFirebaseMessagingService"
    android:directBootAware="true"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
複製代碼

3.4 FCM 其它配置

固然還有一些可修改的內容,例如:

  • icon
  • notification_color

我是直接採用默認的了。這裏官網找到的,貼出來,避免小夥伴有需求還的找。

<meta-data
        android:name="com.google.firebase.messaging.default_notification_icon"
        android:resource="@drawable/appicon" />
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_color"
        android:resource="@color/styleTitleOrange" />
    <meta-data
        android:name="firebase_messaging_auto_init_enabled"
        android:value="false" />
    <meta-data
        android:name="firebase_analytics_collection_enabled"
        android:value="false" />
複製代碼

到此,FCM 完畢~

4、Flutter Android 集成 - Mob

此模塊在廠商相關信息完善時,集成僅僅幾分鐘~

相對於 Flutter 接入推送,不得不說 Mob 作的賊優秀,直接 Flutter 插件搞起,大大的方便了 Flutter 開發者,先比個當心心~ ❤️

附上 Mob 插件地址:

以及對應 Flutter 的集成指南:

Mob 的文檔,真的是良心,集成賊簡單,入手超級方便,一塊兒來看。

2.1 添加 Mob 插件依賴

  • mobpush_plugin: ^1.1.5

2.2 配置 Android 基本環境

首先,根目錄下的 build 文件添加以下:

dependencies {
    // 。。。
    classpath 'com.mob.sdk:MobSDK:+'
}
複製代碼

其次,app 下 build 文件添加對應的配置項,這裏關於 Mob 的配置可單獨提取一個 gradle 文件,這裏當初爲了實現而實現,就不抽離了。

導入插件:

apply plugin: 'com.android.application' // 通常項目自帶有這個,因此這塊的這個能夠忽略
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.mob.sdk'
複製代碼

Mob 推送的相關配置:

  • 基礎的 appKey 以及 appSecret
  • 廠商對應的 key 以及其它信息

代碼以下:

MobSDK {
    appKey "Mob 提供的 App Key"
    appSecret "Mob 提供的 App Secret"

    // 配置MobPush
    MobPush {
        // 配置廠商推送(可選配置,不須要廠商推送可不配置,須要哪些廠商推送只需配置哪些廠商配置便可)
        devInfo {
            // 配置小米廠商推送
            XIAOMI {
                appId "您的小米平臺appId"
                appKey "您的小米平臺appKey"
            }

            // 配置華爲廠商推送
            HUAWEI {
                appId "您的華爲平臺appId"
            }

            // 配置魅族廠商推送
            MEIZU {
                appId "您的魅族平臺appId"
                appKey "您的魅族平臺appKey"
            }

            // 配置FCM廠商推送
            FCM {
                // 設置默認推送通知顯示圖標
                iconRes "@mipmap/default_ic_launcher"
            }

            // 配置 OPPO 廠商推送
            OPPO {
                appKey "您的OPPO平臺appKey"
                appSecret "您的OPPO平臺appSecret"
            }

            // 配置VIVO廠商推送
            VIVO {
                appId "您的VIVO平臺appId"
                appKey "您的VIVO平臺appKey"
            }
        }
    }
}
複製代碼

接着去 MainActivity 中註冊下,通常也無需操做,我這裏是以前寫過一個通道,附上部分代碼:

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity : FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine) 
    }

}
複製代碼

最後就是對於初始化 Mob 以及接受到 Mob 消息推送如何處理了,蠻簡單的,這裏說下幾個點吧:

  • 因爲項目需求設置以用戶名爲別名,因此也涉及到了添加別名這個操做,而在這裏則是本地維護了一個狀態,避免屢次設置重複別名;
  • 其次需求是接收到消息推送執行刷新操做,因此我在這裏直接接收到推送消息後經過 eventBus 發送狀態去執行數據更新操做了。

如下是部分代碼,僅供參考:

/// 目前僅支持 Android 端推送
  void initMobPush() {
    getCacheValue(memberInfo).then((userCache) {
      MemberInfoBean memberInfoBean = memberInfoBeanFromJson(userCache);
      if (Platform.isAndroid) {
        // 設置隱私協議受權狀態
        MobpushPlugin.updatePrivacyPermissionStatus(true);

        getCacheValue(pushAliaState).then((result) {
          LogUtil.e(" ===> 獲取 Mob 註冊狀態");
          if (result == null) {
            LogUtil.e(" ===> Mob 未註冊 須要註冊");
            MobpushPlugin.setAlias(memberInfoBean.memberInfo.id)
                .then((Map<String, dynamic> map) {
              LogUtil.e(" ===> 設置 Mob 推送別名: -> res: ${map['res']} ");
              if (map['errorCode'] == '0') {
                // 註冊成功 本地緩存狀態
                setCacheValue(bool, pushAliaState, true);
              }
            });
          }
        });

        // 添加推送回調
        MobpushPlugin.addPushReceiver(_onEvent, _onError);
      }
    });
  }

  void _onEvent(Object event) {
    LogUtil.e(' 接收到消息內容:$event');
    eventBus.fire(PushEvent(true));
  }

  void _onError(Object event) {}
複製代碼

End

本文內容較多,主要是整理前段時間遇到的問題,其實也不算啥問題吧,主要各類帳號前期準備不足,後期產品調整頻繁致使。

基本都附上了源碼。

菜雞一枚,歡迎各位大佬指正~

Thanks

相關文章
相關標籤/搜索