转发:https://blog.csdn.net/u013692888/article/details/77914181
背景
最近公司的项目需要及时聊天功能,聊天功能基本上已经完成,采用的是自己搭建的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