背景
最近公司的项目需要及时聊天功能,聊天功能基本上已经完成,采用的是自己搭建的socket长连接来实现聊天的方按。安排我研究消息推送,主要确保杀死App后还能正常接收消息,重启后也能收到消息。
消息推送的重难点
1.长连接消息收发功能的实现。
2.消息的实时推送,确保消息的达到率。
第一条目前已经实现,不在本文讨论的范围,主要是针对第二点讨论。要做到消息的实时推送,保证消息的到达率,当然是后台服务常驻的实现,关不掉、杀不死、就算杀死也能重启。
经过测试,测试手机包括各种品牌各种系统版本的手机(三星、小米、魅族、联想、华为、vivo、oppo、tcl)、目前博主的方案在5.1及以下手机基本上能完全做到杀死自启动,除了华为,在6.0手机能做到引导用户去授权页面允许后台自启动(包括小米、华为、oppo、魅族),在7.0上基本无效。
Android 进程拉活包括两个层面:
A. 提高进程优先级,降低进程被杀死的概率。
B. 在进程被杀死后,进行拉活。
技术点研究 参考自腾讯的bugly的文章“Android进程保活招式大全”
提高进程优先级,降低进程被杀死的概率。
利用 Activity 提升权限:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。也就是传说中QQ的黑科技,亲测,无效。
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<ActivityManager.RunningServiceInfo> 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
转载自:http://blog.csdn.net/u013692888/article/details/77914181