Integrating third-party push best practices

This article will be updated from time to time to recommend projects under watch. If you like it, please ask star, if you feel there is a slip, please submit issue, if you have a better idea, you can submit pull request.
The sample code in this article is mainly based on the author's experience. If you have other skills and methods, you can participate in perfecting this article.

Note: This report was written on April 25, 2016. The content of this paper may produce errors and deviations with the development of time and technology.

Fixed connection in this paper: https://github.com/tianzhijiexian/Android-Best-Practices

I. Demand Background

Android has no way to use the system-level push service in China, and the third-party rom for various customizations (customization here is not praised or disparaged), so domestic developers need to choose some mature push platform to do push service. There are many kinds of push platforms at present. This paper will give the specific comparison and research results of push platforms, which will be convenient for later generations to choose. Because I don't know about the overseas push service, I choose the domestic push platform.

II. Demand

  • Research on the Advantages and Disadvantages of Domestic Push Platform
  • Technical scheme and selection are given.
  • Fast integration of push services

III. Realization

A Survey of Research Information

Push platform

  • Jpush
  • Millet push
  • Alliance push
  • Push
  • Ali Push

Types of aircraft participating in the test:

  • 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)

Because the test results are consistent on the same rom, the following test results are simplified to shield the details of the model.

Research results

Carrier pigeon
Our project has long used pigeon service. Why write this article, because the carrier pigeon can not meet the high arrival rate standard, so for Tencent carrier pigeon, no research.

Push
I excluded a push first, not because it is not good, but because it is a charging platform itself, its advantages are full service, fine data granularity, and relatively timely response to developers, but because of its charging attributes I can not choose it as one of the options. Many friends have also said that a push is free when the number of users is small. But this article chooses the full amount of free services, so I will not list a push for the time being.

Ali Push
Ali Push uses Aliyun's whole set of services, but because of big companies, it will be forced to implant one sdk, which is really a family barrel level thing, and the way to contact technology is through Aliwangwang to make people tired. Big and complete stuff has never been my choice, and in the future there may be a tendency to pay for Ali push, so I don't think about it.

Alliance push

Alliance is an old manufacturer of data, but it always makes me feel uncomfortable. I have done alliance feedback before, and I always feel that access and use is not elegant enough. But as far as push is concerned, its background interface and the small tools and documents provided are the most beautiful and practical.
Be careful:
It should be noted that the alliance's sdk has introduced third-party libraries such as httpClient (officially abandoned), okio and so on. I don't think it is necessary at all. I think an independent sdk should be as lightweight as possible. Too many self-contained libraries can cause many problems. If you want to introduce allied feedback, you must pay attention to the thousands of methods it brings!


Pushing Backstage

The alliance adopts the technology of shared connection, that is, multiple apps using affiliate push can share long connections with each other, to ensure that your app can receive the push using brothers app as long as the user opens the app using affiliate push. This technology is also used by many push platforms. It's a good solution. As for whether it's a hooligan or not? Not to say.


Sharing Long Connections (Hang Long Connections on Goethe)

Test results:

  Android Native MIUI ColorOS flymeOS Samsung
app is in the front desk
Return key exit
Kill app s × × (with delay)
After Mobile Phone Restart × × (with delay) (with delay)
Open Brother App - × × × -

(: Can receive push, * Can not receive push, -: meaningless test)

The test results show that the alliance has a good support for the native, and it can not die in the native rom. It has a certain probability of Revival on flyme, but it has a greater probability of losing the news before revival.

It should be noted that:
This test kills applications by sliding on Flme and mx4 and mx5. In mx5, if you kill all applications with the top "broom", then the affiliate push can not reach.


Alliance process

Memory usage

The sdk configuration file is as follows:


service

Through code analysis, some of the allied services are run in a separate process (push), which is also a common way to improve service survivability, so it is necessary to select the right process when breaking point is under debug! ___________


Broadcasting Receiver

We can clearly know that affiliate push will automatically resurrect after listening to broadcasts like boot-up, network changes, application uninstallation, so that it can still receive remote push information in real time after the application is killed.


Brotherhood Application

In the test, I found that even if I opened my brother app (Golden Map) on the MIUI, I couldn't receive the push. The back-end tool told me that the Long-connected service was actually hooked up to the brothers app, and it was unclear why the push could not be received.

Finally, we need to talk about the shortcomings of the alliance. After service is killed, there is a certain probability of losing information. The newly registered equipment can not receive the message immediately. It takes a period of time to receive the test message, and a period of time to receive the official message. It always feels unstable. If a message has not been received for a long time, it is likely to be lost forever.
Remind again! Allied sdk introduced four libraries! It brings thousands of methods! Introduce the discarded httpClient!

Millet push

Millet's push has a great advantage that it will directly use the long connection of the system on MIUI. As long as the system is not dead, your app will receive the push more than 90%. When your MIUI users reach a certain level, you have to use millet push.


Update logs

To Tucao is millet pushing background (web page) often can not be opened, the frequency is very high.

  Android Native MIUI ColorOS flymeOS Samsung
app is in the front desk
Return key exit
Kill app s × ×
After Mobile Phone Restart × × (with delay)
Open Brother App - - × × -

(: Can receive push, * Can not receive push, -: meaningless test)

From the results, millet push is in line with its document description, and can perform well in native and MIUI, so millet push can be included in the selection list.


Memory information on native rom

Memory information on MIUI

I found that millet would automatically use system services on MIUI, that is to say, it would not need to build a service itself, so in theory it would be lighter and less power-efficient.


Millet Push Profile

From the code analysis, millet will listen to the broadcasting of network switching to restart the service. For MIUI level, there may be a special method, not to start additional service, but can handle Message.


Brotherhood Application

Whether or not to receive push messages through brotherly applications, my test results are not received, I do not know if it is a bug. I can only say that it is unreasonable to improve the reception rate of messages in this way.

Jpush

Aurora concentrates on push service in its early days. It can be said that the precipitation is very deep. Now it has been renamed "Aurora" and its business is more extensive. Aurora responds best to developers'questions. Android Documents for Institutions The purpose of each function mentioned here is not to add some privileges secretly like OneSDK (Ali family barrel). Documents also follow the style of AS, which is more modern. The disadvantage is that the background is relatively old, three years ago is such an ugly appearance, the sending operation has a delayed process, no allies do well.
Aurora also supports the pull-up function of brothers'apps, starting with the 1.8.0 version from the code point of view.

Reasons why third-party systems can't receive push messages (excerpted from) Aurora Document)
Because third-party ROM management software requires user manual operation
Millet [MIUI]
Self-startup management: Apps need to be added to the Self-Startup Management list, otherwise processes can not be opened after killing or restarting.
Notification Bar Settings: By default, applications display notification bars. If closed, notifications will not be prompted.
Network Assistant: Can you manually prohibit installed third party programs from accessing 2G/3G and WIFI networks and whether new installers will allow access to 2G/3G and WIFI networks after setup?
MIUI 7 Divine and Hidden Mode: Allow applications to configure custom mode, applications in the background to keep the network available, otherwise when applications enter the background, applications can not receive messages normally. [Settings] Power-down and Performance in the Hidden Mode

Huawei [Emotion]
Self-startup management: You need to add the application to the self-startup management list, otherwise the process will not open after killing or rebooting, but can only start the application manually.
Background Application Protection: Apps need to be manually added to this list, otherwise the device will automatically kill the application process after sleep, and only by manually opening the application can it resume operation.
Notification management: There are three application states: prompt, permission and prohibition. No warning will be given in the notification bar if the application is prohibited.

Flyme
Self-startup management: Apps need to be added to the Self-Startup Management list, otherwise processes can not be opened after killing or restarting.
Notification Bar Push: Close the application notification and receive the message without any display.
Power-saving management: Power-saving mode is set up in the security center. When the application of standby power consumption management is allowed, it should be allowed. Otherwise, the mobile phone will be dormant or the application will be idle for a period of time, and the message will not be received normally.

VIVO[Funtouch OS]
Memory one-click cleanup: Apps need to be added to the whitelist list, otherwise the system's own "one-click acceleration" will kill the process
Self-startup management: Apps need to be added to the self-startup management list in "i housekeeper", otherwise the process will not start after restarting the phone. But mandatory manual killing processes, even if added to this list, follow-up processes can not start themselves.

OPPO[ColorOS]
Freeze Application Management: Apps need to be added to the pure background, otherwise messages can not be received in time when the screen is locked.
Self-startup management: While adding applications to the list of self-startup management, you also need to lock the application process in the settings-applications-running process, otherwise the killing process or the process will not open after booting, and you can only start the application manually.

In short, it is a very real thing. It can't do anything about android's authority and China's national conditions. It can only achieve a high delivery rate as far as possible.

  Android Native MIUI ColorOS flymeOS Samsung
app is in the front desk
Return key exit
Kill app s (with delay) × × × (with delay)
After Mobile Phone Restart × × × (with delay)
Open Brother App - × × × -

(: Can receive push, * Can not receive push, -: meaningless test)

With regard to delays:
The delay is due to the inability to receive real-time messages after the exterior service is killed and will not continue to work until resurrection, resulting in message delays. This is evident in android native and Samsung rom s. If you kill app - > shut down - > light up the screen on Samsung's mobile phone, the service will restart automatically. I think this is one of the ways to restart the service.


Memory information

configuration file

The Aurora configuration file details what is used for testing, what is required, what is optional, and whether to put services into an independent process is a recommended option rather than a mandatory one. The overall demo and documentation give people the feeling that they are not hooligans, but helpless.


Brotherhood Application

I opened Aurora's official brothers app, but I couldn't receive a push message for my test app.

conclusion

  • Millet Push has a strong advantage in native and MIUI.
  • On Samsung's ROM, Millet and his allies are inseparable.
  • In Meizu Flyme, allies have a certain probability of resurrection.
  • None of the three Colosos in OPPO showed any outstanding performance.

The overall conclusion is that the success rate of rom push abroad is high, while the success rate of domestic rom push is low. Domestic customization is really not harmful.

Technical scheme and selection

Because app has a large number of MIUI users, millet push must be introduced, because the performance of alliances on flyme is better, so finally, millet and alliances jointly access app. The current strategy is to enable millet push on MIUI instead of alliance push, and to abandon millet push on non-miui.

To determine whether user rom is a miui method:?

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;
    }

Because the push service uses another process, the application will be started many times, so it is necessary to determine which process started the application, so that the configuration work required by the main process can be shielded from the push process. What the developer needs to do is to determine whether it is the main process, and if so, execute the code in the original application.

    /**
     * Note: Because push services are set to run in another process, this Application will be instantiated twice.
     * Some operations need to be done by the main process of the application, so this method is used.
     */
    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;
    }

Be careful!!!!!!!!
Because the resurrection of the push service will start the application, that is, wake up the application. So don't do network requests when the application starts. Because once the push is started, a large number of users'applications will be awakened, and then a large number of requests for a few APIs will be generated in a few minutes. Backstage services are likely to be suspended, so be careful. That's one of the reasons why I strongly don't recommend doing a lot of things in application. Consider transferring these requests to mainActivity.

Integrated push service

At present, I use the integration of multiple third-party push, and then register different push services according to the model. The back end does not control the push platform. Because the promotion of alliances must have a module (I don't know why), so I simply put the third-party push platform into a module.

Be careful:
There will be conflict between alliance push and alliance feedback. Now alliance feedback is no longer maintained, so it is strongly not recommended to use alliance feedback. And there is a conflict between affiliate push and Ali's products, so if there is a conflict, download the SDK to UTDID.


Allied and Ali Conflict Resolution

Alliance Feedback and Alliance Push Conflict Solutions (I only learned that Alliance Feedback had secretly integrated push services):

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

module Overview


List

libs integrates so packages needed by allies (introducing an alliance push, introducing a bunch of things, and conflicts, all of which are allies), as well as a jar package for millet.

Usually we don't put so in lib, we will use a new main/jniLib folder to put it in. The alliance uses another method, so there is a code in build.gradle:


gradle

The alliance document says that to introduce my push, you have to introduce httpClient, so we have to introduce httpClient that has been abandoned by Google in build.gradle!!!! :

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    // http://dev.umeng.com/push/android/integration
    // If you are compiling an api with Android 6.0 or more, you need to build the PushSDK.gradleDocumentary android{}Add in Block:
    // useLibrary 'org.apache.http.legacy',And put compileSdkVersion Change the version number to 23.
    useLibrary 'org.apache.http.legacy'

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 23
    }
}

The alliance also uses the square family, so it has to introduce the following two dependencies:


Paste_Image.png

So far, I finally introduced the SDK of the Alliance. I won't mention millet's sdk. There's also a jar bag in the picture above.

Permissions in Manifest

Because the major push platforms have their own rights, but if mixed together, it is very difficult to delete the code of a platform in the future, and ultimately do not know who joined the entire rights, what role. So I have made the permissions of each platform independent. Although there will be warnings of redefining permissions when compiling, it does not affect the final package. This has been confirmed by decompilation, please rest assured.

    <!-- umeng Mandatory -->
    <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 Optional -->
    <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" />


    <!--Millet Push Begins-->
    <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" />
    <!--Millet Push End-->

On key in Mainfest

Because keys and project-specific package names are used in the configuration file, and I want the entire push module to remain independent, I end up writing keys in code instead of defining data tags in manifest.

Write push management class

Because of the integration of multiple platforms, there are many things and management classes are needed as soon as there are more things. The management class does something very simple, registering services, receiving push messages, and displaying notifications.

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

    private static final String TAG = "PushManager";

    public static String tokenType; // Caching of token type

    public static String token; // token cache

    @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);
        }
    }

    /**
     * Registered millet push service
     * After successful registration, they will go to{@link MiMessageReceiver}Send Broadcasting
     */
    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);
        }
    }

    /**
     * Promotion Service of Registered Alliances
     */
    private static void registerUMengPush(final Context context) {
        PushAgent agent = PushAgent.getInstance(context);
        // If you use allied data analysis, this key is consistent with allied data analysis.
        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() {
                        //The parameter registrationId of the onRegistered method is device_token
                        sendDeviceToken(context, PushType.UMENG, deviceToken);
                        handleRegisterResult(context, PushType.UMENG, true, null);
                    }
                });
            }
        };
        agent.enable(callback);
        // There are two ways to get token, which ensures that token is as available as possible
        // The first acquisition needs to be networked. After acquisition, the alliance will be stored in sp, so there are two methods.
        String deviceToken = UmengRegistrar.getRegistrationId(context);// Get deviceToken
        if (deviceToken != null) {
            callback.onRegistered(deviceToken);
        }

        agent.setResourcePackageName("com.duitang.main");
        // The following code must be invoked in application
        agent.setMessageHandler(new UmengMessageHandler() {
            /**
             * Reference integration document 1.6.3
             * http://dev.umeng.com/push/android/integration#1_6_3
             *
             * If the developer needs to handle custom passes, the method dealWithCustomMessage() needs to be rewritten, and the content of the custom message is stored in 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))) {
                    // If the developer responds to allied feedback, return it directly
                    return;
                }

                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                       // Handling through messages
                        PushNotificationUtil.handlePassThroughNotify(context, PushType.UMENG, msg.custom);
                    }
                });
            }
        });
        agent.setNotificationClickHandler(new UmengNotificationClickHandler() {
            /**
             * Events triggered by clicking push
             */
            @Override
            public void dealWithCustomAction(Context context, UMessage message) {
                super.dealWithCustomAction(context, message);
                // Parsing information
                PushNotificationUtil.MsgBean bean =
                        PushNotificationUtil.parsePushMsg(message.custom);

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

    /**
     * At present, a device has only one token, so it uses static string as cache.
     * If there are more than one token in the future, then{@link PushManager}Turn into a singleton and save a map of token.
     */
    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 sends token to server
    }

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

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

}

Millet Push Information Receiver:

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

    /**
     * Triggered when the notification message is clicked
     */
    @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();
        }
    }

    /**
     * Used to accept the results of client registration
     */
    @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);
        // If it's a registered command, you get 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);
        }
    }

    /**
     * Receiving through message
     */
    @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();
        }
    }

}

Push Notification Util code is not pasted, different projects will have different forms of implementation. So far, the integration of push has been completed!

Be careful
On miui, if the application is killed, even if it is pushed by millet, the message can't be received. It can be considered to use the non-transmissive mode for push, which is simple and convenient, and has its own click statistics. Whether or not to adopt non-through transmission depends on the design and requirements of the project.



Author: Tianzhi Boundary 2010
Link: http://www.jianshu.com/p/d650d02a1c7a
Source: Brief Book
Copyright belongs to the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

Tags: Android MIUI SDK Mobile

Posted on Mon, 10 Jun 2019 12:04:18 -0700 by tmann