集成第三方推送最佳实践

IM篇 I (╯‵□′)╯︵┻━┻ 极光?个推?腾讯云?第三方推送平台该怎么选? 推送的意义 「推送机制」为我们提供了一条可以「直接触达用户」的路径,使移动设备终端的「全时信息传播」成为可能,是C端运营人员提高用户活跃度、提高应用留存率的常规途径之一,以助于其「更高效地完成运营目标」。 推送的技术实现难点 自建推送通道 主流的推送方案实现,是由客户端和服务端建立一条TCP长连接通道,并由客户端定期向服务端发送心跳包,以保持长连接可用。服务端有消息要推送时,即可通过这个长连接通道直接下发到客户端。 然而,要实现这样一套自建推送通道,却没有以上所描述的那样简单。且不说服务端需要应对海量长连 阅读详情

本文会不定期更新,推荐watch下项目。如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request。
本文的示例代码主要是基于作者的经验来编写的,若你有其他的技巧和方法可以参与进来一起完善这篇文章。

注意:本报告写于2016年04月25日,本文内容可能随着时间和技术的发展而产生错误和偏差。

本文固定连接:https://github.com/tianzhijiexian/Android-Best-Practices

一、需求背景

Android在国内没有办法利用系统级的推送服务,再加上第三方rom对于原生的各种定制(这里的定制并无褒贬之意)所以国内的开发者就需要选择一些成熟的推送平台做推送服务了。目前的推送平台种类繁多,本文将给出推送平台的具体对比和调研结果,方便后来人进行选择。因本人对于国外的推送服务没有了解,所以选择的是国内的推送平台。

二、需求

  • 调研国内推送平台的优劣
  • 给出技术方案和选型
  • 能快速集成推送服务

三、实现

调研信息一览

推送平台

  • 极光推送
  • 小米推送
  • 友盟推送
  • 个推
  • 阿里推送

参与测试的机型:

  • MI2 (android 5.1.1)
  • N5 (android 6.0.1)
  • MI4 (miui 7.2.4)
  • OPPO (colorOS 2.1)
  • MX4 (flymeOS 4.2.2.1c)
  • Samsung (android4.1.2)

因为测试的结果在相同的rom上表现一致,所以下文的测试结果做了简化,屏蔽了机型的细节。

调研结果

信鸽
我们的项目长期使用的是信鸽的服务。为什么写这篇文章,是因为信鸽完全不能满足高到达率的标准,所以对于腾讯信鸽,就不进行调研了。

个推
我先排除的是个推,排出它并非是因为他不好,而是因为个推本身就是收费平台,其优点就是服务全,数据颗粒度细,对开发者的响应也比较及时,但因为其收费的属性让我无法将其作为选择之一。有很多朋友也说到了,个推在用户量少的情况下是免费的。只是本文挑选的是全量免费的服务,所以就暂时不列举个推了。

阿里推送
阿里推送采用的是阿里云的一整套服务,但因为大公司的原因,所以会强制植入one sdk,这个东西真的是全家桶级别的东西,而且联系技术的方式是通过阿里旺旺让人心累。大而全的东西一向不是我的选择,而且未来阿里推送可能会有付费的趋势,因此我对它不做考虑。

友盟推送

友盟是做数据的老牌厂商,但其给我的感觉总是不舒服,之前做过友盟反馈,接入和使用总觉得不够优雅。但就推送来说,它的后台界面和提供的小工具、文档都是最美观实用的。
注意:
需要注意的是友盟的sdk引入了httpClient(官方已废弃)、okio等第三方库,我是认为完全没有必要的。我认为一个独立的sdk应该尽可能的保持轻量,过多的自带库会引起很多问题。如果你要引入友盟反馈的话,那么一定要注意它带来的几千个方法数!


推送后台

友盟采用了共享连接的技术方案,即多个采用友盟推送的App可以相互共用长连接,保证你的app被杀死后只要用户打开了采用友盟推送的app,你的app就可以利用兄弟app来接收推送。这个技术也被很多推送平台所采用,是一个很不错的方案,至于是否流氓?不可说。


共享长连接(将长连接挂在高德上)

测试结果:

 安卓原生MIUIColorOSflymeOSSamsung
app处于前台√√√√√
返回键退出√√√√√
杀掉app√××√(有延迟)√
手机重启后√××√(有延迟)√(有延迟)
打开兄弟App-×××-

(√:能收到推送,×:收不到推送,-:无意义的测试)

测试结果表明友盟对于原生的支持较好,可以做到在原生rom上不死,在flyme上面能有一定的几率复活,只是复活后有较大几率丢失之前的消息。

需要说明的是:
本次测试在flyme上在mx4和mx5上都是通过滑动来杀掉应用的,在mx5中,如果用顶部的“扫把”进行所有应用的杀死,那么友盟推送也无法到达了。


友盟进程

内存使用情况

sdk配置文件如下:


服务

通过代码分析,友盟的一些服务是在独立的进程(push)中运行的,这也是用来提升service存活率的通用方法,因此在debug下断点的时候需要选对进程!


广播接收器

我们可以明确的知道,友盟推送在监听到(可能在不同的rom有监听不到的情况)开机、网络改变、应用卸载这样的广播后就会自动复活,以便再应用被杀死后仍旧可以实时接收远端的推送信息。


兄弟应用

在测试中我发现在MIUI上即使打开了兄弟app(高德地图),我也收不到推送。后台工具告诉我长连接的service确实挂到了兄弟app上,至于为什么没办法接收到推送就不得而知了。

最后需要说下友盟的缺点,在service被杀死后有一定概率丢失消息,刚注册的设备是无法收马上到消息的,需要过一段时间才能收到测试消息,再过一段时间才能收到正式消息,总觉得不稳定。如果一条消息很久都没有收到,那么很可能会永久丢失。
再次提醒!友盟的sdk引入了4个库!带来了好几千个方法数!引入了已经被废弃的httpClient!

小米推送

小米的推送有一个很大的优势就是在MIUI上会直接使用系统的长连接,只要系统不死,那么你的app就有90%以上的概率接收到推送。当你的MIUI用户达到了一定的量级,你就必须要使用小米的推送。


更新日志

要吐槽的是小米的推送后台(网页端)经常出现打不开的情况,复现频率极高。

 安卓原生MIUIColorOSflymeOSSamsung
app处于前台√√√√√
返回键退出√√√√√
杀掉app√√××√
手机重启后√√××√(有延迟)
打开兄弟App--××-

(√:能收到推送,×:收不到推送,-:无意义的测试)

从结果来看小米推送是符合其文档描述的,能够在原生和MIUI上有良好的表现,所以小米推送可以被列入选择列表。


原生rom上的内存信息

MIUI上的内存信息

我发现小米在MIUI上会自动采用系统的服务,也就是说不用自身建立一个service,所以理论上要更加轻量省电。


小米推送配置文件

从代码来分析,小米会监听网络切换的广播来重启service,对于MIUI层面可能会有一个特殊的方法,不启动额外service,却可以handleMessage。


兄弟应用

对于能否通过兄弟应用来接收推送信息,我的测试结果是无法收到,不知道是不是bug。我只能说通过这样的方式来提升消息的接收率是不靠谱的。

极光推送

极光早期专注于推送服务,可以说沉淀很深,现在改名为“极光”后业务面更广了。极光对开发者提问的响应可以说是做的最好的。机关的Android文档里说到了各个功能的目的,不会像OneSDK(阿里全家桶)那样偷偷加入一些权限。文档也是走AS的风格,较为现代。不足之处就是后台比较古老,三年前就是这样丑丑的样子,发送操作有一个延迟的过程,没有友盟做的好。
极光也支持兄弟应用相互拉起的功能,从代码上看是1.8.0之后的版本开始支持的。

第三方系统收不到推送的消息的原因(摘自极光文档)
由于第三方 ROM 的管理软件需要用户手动操作
小米【MIUI】
自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启
通知栏设置:应用默认都是显示通知栏通知,如果关闭,则收到通知也不会提示
网络助手:可以手动禁止已安装的第三方程序访问2G/3G和WIFI的网络和设置以后新安装程序是否允许访问2G/3G和WIFI的网络
MIUI 7 神隐模式: 允许应用进行自定义配置模式,应用在后台保持联网可用,否则应用进入后台时,应用无法正常接收消息。【设置】下电量和性能中【神隐模式】

华为【Emotion】
自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程不会开启,只能手动开启应用
后台应用保护:需要手动把应用加到此列表,否则设备进入睡眠后会自动杀掉应用进程,只有手动开启应用才能恢复运行
通知管理:应用状态有三种:提示、允许、禁止。禁止应用则通知栏不会有任何提醒

魅族【Flyme】
自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启
通知栏推送:关闭应用通知则收到消息不会有任何展示
省电管理: 安全中心里设置省电模式,在【待机耗电管理】中允许应用待机时,保持允许,否则手机休眠或者应用闲置一段时间,无法正常接收消息。

VIVO【Funtouch OS】
内存一键清理:需要将应用加入【白名单】列表,否则系统自带的“一键加速”,会杀掉进程
自启动管理:需要将应用加入“i管家”中的【自启动管理】列表,否则重启手机后进程不会自启。但强制手动杀进程,即使加了这个列表中,后续进程也无法自启动。

OPPO【ColorOS】
冻结应用管理:需要将应用加入纯净后台,否则锁屏状态下无法及时收到消息
自启动管理:将应用加入【自启动管理】列表的同时,还需要到设置-应用程序-正在运行里锁定应用进程,否则杀进程或者开机后进程不会开启,只能手动开启应用

总之,它就是一个很实在的东西,对于android的权限和中国国情无能为力,它只能尽可能做到高的推送送达率。

 安卓原生MIUIColorOSflymeOSSamsung
app处于前台√√√√√
返回键退出√√√√√
杀掉app√(有延迟)×××√(有延迟)
手机重启后√×××√(有延迟)
打开兄弟App-×××-

(√:能收到推送,×:收不到推送,-:无意义的测试)

关于延迟:
延迟的原因是极光的服务被杀死后,是无法接收实时消息的,直到复活后才会继续工作,所以产生了消息延迟。这点在android原生和三星的rom上有明显的规律。在三星的手机上杀掉app->关屏->点亮屏幕后服务就会自动重启,我认为这个就是服务重启的方式之一。


内存信息

配置文件

极光的配置文件中详细说明了什么是用来测试的,什么是必选的,什么是可选的,而且是否将服务放入独立进程都是一个建议选项而非强制。整体的demo和文档给人的感觉是不流氓,却无奈。


兄弟应用

我打开了极光官方提供的兄弟app,但并不能接收到我测试app的推送消息。

结论

  • 在原生和MIUI上小米推送有很强的优势
  • 在三星的ROM上小米和友盟难分伯仲
  • 在魅族的Flyme中友盟有一定概率复活
  • 在OPPO的ColorOs中三家均无突出表现

整体结论是国外的rom推送成功率高,国内rom推送成功率低,国内定制真的是伤不起啊。

技术方案和选型

因为app有大量的miui用户,所以小米推送是必须被引入的,因为在flyme上友盟的表现较好,所以最终采用了小米和友盟共同接入app的方案。目前的策略是在miui上启用小米推送,不启用友盟推送;非miui上启用友盟,废弃小米推送。

判断用户rom是否是miui的方法:

public static boolean isMiUi() {
        return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name"));
    }

    public static String getSystemProperty(String propName) {
        String line;
        BufferedReader input = null;
        try {
            java.lang.Process p = Runtime.getRuntime().exec("getprop " + propName);
            input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
            line = input.readLine();
            input.close();
        } catch (IOException ex) {
            return null;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return line;
    }

因为推送服务是采用另一个进程,所以application会被启动多次,因此必须要判断是哪个进程启动的application,这样就可以让主进程才需要的配置工作对push进程屏蔽。开发者需要做的就是判断是否是主进程,如果是那么就执行原本application中的代码。

    /**
     * 注意:因为推送服务等设置为运行在另外一个进程,这导致本Application会被实例化两次。
     * 而有些操作我们需要让应用的主进程时才进行,所以用到了这个方法
     */
    public static boolean isInMainProcess(Context context) {
        ActivityManager am = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE));
        List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
        String mainProcessName = context.getPackageName();
        int myPid = Process.myPid();
        for (ActivityManager.RunningAppProcessInfo info : processes) {
            if (info.pid == myPid && mainProcessName.equals(info.processName)) {
                return true;
            }
        }
        return false;
    }

千万要注意!!!!!!!!
因为推送服务的复活会启动application,即唤醒应用。所以千万不要在application启动的时候进行网络请求的操作。因为一旦开始推送,大量用户的应用会被唤醒,然后再几分钟内会产生对于少数api的大量请求。后台服务很可能会被搞挂,所以千万要小心这点。这也就是为什么我强烈不推荐application中做很多事情的原因之一。可以考虑把这些请求转移到mainActivity中进行。

集成推送服务

目前我采用的是集成多个第三方推送,然后根据机型来注册不同的推送服务。后端不控制推送的平台。因为友盟的推送必须要一个module(我也不知道为啥),所以我索性把第三方推送平台都整个成了一个module。

注意:
友盟推送和友盟反馈会有冲突,现在友盟反馈已经不再维护了,所以强烈不建议使用友盟反馈。而且友盟推送和阿里的产品有冲突,所以如果产生了冲突,要下载去UTDID的SDK。


友盟和阿里冲突解决

友盟反馈和友盟推送冲突的解决方案(我才知道友盟反馈原来偷偷集成了推送服务):

compile('com.umeng:fb:5.4.0') {   
   transitive = true   
   exclude group: 'com.umeng', module: 'message'
}

module一览


一览

libs中集成了友盟所需要的so包(引入一个友盟的推送,要引入一堆东西,还各种冲突,这些so全是友盟的),此外还有小米的一个jar包。

一般我们不会把so放在lib中,都会采用新建一个main/jniLib文件夹来放,友盟采用了另一种方法来方,因此在build.gradle中出现了这样一段代码:


gradle

友盟文档中说,要引入我的推送,你就必须引入httpClient,因此我们必须在build.gradle中引入已经被谷歌废弃的httpClient!!!!!:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    // http://dev.umeng.com/push/android/integration
    // 如果是android6.0以上的api编译,需要在PushSDK的build.gradle文件的android{}块内添加:
    // useLibrary 'org.apache.http.legacy',并把compileSdkVersion的版本号改为23。
    useLibrary 'org.apache.http.legacy'

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 23
    }
}

友盟还使用了square家的东西,所以必须要引入以下两个依赖:


Paste_Image.png

到此为止,我终于引入了友盟的sdk。小米的sdk我就不说了,上面的图中也有,一个jar包搞定。

Manifest中的权限

因为各大推送平台都有自己的权限,但是如果混合到一起,以后想删除某个平台的代码都很难删,最终也不知道整个权限是谁加入的,有什么作用。因此我把各个平台的权限做了独立,这样做虽然在编译的时候会有重复定义权限的警告,但是并不影响最终的包,这点我通过反编译已经证实了,请放心。

    <!-- umeng必选 -->
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <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.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <!-- umeng可选 -->
    <uses-permission android:name="android.permission.BROADCAST_PACKAGE_ADDED" />
    <uses-permission android:name="android.permission.BROADCAST_PACKAGE_CHANGED" />
    <uses-permission android:name="android.permission.BROADCAST_PACKAGE_INSTALL" />
    <uses-permission android:name="android.permission.BROADCAST_PACKAGE_REPLACED" />
    <uses-permission android:name="android.permission.RESTART_PACKAGES" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />


    <!--小米推送开始-->
    <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_PHONE_STATE" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <permission
        android:name="${applicationId}.permission.MIPUSH_RECEIVE"
        android:protectionLevel="signature"
        />
    <uses-permission android:name="${applicationId}.permission.MIPUSH_RECEIVE" />
    <!--小米推送结束-->

关于Mainfest中的key

在配置文件中因为用到了key和具体项目的包名,而我希望整个push的module保持独立性,所以我最终将key用代码进行写入,而不是采用在manifest中定义data标签的方式。

编写推送管理类

因为集成了多个平台,所以东西多,东西一多就需要管理类。管理类做的事情很简单,注册服务,接收推送消息,展示通知。

/**
 * @author Kale
 * @date 2016/4/27
 */
public class PushManager {

    private static final String TAG = "PushManager";

    public static String tokenType; // token类型的缓存

    public static String token; // token的缓存

    @Retention(RetentionPolicy.SOURCE)
    @StringDef({PushType.MI, PushType.UMENG})
    public @interface PushType {

        String MI = "MI", UMENG = "UMENG";
    }

    public static void register(Context context) {
        if (Utils.isMiUi()) {
            registerMiPush(context);
        } else {
            registerUMengPush(context);
        }
    }

    /**
     * 注册小米push服务
     * 注册成功后会向{@link MiMessageReceiver}发送广播
     */
    private static void registerMiPush(Context context) {
        if (BuildConfig.DEBUG) {
            MiPushClient.checkManifest(context);
        }

        final String APP_ID = "xxxxxxxxxxxxxx";
        final String APP_KEY = "xxxxxxxxxxxxxx";

        if (DTUtil.isInMainProcess(context)) {
            MiPushClient.registerPush(context, APP_ID, APP_KEY);
        }

        LoggerInterface newLogger = new LoggerInterface() {
            @Override
            public void setTag(String tag) {
                // ignore
            }

            @Override
            public void log(String content, Throwable t) {
                P.e(content, t); // log
            }

            @Override
            public void log(String content) {
                P.d(content); // log
            }
        };
        Logger.setLogger(context, newLogger);

        if (!BuildConfig.DEBUG) {
            Logger.disablePushFileLog(context);
        }
    }

    /**
     * 注册友盟的推送服务
     */
    private static void registerUMengPush(final Context context) {
        PushAgent agent = PushAgent.getInstance(context);
        // 如果你用了友盟数据分析,那么这个key和友盟数据分析保持一致
        final String appKey = AnalyticsConfig.getAppkey(context); 
        final String appSecret = "xxxxxxxxxxxxxx";

        agent.setAppkeyAndSecret(appKey, appSecret);

        agent.setMessageChannel(AnalyticsConfig.getChannel(context));
        agent.setDebugMode(BuildConfig.DEBUG);
        agent.setPushCheck(BuildConfig.DEBUG);

        IUmengRegisterCallback callback = new IUmengRegisterCallback() {
            @Override
            public void onRegistered(final String deviceToken) {
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        //onRegistered方法的参数registrationId即是device_token
                        sendDeviceToken(context, PushType.UMENG, deviceToken);
                        handleRegisterResult(context, PushType.UMENG, true, null);
                    }
                });
            }
        };
        agent.enable(callback);
        // 通过两种方式获得token,这里确保尽可能能获得token
        // 第一次获得是需要联网的,获得后友盟会存放在sp中,因此产生两种方法。
        String deviceToken = UmengRegistrar.getRegistrationId(context);// 得到deviceToken
        if (deviceToken != null) {
            callback.onRegistered(deviceToken);
        }

        agent.setResourcePackageName("com.duitang.main");
        // 下面这段代码必须在application中调用
        agent.setMessageHandler(new UmengMessageHandler() {
            /**
             * 参考集成文档的1.6.3
             * http://dev.umeng.com/push/android/integration#1_6_3
             *
             * 若开发者需要处理自定义的透传,则需要将方法dealWithCustomMessage()重写,自定义消息的内容则存放在UMessage.custom里
             * */
            @Override
            public void dealWithCustomMessage(final Context context, final UMessage msg) {
                // http://dev.umeng.com/feedback/android/integration
                if (FeedbackPush.getInstance(context).dealFBMessage(new FBMessage(msg.custom))) {
                    // 如果是开发者回复友盟反馈的消息,那么就直接return
                    return;
                }

                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                       // 处理透传消息
                        PushNotificationUtil.handlePassThroughNotify(context, PushType.UMENG, msg.custom);
                    }
                });
            }
        });
        agent.setNotificationClickHandler(new UmengNotificationClickHandler() {
            /**
             * 点击推送后会触发的事件
             */
            @Override
            public void dealWithCustomAction(Context context, UMessage message) {
                super.dealWithCustomAction(context, message);
                // 解析信息
                PushNotificationUtil.MsgBean bean =
                        PushNotificationUtil.parsePushMsg(message.custom);

                if (bean.target != null) {
                    Intent intent = PushNotificationUtil.getPushIntent(context, bean.target);
                    context.startActivity(intent);
                }
            }
        });
    }

    /**
     * 目前一个设备仅仅有一个token,所以用了静态的string做缓存;
     * 做如果以后有多个token,那么把{@link PushManager}变成单例,存一个token的map即可。
     */
    public static void sendDeviceToken(final Context context) {
        sendDeviceToken(context, PushManager.tokenType, PushManager.token);
    }

    public static void sendDeviceToken(final Context context, @PushType String type, String token) {
        if (token == null) {
            return;
        }
        PushManager.tokenType = type;
        PushManager.token = token;

        // todo 发送token给服务器
    }

    public static void handleRegisterResult(Context context, @PushType String type,
            boolean isSuccess, @Nullable String resultMsg) {

        if (isSuccess) {
            // todo
        } else {
            // todo
        }
    }

}

小米推送信息接收器:

/**
 * @author Kale
 * @date 2016/4/27
 *
 * http://dev.xiaomi.com/doc/?p=544
 */
public class MiMessageReceiver extends PushMessageReceiver {

    /**
     * 通知消息被点击时触发
     */
    @Override
    public void onNotificationMessageClicked(Context context, MiPushMessage msg) {
        try {
            PushNotificationUtil.MsgBean bean = PushNotificationUtil
                    .parsePushMsg(URLDecoder.decode(msg.getContent(), "UTF-8"));

            if (bean.target != null) {
                Intent intent = PushNotificationUtil.getPushIntent(context, bean.target);
                context.startActivity(intent);
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 用来接受客户端注册的结果
     */
    @Override
    public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
        String command = message.getCommand();
        List<String> arguments = message.getCommandArguments();
        String cmdFirstArg = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
        // 如果是注册的命令,那么就得到token
        if (MiPushClient.COMMAND_REGISTER.equals(command)) {
            PushManager.sendDeviceToken(context,PushManager.PushType.MI, cmdFirstArg); // add token

            long code = message.getResultCode();
            PushManager.handleRegisterResult(context, PushManager.PushType.MI, code == ErrorCode.SUCCESS, cmdFirstArg);
        }
    }

    /**
     * 接收透传消息
     */
    @Override
    public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
        try {
            PushNotificationUtil.handlePassThroughNotify(context, PushManager.PushType.MI,
                    URLDecoder.decode(message.getContent(), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

}

PushNotificationUtil的代码就不贴了,不同的项目会有不同的实现形式。至此,推送的集成工作已经完毕!

注意
在miui上,如果应用被杀死,即使用了小米推送,透传消息也是无法收到的。
可以考虑采用非透传的模式进行推送,简单方便,而且自带点击统计。至于是否采用非透传,就看项目的设计和需求了。



作者:天之界线2010
链接:http://www.jianshu.com/p/d650d02a1c7a
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Android第三方推送到达率调研 由于众所周知的原因,国内APP不能使用谷歌官方推送GCM,同时国内android系统几乎都经过厂商的深度定制,对于后台应用的管控严格,导致推送到达率很低,现对市面上一些主流第三方推送(非所有)做一个调研。这里选择三个渠道:友盟、小米和华为。简述一下选择的原因,友盟作为免费的商业级第三方入选,当然也可以选择极光什么的;小米和华为是目前最主流的两款手机,他们有一个共同特点,都有自己的系统级推送(到达率差不多100%),其他推送则有可能被后台杀死导致收不到推送,主要调研他们的推送在本身品牌手机上面的到达率。 阅读详情

相关推荐

Java集成WebSocket实现消息推送,详细步骤以及出现的问题如何解决

这是Java EE的标准API- Spring框架提供的WebSocket支持在本教程中,我们将使用JSR 356规范,它从Java EE 7开始被支持。首先,我们需要添加相关依赖。通过Java集成WebSocket实现消息推送是构建实时应用程序的强大方式。在本文中,我们详细介绍了如何使用Java EE的WebSocket API创建服务器端和客户端应用程序,以及如何在前端集成WebSocket。通过遵循这些最佳实践和注意事项,你可以构建可靠、安全和高性能的实时消息推送系统。

qu1210的博客 3046

推送平台比较

支持平台       个推 极光 信鸽 Android √ √ √ ios √ √ √ Window  Phone ╳ √ ╳ 在支持平台上,个推和信鸽一样仅支持Android、ios。极

wykpaopao的专栏 2621

友盟平台集成第三方登录、分享与推送完整指南

OAuth 2.0 协议支持多种授权模式,包括授权码模式、简化模式、密码模式和客户端模式。每种模式都设计用来适应不同的场景需求。以授权码模式为例,它通常用于有服务器后端支持的应用程序。用户通过重定向到授权服务器,并在用户确认授权后,应用获得一个授权码,最后应用用该授权码换取访问令牌。关键性能指标(KPI)通常包括系统响应时间、吞吐量、错误率、资源利用率等。这些指标能够帮助我们从不同维度了解系统的性能状态。响应时间(Response Time)

weixin_42146230的博客 1333

国内第三方移动推送对接调查:Android、IOS、Flutter,各种云推送、个推、极光、统一推送联盟

文章目录前言一、来自小组的争议二、开启调查三、调查结果总结 前言 第三方移动推送对接,刚开始是移动端发起的。在开会讨论这个对接时,心里突然很迷茫,为什么要做第三方移动推送对接?我们自己为什么不能做移动推送? 话说,项目里目前所使用的推送就是自己做的。但是在App离线情况下,消息就收不到了。想起来了,这是最最重要的问题,是为了在离线的情况下,App还能收到通知和消息。如果不是因为这个,这个对接可以不做。 因为手机端的app层不出穷太多了,为了给手机省电,用户会主动把运行在前端的app给咔嚓掉…虽然咔嚓掉,但是

u011613451的博客 4824

从气象预报到物流规划:多尺度地理加权回归(MGWR)在ArcGIS Pro中的实战应用

本文深入探讨了多尺度地理加权回归(MGWR)在ArcGIS Pro中的实战应用。通过对比传统GWR模型,MGWR的核心优势在于能识别不同解释变量的独特空间作用尺度,有效解决空间异质性问题。文章结合气象预报误差修正与物流网点选址两个案例,详细讲解了从数据准备、参数设置、带宽优化到结果解读的全流程,为地理数据分析师处理复杂空间数据提供了强大工具。

egg99的博客 1029

Keil5安装包别乱下!手把手教你从官网精准下载C51/V960和MDK5,并完成2032年有效注册(避坑网络资源失效)

本文详细介绍了如何从Keil官网安全下载C51 V9.60和MDK5开发环境,并提供2032年有效注册的完整指南。通过验证安装包完整性、自定义安装路径和长期激活方案,帮助开发者避开网络资源陷阱,搭建稳定的STM32开发环境。

weixin_26886855的博客 529

android、ios当前主流推送平台(消息推送)功能、性能对比技术研发文档

推送平台功能分析 一、简易介绍: 1.极光推送 极光推送为深圳市和讯华谷信息技术有限公司旗下产品,2011年,推出了极光推送(JPush),成为国内最早的第三方移动消息推送服务提供商,专业做推送的一家公司,当然今年它也推出了“极光IM”等及时通讯功能。 极光推送的服务对象包括了顺丰、易信宜人贷、迅雷、刀塔传奇、乐视网、神州租车、中国电信、中国农业银行、去哪儿、珍爱网、美的、格力; 极光推

Susan8888的专栏 5542

达梦7实时主备集群

达梦7实时主备配置实时主备数据环境说明配置主库配置 dm.ini配置dmmal.ini配置dmarch.ini配置dmwatcher.ini配置dmwatcher.ini以mount方式启动主库设置OGUID修改数据库为primary模式配置备库 在服务器部署过程中,通常搭建有数据库主备集群,用于防备数据库服务器产生问题影响数据正常,在此,我们分享一下在虚拟机环境中模拟搭建实时主备的数据守护模式。 在搭建环境之前,需用户确认主备DM7的数据库已安装完成,且版本一致,同时还应注意各实例所在主机的操作系统位数、

g360382的博客 1022

PushDeer扩展开发:如何为推送服务添加新功能

想要让PushDeer推送服务更加强大?掌握扩展开发技能是关键!PushDeer作为一款开源推送服务,提供了丰富的扩展开发接口,让你能够轻松添加自定义功能,满足个性化推送需求。😊 ## 为什么要扩展PushDeer? PushDeer的核心价值在于「易用」、「可控」和「渐进」。通过扩展开发,你可以: - **集成到更多平台**:如WordPress评论通知 - **添加推送类型**:支持更

gitblog_00022的博客 418

I2C总线原理及总线信号的实现

I2C总线是一种多主机总线,连接IIC总线上的器件分为主机和从机,主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,I2C也具备冲突检测和仲裁的功能来防止错误产生;每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;I2C总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器;主要用于近距离(抗干扰能力稍弱),低速的芯片之间的通信(传输视频,带宽

YLG_lin的博客 3941

飞塔防火墙和paloalto防火墙构建ipsec

一、配置fortigate防火墙1.防火墙配置上网功能1.1配置fortigate的wan1接口IP为100.100.100.2/24VLAN交换internal,IP地址为192.168.156.99/24。开启DHCP分配IP地址范围为192.168.156.110-192.168.156.2101.2配置访问互联网的缺省路由1.3配置访问互联网的安全策略2.配置与Paloalto防火墙的IPSECVPN。

weixin_44675999的博客 3738

Android-Best-Practices:第三方推送集成完全攻略与性能优化

在移动应用开发中,第三方推送服务是提升用户活跃度和留存率的关键工具。Android-Best-Practices项目提供了一套完整的解决方案,帮助开发者轻松实现高效、稳定的推送功能。本文将详细介绍如何在Android应用中集成第三方推送服务,并分享实用的性能优化技巧,让你的应用在保持推送及时性的同时,实现资源消耗最小化。 ## 为什么选择第三方推送服务? 第三方推送服务能够解决Android系

gitblog_01025的博客 798

Delphi 12.3 精简绿色版下载

Delphi精简绿色版基于原版安装,去掉了InterBase、法语、德语、日语,只保留了Delphi Windows 32和Delphi Windows 64桌面开发,其余部分未做任何修改。Delphi精简绿色版不含任何破解,仅供学习和研究,24小时后请自行删除,如需继续使用Delphi,请购买正版。绿色版基于原版安装,未做任何精简和修改。绿色版不含任何破解,仅供学习和研究,24小时后请自行删除,如需继续使用RAD Studio,请购买正版。

全粘工程师 4296

QD框架自定义推送开发指南:DIY推送器实现原理

QD框架作为强大的HTTP请求定时任务自动执行框架,其推送功能是确保任务执行状态及时反馈的关键组件。本文将深入解析QD框架自定义推送的实现原理,帮助开发者掌握DIY推送器的开发方法。🚀 ## 什么是自定义推送功能? 在QD框架中,自定义推送功能允许用户通过简单的HTTP请求配置,将任务执行结果推送到任意自定义服务。无论您需要对接企业内部系统、第三方API还是自建通知服务,都可以通过自定义推送

gitblog_00617的博客 493

Geneformer实战:如何用Hugging Face Transformers快速搭建单细胞转录组分类模型

本文详细介绍了如何使用Geneformer和Hugging Face Transformers快速搭建单细胞转录组分类模型。通过环境准备、数据预处理、模型加载与配置、训练Pipeline构建等步骤,帮助读者掌握基于Transformer架构的单细胞数据分析技术,提升分类准确率和效率。

rl6adventurer的博客 605

Coffin-Manson模型的详细讲解

Coffin-Manson模型是一个简单而强大的工具,用于预测低循环疲劳寿命,尤其在半导体功率循环中评估热机械可靠性。其核心是通过应变(或温度变化)与循环次数的对数关系,实现加速测试和寿命外推。尽管有局限性,通过改进版本(如加入激活能),它在电子封装、焊点疲劳和器件设计中仍广泛应用。实际使用时,建议结合实验数据拟合参数,并验证模型预测。如果需要特定材料的参数值、代码实现或进一步计算示例,请提供更多细节!

专注于前沿技术的探索与实践,热衷于从代码逻辑、架构设计到行业应用等多维度深挖技术本质。期待在技术之路上与你共同成长~ 2144

从零开始的Metabase插件开发:解锁数据可视化与第三方服务集成新范式

Metabase作为开源数据可视化平台,其插件系统是扩展核心功能的关键。本文将系统讲解插件开发全流程,包括协议定义、驱动实现、第三方集成等实战技能,帮助开发者快速构建自定义解决方案。 ## 插件架构与开发环境准备 Metabase插件系统基于Clojure协议(Protocol)设计,采用分层架构实现功能解耦。核心开发文件位于[src/metabase/plugins/](https://li...

gitblog_00140的博客 828

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

android 好的文章保存

一.推送方面的 1.集成第三方推送最佳实践 http://www.jianshu.com/p/d650d02a1c7a

xxzjwdnlwx的专栏 426

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Flutter Starter Kit扩展指南:如何添加新功能模块与第三方库

Flutter Starter Kit是一个面向初学者的完整框架,集成了Bloc模式、RxDart、sqflite、Fluro和Dio等主流技术,通过App Store示例应用展示了如何构建结构清晰的Flutter项目。本指南将教你如何在现有框架基础上快速添加新功能模块和集成第三方库,让你的Flutter应用开发效率提升3倍! [![Flutter Starter Kit标志](https://

gitblog_00816的博客 459

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

上一篇: 高质量的seo优化方案
下一篇: laravel 基础教程 —— 请求
dwf_学海无涯
博客等级 码龄10年 42粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值