Android消息推送之自启动

背景

最近公司的项目需要及时聊天功能,聊天功能基本上已经完成,采用的是自己搭建的socket长连接来实现聊天的方按。安排我研究消息推送,主要确保杀死App后还能正常接收消息,重启后也能收到消息。

消息推送的重难点

  • 1.长连接消息收发功能的实现。
  • 2.消息的实时推送,确保消息的达到率。

    第一条目前已经实现,不在本文讨论的范围,主要是针对第二点讨论。要做到消息的实时推送,保证消息的到达率,当然是后台服务常驻的实现,关不掉、杀不死、就算杀死也能重启。

经过测试,测试手机包括各种品牌各种系统版本的手机(三星、小米、魅族、联想、华为、vivo、oppo、tcl)、目前博主的方案在5.1及以下手机基本上能完全做到杀死自启动,除了华为,在6.0手机能做到引导用户去授权页面允许后台自启动(包括小米、华为、oppo、魅族),在7.0上基本无效。

Android 进程拉活包括两个层面:
A. 提高进程优先级,降低进程被杀死的概率。
B. 在进程被杀死后,进行拉活。

技术点研究 参考自腾讯的bugly的文章“Android进程保活招式大全”

提高进程优先级,降低进程被杀死的概率。

  1. 利用 Activity 提升权限:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。也就是传说中QQ的黑科技,亲测,无效
  2. Android 中 Service 的优先级为4,通过 setForeground 接口可以将后台 Service 设置为前台 Service,使进程的优先级由4提升为2,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。亲测,并没有什么卵用

进程死后拉活的方案

1.利用系统广播拉活 : 在发生特定系统事件时,系统会发出响应的广播,通过在 AndroidManifest 中“静态”注册对应的广播监听器,即可在发生响应事件时拉活。在某些5.0以下手机上有用。


        <receiver android:name=".receiver.KeepLiveReceivers">
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
                <action android:name="android.intent.action.SCREEN_ON"/>
                <action android:name="android.intent.action.SCREEN_OFF" />
                <action android:name="android.intent.action.USER_PRESENT" />
            intent-filter>
        receiver>
/**
 * 网络连接改变锁屏广播和解锁
 * Created by haifeng on 2017/8/28.
 */
public class KeepLiveReceivers extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.i("91ysdk",action);
        if(KeepLiveUtils.hasNetwork(context) == false){
            return;
        }
        if (!KeepLiveUtils.isServiceWork(context,"service.keppliveservice.service.OnLineService")){
            Intent startSrv = new Intent(context, OnLineService.class);
            startSrv.putExtra("CMD", "TICK");
            context.startService(startSrv);
        }
    }
}

2.利用 JobScheduler 机制拉活,只在5.0及5.1手机上有用。

import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;

import service.keppliveservice.KeepLiveUtils;

/**
 * Created by haifeng on 2017/8/25.
 */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService{

    @Override
    public boolean onStartJob(JobParameters params) {
        Log.d("91ySdk", "onStartJob");
        startService();
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        Log.i("91ySdk","onStopJob");
        return false;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("91ySdk","onStartCommand");
        try {
            int id = 1;
            JobInfo.Builder builder = new JobInfo.Builder(id,
                    new ComponentName(getPackageName(), MyJobService.class.getName() ));
            builder.setPeriodic(60*1000);  //间隔1分钟毫秒调用onStartJob函数
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); //有网络的时候唤醒
            JobScheduler jobScheduler = (JobScheduler)this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            int ret = jobScheduler.schedule(builder.build());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        startService();
        return super.onStartCommand(intent, flags, startId);
    }

    public void startService(){
        boolean isOnLineServiceWork = KeepLiveUtils.isServiceWork(this, "service.keppliveservice.service.OnLineService");
        boolean isKeepLiveServiceWork = KeepLiveUtils.isServiceWork(this, "service.keppliveservice.service.KeepLiveService");
        if(!isOnLineServiceWork||
                !isKeepLiveServiceWork){
            this.startService(new Intent(this,OnLineService.class));
            this.startService(new Intent(this,KeepLiveService.class));
            Toast.makeText(this, "进程启动", Toast.LENGTH_SHORT).show();
        }
    }
}

3.利用闹钟广播拉活,在4.4及以下部分手机上有效

protected void setTickAlarm(){
        AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this,TickAlarmReceiver.class);
        int requestCode = 0;
        tickPendIntent = PendingIntent.getBroadcast(this,
        requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        //小米2s的MIUI操作系统,目前最短广播间隔为5分钟,少于5分钟的alarm会等到5分钟再触发!
        long triggerAtTime = System.currentTimeMillis();
        int interval = 300 * 1000;
        alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtTime, interval, tickPendIntent);
    }
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager.WakeLock;
import android.util.Log;

import service.keppliveservice.KeepLiveUtils;
import service.keppliveservice.service.OnLineService;


public class TickAlarmReceiver extends BroadcastReceiver {

    WakeLock wakeLock;

    public TickAlarmReceiver() {

    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e("91ysdk", "收到闹钟广播");
        if(KeepLiveUtils.hasNetwork(context) == false){
            return;
        }
        Intent startSrv = new Intent(context, OnLineService.class);
        startSrv.putExtra("CMD", "TICK");
        context.startService(startSrv);
    }

}

4.采用aidl双进程守护互相监听,一个被杀后立即拉活,在5.0以下绝大部分手机上有效

        /*
         * 此线程用监听Service2的状态
         */
        new Thread() {
            public void run() {
                while (true) {
                    boolean isRun = KeepLiveUtils.isServiceWork

(OnLineService.this,"service.keppliveservice.service.KeepLiveService");
                    if (!isRun) {
                        android.os.Message msg = 

android.os.Message.obtain();
                        msg.what = 1;
                        handler.sendMessage(msg);
                    }
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();

private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case 1:
                    startKeepLiveService();
                    break;

                default:
                    break;
            }

        };
    };

    /**
     * 使用aidl 启动Service2
     */
    private StrongService startS2 = new StrongService.Stub() {

        @Override
        public void stopService() throws RemoteException {
            Intent i = new Intent(getBaseContext(), 

KeepLiveService.class);
            getBaseContext().stopService(i);
        }

        @Override
        public void StartService() throws RemoteException {
            Intent i = new Intent(getBaseContext(), 

KeepLiveService.class);
            getBaseContext().startService(i);
        }
    };

    /**
     * 判断Service2是否还在运行,如果不是则启动Service2
     */
    private void startKeepLiveService() {
        boolean isRun = KeepLiveUtils.isServiceWork(OnLineService.this,
                "service.keppliveservice.service.KeepLiveService");
        if (isRun == false) {
            try {
                startS2.StartService();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
package service.keppliveservice.service;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.widget.Toast;

import service.keppliveservice.KeepLiveUtils;
import service.keppliveservice.StrongService;


/**
 * Created by haifeng on 2017/8/24.
 */

public class KeepLiveService extends Service {

    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case 1:
                    startOnlineService();
                    break;

                default:
                    break;
            }

        };
    };

    @Override
    public void onCreate() {

        Toast.makeText(KeepLiveService.this, "Service2 启动中...", Toast.LENGTH_SHORT).show();
        startOnlineService();
        /*
         * 此线程用监听Service2的状态
         */
        new Thread() {
            public void run() {
                while (true) {
                    boolean isRun = KeepLiveUtils.isServiceWork(KeepLiveService.this,"service.keppliveservice.service.OnLineService");
                    if (!isRun) {
                        Message msg = Message.obtain();
                        msg.what = 1;
                        handler.sendMessage(msg);
                    }
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return (IBinder) startS1;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    /**
     * 判断Service1是否还在运行,如果不是则启动Service1
     */
    private void startOnlineService() {
        boolean isRun = KeepLiveUtils.isServiceWork(KeepLiveService.this,
                "service.keppliveservice.service.OnLineService");
        if (isRun == false) {
            try {
                startS1.StartService();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 使用aidl 启动Service1
     */
    private StrongService startS1 = new StrongService.Stub() {

        @Override
        public void StartService() throws RemoteException {
            Intent i = new Intent(getBaseContext(), OnLineService.class);
            getBaseContext().startService(i);
        }

        @Override
        public void stopService() throws RemoteException {
            Intent i = new Intent(getBaseContext(), OnLineService.class);
            getBaseContext().stopService(i);
        }
    };

    @Override
    public void onTrimMemory(int level) {
        startOnlineService();
    }
}

5.对于6.0和7.0某些国产大牌机器、必须引导用户去授权管理页面去授权。

package service.keppliveservice;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;

import java.util.List;

/**
 * Created by haifeng on 2017/8/29.
 */

public class KeepLiveUtils {

    /**
     * Get Mobile Type
     *
     * @return
     */
    private static String getMobileType() {
        return Build.MANUFACTURER;
    }

    /**
     * GoTo Open Self Setting Layout
     * Compatible Mainstream Models 兼容市面主流机型
     *
     * @param context
     */
    public static void jumpStartInterface(Context context) {
        Intent intent = new Intent();
        try {
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            Log.e("HLQ_Struggle", "******************当前手机型号为:" + getMobileType());
            ComponentName componentName = null;
            if (getMobileType().equals("Xiaomi")) { // 红米Note4测试通过
                componentName = new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity");
            } else if (getMobileType().equals("Letv")) { // 乐视2测试通过
                intent.setAction("com.letv.android.permissionautoboot");
            } else if (getMobileType().equals("samsung")) { // 三星Note5测试通过
                componentName = new ComponentName("com.samsung.android.sm_cn", "com.samsung.android.sm.ui.ram.AutoRunActivity");
            } else if (getMobileType().equals("HUAWEI")) { // 华为测试通过
                componentName = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity");
            } else if (getMobileType().equals("vivo")) { // VIVO测试通过
                componentName = ComponentName.unflattenFromString("com.iqoo.secure/.safeguard.PurviewTabActivity");
            } else if (getMobileType().equals("Meizu")) { //万恶的魅族
                // 通过测试,发现魅族是真恶心,也是够了,之前版本还能查看到关于设置自启动这一界面,系统更新之后,完全找不到了,心里默默Fuck!
                // 针对魅族,我们只能通过魅族内置手机管家去设置自启动,所以我在这里直接跳转到魅族内置手机管家界面,具体结果请看图
                componentName = ComponentName.unflattenFromString("com.meizu.safe/.permission.PermissionMainActivity");
            } else if (getMobileType().equals("OPPO")) { // OPPO R8205测试通过
//                componentName = ComponentName.unflattenFromString("com.oppo.safe/.permission.startup.StartupAppListActivity");
                componentName = ComponentName.unflattenFromString("com.oppo.safe/.permission.startup.StartupAppListActivity");
                Intent intentOppo = new Intent();
                intentOppo.setClassName("com.oppo.safe/.permission.startup", "StartupAppListActivity");
                if (context.getPackageManager().resolveActivity(intentOppo, 0) == null) {
                    componentName = ComponentName.unflattenFromString("com.coloros.safecenter/.startupapp.StartupAppListActivity");
                }
            } else if (getMobileType().equals("ulong")) { // 360手机 未测试
                componentName = new ComponentName("com.yulong.android.coolsafe", ".ui.activity.autorun.AutoRunListActivity");
            } else {
                // 以上只是市面上主流机型,由于公司你懂的,所以很不容易才凑齐以上设备
                // 针对于其他设备,我们只能调整当前系统app查看详情界面
                // 在此根据用户手机当前版本跳转系统设置界面
                if (Build.VERSION.SDK_INT >= 9) {
                    intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
                    intent.setData(Uri.fromParts("package", context.getPackageName(), null));
                } else if (Build.VERSION.SDK_INT <= 8) {
                    intent.setAction(Intent.ACTION_VIEW);
                    intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
                    intent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
                }
            }
            intent.setComponent(componentName);
            context.startActivity(intent);
        } catch (Exception e) {//抛出异常就直接打开设置页面
            intent = new Intent(Settings.ACTION_SETTINGS);
            context.startActivity(intent);
        }
    }

    public static boolean hasNetwork(Context context) {
        ConnectivityManager cm =
                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo netInfo = cm.getActiveNetworkInfo();
        if (netInfo != null && netInfo.isConnected()) {
            return true;
        }
        return false;
    }

    /**
     * 判断某个服务是否正在运行的方法
     *
     * @param mContext
     * @param serviceName
     *            是包名+服务的类名(例如:com.beidian.test.service.BasicInfoService )
     * @return
     */
    public static boolean isServiceWork(Context mContext, String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager) mContext
                .getSystemService(Context.ACTIVITY_SERVICE);
        List myList = myAM.getRunningServices(100);
        if (myList.size() <= 0) {
            return false;
        }
        for (int i = 0; i < myList.size(); i++) {
            String mName = myList.get(i).service.getClassName().toString();
            if (mName.equals(serviceName)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }
}

主要代码片段就是以上那些,本文是参考了网上很多文章并总结汇总验证而来。测试机型虽然很多,但是也不是很全面,希望大家多多测试提意见,共同完善推送。
网上还有个开源项目https://github.com/Marswin/MarsDaemon,亲测在我司测试机上很多都不行,但是可以借鉴看原理,参考参考。
demo代码是在开源项目ddpush上面演变而来,代码等下发github,想要详细了解的朋友可以参考参考,跑跑代码。
https://github.com/Ahuanghaifeng/MessagePush

你可能感兴趣的:(Android)