背景
最近公司的项目需要及时聊天功能,聊天功能基本上已经完成,采用的是自己搭建的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