简介
进程保活对资讯类的App和即时通讯App的用处很大,但随着这一技术的滥用,各大手机厂商与谷歌都开始逐步收紧机制,进程保活也越来越难实现,可以说如今几乎无法实现100%保活(Android高版本特为尤甚),程序员能做的只是尽可能提升进程存活的几率(优先级)。当然,使用各种技巧提升进程存活几率的前提是对Android进程相关机制有一定的认知。
本文主要介绍一下目前网上主流的保活方案。
1像素保活
本方案主要是利用了安卓熄屏广播拉起仅有1个透明像素的OnePieceActivity来提升进程优先级以达到尽可能不被Kill的目的。
项目结构如下。
AndroidManifest.xml:
<activity
android:name=".OnePieceActivity"
android:excludeFromRecents="true" //不在最近任务列表中展示
android:finishOnTaskLaunch="false" //按Home去主页,再点击图标会进入MainActivity,且销毁本Activity
android:launchMode="singleInstance"
android:theme="@style/OnePieceTheme"> //使用自定义透明style
</activity>
res\values\styles.xml:
<style name="OnePieceTheme" parent="AppTheme">
<!-- 无背景(会使用主题默认背景) -->
<item name="android:windowBackground">@null</item>
<!-- 是否为透明窗口 -->
<item name="android:windowIsTranslucent">true</item>
</style>
KeepAliveManager :该类主要用于控制保活Activity的创建与销毁,并保证只有一个保活Activity实例。还提供了注册、注销接收熄屏、亮屏广播的工具方法。
package com.zyc.onepiece;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import java.lang.ref.WeakReference;
public class KeepAliveManager {
private static final KeepAliveManager instance = new KeepAliveManager();
private WeakReference<OnePieceActivity> activity; //弱引用,防止内存泄漏
private KeepAliveReceiver receiver;
private KeepAliveManager() {
}
public static KeepAliveManager getInstance() {
return instance;
}
public void setKeepLiveActivity(OnePieceActivity activity) {
this.activity = new WeakReference<>(activity);
}
//开启保活Activity
public void startOnePieceActivity(Context context) {
Intent intent = new Intent(context, OnePieceActivity.class);
context.startActivity(intent);
}
//关闭保活Activity
public void finishOnePieceActivity() {
if (activity != null && activity.get() != null) {
activity.get().finish();
}
}
//注册广播
public void registerKeepLiveReceiver(Context context) {
Log.d(MainActivity.TAG, "KeepAliveReceiver已注册");
receiver = new KeepAliveReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
context.registerReceiver(receiver, filter);
}
//注销广播
public void unregisterKeepLiveReceiver(Context context) {
Log.d(MainActivity.TAG, "KeepAliveReceiver已注销");
if (receiver != null) {
context.unregisterReceiver(receiver);
}
}
}
KeepAliveReceiver :接收熄屏、亮屏广播后拉起、结束保活Activity。
package com.zyc.onepiece;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class KeepAliveReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
Log.d(MainActivity.TAG, "屏幕关闭,准备拉起OnePieceActivity");
KeepAliveManager.getInstance().startOnePieceActivity(context);
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
Log.d(MainActivity.TAG, "屏幕开启,准备关闭OnePieceActivity");
KeepAliveManager.getInstance().finishOnePieceActivity();
}
}
}
OnePieceActivity :保活Activity,无界面,看上去是透明的。
package com.zyc.onepiece;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import androidx.appcompat.app.AppCompatActivity;
public class OnePieceActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(MainActivity.TAG, "OnePieceActivity onCreate");
//左上角显示
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
//设置为1像素大小
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.y = 0;
params.width = 1;
params.height = 1;
window.setAttributes(params);
//KeepAliveManager中的保活Activity初始化为本Activity
KeepAliveManager.getInstance().setKeepLiveActivity(this);
}
@Override
protected void onStart() {
Log.d(MainActivity.TAG, "OnePieceActivity onStart");
super.onStart();
}
@Override
protected void onRestart() {
Log.d(MainActivity.TAG, "OnePieceActivity onRestart");
super.onRestart();
}
@Override
protected void onStop() {
Log.d(MainActivity.TAG, "OnePieceActivity onStop");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(MainActivity.TAG, "OnePieceActivity onDestroy");
super.onDestroy();
}
}
MainActivity :主要用于控制广播的注册与注销。
package com.zyc.onepiece;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
public static String TAG = "MyLog";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(MainActivity.TAG, "MainActivity onCreate");
KeepAliveManager.getInstance().registerKeepLiveReceiver(this);
}
@Override
protected void onStart() {
Log.d(MainActivity.TAG, "MainActivity onStart");
super.onStart();
}
@Override
protected void onRestart() {
Log.d(MainActivity.TAG, "MainActivity onRestart");
super.onRestart();
}
@Override
protected void onStop() {
Log.d(MainActivity.TAG, "MainActivity onStop");
super.onStop();
}
@Override
protected void onDestroy() {
KeepAliveManager.getInstance().unregisterKeepLiveReceiver(this);
super.onDestroy();
}
}
运行,发现在笔者的一加6T(Android 10)下应用内熄屏可以拉起1像素保活Activity,但按Home回到主页后,再熄屏虽然可以接到广播,但无法拉起Activity:
设置前台Service
本方案原理是拥有前台Service的进程将拥有更高的优先级,也就更难被回收掉。注意:部分Android版本前台Service会在顶部通知栏露出“马脚”:
可以试着在前台Service创建通知后立马清除它来隐藏自己。利用相同id前台Service会使用同一条通知的特性,创建一个与前台Service有相id的“替罪羊”前台Service,然后结束它,这样通知会被随之清除,但原本的Service可以继续工作。
项目结构如下。
AndroidManifest.xml:
//不加该权限会报错:java.lang.SecurityException: Permission Denial: startForeground from pid=XXX, uid=XXX requires android.permission.FOREGROUND_SERVICE
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
...
<service
android:name=".ScapegoatService"
android:enabled="true"
android:exported="true" />
<service
android:name=".ForegroundService"
android:enabled="true"
android:exported="true" />
ForegroundService :模拟工作的(前台)线程,如有必要会开启ScapegoatService来清除通知栏通知。由于缺乏更多高版本系统样本,Android 8.0以上部分可能不靠谱,不过推荐还是老老实实按谷歌要求发出通知为妙。
package com.zyc.foregroundservice;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.util.Timer;
import java.util.TimerTask;
public class ForegroundService extends Service {
private static final int SERVICE_ID = 1;
private Timer timer;
private TimerTask timerTask;
public static int count;
public ForegroundService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(MainActivity.TAG, "创建前台服务");
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startTask();
//判断版本
if (Build.VERSION.SDK_INT < 18) {//Android4.3以下版本
//直接调用startForeground即可,不会在通知栏创建通知
startForeground(SERVICE_ID, new Notification());
} else if (Build.VERSION.SDK_INT < 24) {//Android4.3 - 7.0之间
Intent scapegoatIntent = new Intent(this, ScapegoatService.class);
startService(scapegoatIntent);
} else {//Android 8.0以上
//经测试,本人的一加6T(Android 10)这样写并不会在通知栏创建通知,其他机型与版本效果仍需考证
startForeground(SERVICE_ID, new Notification());
}
return START_STICKY;
}
/**
* 开启定时任务,count每秒+1
*/
private void startTask() {
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
Log.d(MainActivity.TAG, "服务运行中,count: " + count);
count++;
}
};
timer.schedule(timerTask, 0, 1000);
}
/**
* 结束定时任务
*/
private void stopTask() {
if (timer != null) {
timer.cancel();
timer = null;
}
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
count = 0;
}
@Override
public void onDestroy() {
stopTask();
Log.d(MainActivity.TAG, "销毁前台服务");
super.onDestroy();
}
}
ScapegoatService :拥有和工作线程相同id,启动后立马自行停止并销毁,也就替ForegroundService 消除了通知。
package com.zyc.foregroundservice;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class ScapegoatService extends Service {
private static final int SERVICE_ID = 1; //后台ForegroundService的SERVICE_ID相同
public ScapegoatService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(MainActivity.TAG, "创建前台服务替身");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(SERVICE_ID, new Notification());
stopForeground(true);//移除通知栏消息
stopSelf();
return START_STICKY;
}
@Override
public void onDestroy() {
Log.d(MainActivity.TAG, "销毁前台服务替身");
super.onDestroy();
}
}
MainActivity :启动/停止Service而已,不多赘述。
package com.zyc.foregroundservice;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
public static String TAG = "MyLog";
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent(this, ForegroundService.class);
startService(intent);
}
@Override
protected void onDestroy() {
stopService(intent);
super.onDestroy();
}
}
运行,在Android 5.1.1下可以以此消除通知栏Service提示。
单进程广播守护
本方案主要是通过Service销毁发出广播通知BroadcastReceiver“复活”自己实现进程保活。
项目结构如下。
AndroidManifest.xml:
<receiver
android:name=".KeepAliveReceiver"
android:enabled="true"
android:exported="true" />
<service
android:name=".KeepAliveService"
android:enabled="true"
android:exported="true" />
KeepAliveReceiver :为啥用BroadcastReceiver 而不直接在 Service的onDestroy()方法中重启服务?因为这种方式启动的Service仍旧和原进程"在一起",会被一并杀死,而BroadcastReceiver 接到广播启动的则与原进程有隔离性,可以存活。
package com.zyc.demo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class KeepAliveReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(MainActivity.LOG_TAG, "收到保活广播");
if (!MainActivity.isServiceRunning(context.getApplicationContext(), KeepAliveService.class)) {
Log.d(MainActivity.LOG_TAG, "检测到服务未在运行,启动服务");
Intent serviceIntent = new Intent(context, KeepAliveService.class);
context.startService(serviceIntent);
} else {
Log.d(MainActivity.LOG_TAG, "检测到服务正在运行,无需再次启动");
}
}
}
KeepAliveService :该Service会在创建后会对变量count 执行 +1/秒 定时任务,在服务终止/创建时会保存/读取count ,以保证count 的状态不会丢失。保活手段体现在:
- onStartCommand()返回 START_STICKY,Service所在进程被杀死后系统会尝试再次启动,但具体启动时机由系统决定。
- onDestroy()方法发送出Broadcast,等KeepAliveReceiver 收到后来启动自己。
package com.zyc.demo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
public class KeepAliveService extends Service {
private Timer timer;
private TimerTask timerTask;
public static int count;
@Override
public void onCreate() {
Log.d(MainActivity.LOG_TAG, "服务创建了");
int save = SharedPreferenceTool.getInstance(getApplicationContext()).getInt("count", -1);
if (save == -1) {
this.count = 0;
Log.d(MainActivity.LOG_TAG, "count首次启动,从0开始计数");
} else {
this.count = save;
Log.d(MainActivity.LOG_TAG, "count从上次保存的 " + save + " 开始计数");
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startTask();
Log.d(MainActivity.LOG_TAG, "服务启动了");
return START_STICKY;
}
/**
* 开启定时任务,count每秒+1
*/
private void startTask() {
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
Log.d(MainActivity.LOG_TAG, "服务运行中,count: " + count);
count++;
}
};
timer.schedule(timerTask, 0, 1000);
}
/**
* 结束定时任务
*/
private void stopTask() {
if (timer != null) {
timer.cancel();
timer = null;
}
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
count = 0;
}
@Override
public void onDestroy() {
stopTask();
Log.d(MainActivity.LOG_TAG, "服务停止了");
Intent intent = new Intent(this, KeepAliveReceiver.class);
sendBroadcast(intent);
Log.d(MainActivity.LOG_TAG, "发送保活广播");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
MainActivity :主要用于开启Service,并提供了判断指定Service是否运行的工具方法。count放在MainActivity 而不是Service是因为Service的onDestroy()在异常结束时不一定被调用。
package com.zyc.demo;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
public static String LOG_TAG = "MyLog";
private Intent serviceIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (!isServiceRunning(getApplicationContext(), KeepAliveService.class)) {
Log.d(LOG_TAG, "检测到服务未在运行,启动服务");
serviceIntent = new Intent(this, KeepAliveService.class);
startService(serviceIntent);
} else {
Log.d(LOG_TAG, "检测到服务正在运行,无需再次启动");
}
}
/**
* 判断某个Service是否在运行
* @param context
* @param serviceClass 需要查看的Service的Class
* @return
*/
public static boolean isServiceRunning(Context context, Class serviceClass) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo runningServiceInfo : activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(runningServiceInfo.service.getClassName())) {
return true;
}
}
return false;
}
@Override
protected void onDestroy() {
if (serviceIntent != null) {
stopService(serviceIntent);
}
SharedPreferenceTool.getInstance(getApplicationContext()).putInt("count", KeepAliveService.count);
Log.d(LOG_TAG, "count保存了");
super.onDestroy();
}
}
SharedPreferenceTool :一个用于保存例中count的SharedPreference工具类。切记用context.getApplicationContext()传入Context ,因为这里的instance是一个static且强引用的,如果随处使用XXXActivity.this传入Context ,这些Activity会随着instance的全局存在而难以回收,最终将造成内存泄漏。
package com.zyc.demo;
import android.content.Context;
import android.content.SharedPreferences;
public class SharedPreferenceTool {
public static SharedPreferenceTool instance;
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor editor;
private SharedPreferenceTool(Context context) {
sharedPreferences = context.getSharedPreferences("preferences", Context.MODE_PRIVATE);
editor = sharedPreferences.edit();
}
public static SharedPreferenceTool getInstance(Context context) {
if (instance == null) {
synchronized (SharedPreferenceTool.class) {
if (instance == null) {
// 使用双重同步锁
instance = new SharedPreferenceTool(context);
}
}
}
return instance;
}
/**
* 往SharedPreference存放整型数据
*/
public void putInt(String key, int value) {
editor.putInt(key, value);
editor.commit();
}
/**
* 从SharedPreference取出整型数据
*/
public int getInt(String key, int defaultValue) {
return sharedPreferences.getInt(key, defaultValue);
}
}
运行,虽然有时Service重启并不及时,但在Android 5.1.1总体而言count被很大程度保留在后台运转。但此方法无法对抗 设置–应用程序–强制停止,也无法在Android 10下拉起服务。
AIDL双进程守护
本方案主要是两个(不同进程)Service通过AIDL“互相拉起”用于实现进程保活。
项目结构如下。
AndroidManifest.xml:
<!-- 进程名remote,冒号开头表示私有进程 -->
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote" />
<service
android:name=".LocalService"
android:enabled="true"
android:exported="true" />
AIDL:
package com.zyc.aidlkeepalive;
interface IKeepAliveAidlInterface {}
LocalService:与进程RemoteService相互绑定,当RemoteService被终止导致onServiceDisconnected()方法被触发时再次将其启动。
package com.zyc.aidlkeepalive;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
public class LocalService extends Service {
private IBinder binder;
private ServiceConnection serviceConnection;
private Timer timer;
private TimerTask timerTask;
private int count = 0;
public LocalService() {}
@Override
public void onCreate() {
super.onCreate();
Log.d(MainActivity.TAG, "LocalService onCreate");
binder = new LocalServiceBinder();
serviceConnection = new LocalServiceConnection();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(MainActivity.TAG, "LocalService onStartCommand");
startTask();
wakeService();
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
Log.d(MainActivity.TAG, "LocalService onBind");
return binder;
}
@Override
public void onDestroy() {
stopTask();
stopSelf();
unbindService(serviceConnection);
Log.d(MainActivity.TAG, "LocalService onDestroy");
super.onDestroy();
}
private void wakeService() {
if (!MainActivity.isServiceRunning(this, RemoteService.class)) {
startService(new Intent(this, RemoteService.class));
}
bindService(new Intent(this, RemoteService.class), serviceConnection, Context.BIND_IMPORTANT);
}
class LocalServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(MainActivity.TAG, "触发LocalService onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(MainActivity.TAG, "触发LocalService onServiceDisconnected");
wakeService();
}
}
class LocalServiceBinder extends IKeepAliveAidlInterface.Stub {}
/**
* 开启定时任务,count每秒+1
*/
private void startTask() {
count = 0;
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
Log.d(MainActivity.TAG, "服务运行中,count: " + count);
count++;
}
};
timer.schedule(timerTask, 0, 1000);
}
/**
* 结束定时任务
*/
private void stopTask() {
if (timer != null) {
timer.cancel();
timer = null;
}
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
count = 0;
}
}
RemoteService:与LocalService作用基本相同,不多赘述。
package com.zyc.aidlkeepalive;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
public class RemoteService extends Service {
private IBinder binder;
private ServiceConnection serviceConnection;
private Timer timer;
private TimerTask timerTask;
private int count = 0;
public RemoteService() {}
@Override
public void onCreate() {
super.onCreate();
Log.d(MainActivity.TAG, "RemoteService onCreate");
binder = new RemoteServiceBinder();
serviceConnection = new RemoteServiceConnection();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(MainActivity.TAG, "RemoteService onStartCommand");
startTask();
wakeService();
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
Log.d(MainActivity.TAG, "RemoteService onBind");
return binder;
}
@Override
public void onDestroy() {
stopTask();
stopSelf();
unbindService(serviceConnection);
Log.d(MainActivity.TAG, "RemoteService onDestroy");
super.onDestroy();
}
class RemoteServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(MainActivity.TAG, "触发RemoteService onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(MainActivity.TAG, "触发RemoteService onServiceDisconnected");
wakeService();
}
}
private void wakeService() {
if (!MainActivity.isServiceRunning(this, LocalService.class)) {
startService(new Intent(this, LocalService.class));
}
bindService(new Intent(this, LocalService.class), serviceConnection, Context.BIND_IMPORTANT);
}
class RemoteServiceBinder extends IKeepAliveAidlInterface.Stub {
}
/**
* 开启定时任务,count每秒+1
*/
private void startTask() {
count = 0;
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
Log.d(MainActivity.TAG, "服务运行中,count: " + count);
count++;
}
};
timer.schedule(timerTask, 0, 1000);
}
/**
* 结束定时任务
*/
private void stopTask() {
if (timer != null) {
timer.cancel();
timer = null;
}
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
count = 0;
}
}
MainActivity:
package com.zyc.aidlkeepalive;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MyLog";
private Intent intentLocal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentLocal = new Intent(this, LocalService.class);
startService(intentLocal);
}
public static boolean isServiceRunning(Context context, Class serviceClass) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo runningServiceInfo : activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (runningServiceInfo.service.getClassName().equals(serviceClass.getName())) {
return true;
}
}
return false;
}
}
运行,在Android 5.1.1两个进程可以在一定程度上相互拉起(不是100%),但无法抵抗应用–强制终止。
NDK双进程守护
由于NDK进程优先级非常高,所以我们可以把上面的Java双进程守护放到NDK实现。本方案主要是使用C++ fork()一个子进程来保护APP进程,两者通过socket连接,一旦APP进程被杀死,socket连接断开,守护进程就知道该启动APP进程了。PS:采用socket方案要比轮询更节省资源。
项目结构如下。
AndroidManifest.xml:
<service
android:name=".WorkService"
android:enabled="true"
android:exported="true">
</service>
CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1)
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
Guard:声明JNI方法。
package com.zyc.doubleguard;
public class Guard {
static {
System.loadLibrary("native-lib");
}
public native void create(String userId);
public native void connect();
}
WorkService:模拟APP要被守护的工作服务,不停打印Log。
package com.zyc.doubleguard;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
public class WorkService extends Service {
private static int count = 0;
private Guard guard;
private Timer timer;
private TimerTask timerTask;
public WorkService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d("MyLog", "服务创建");
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
Log.i("MyLog", "服务进行中 count=" + count);
count++;
}
};
guard = new Guard();
guard.create(String.valueOf(Process.myUid()));
guard.connect();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyLog", "服务启动");
startWork();
return START_STICKY;
}
@Override
public void onDestroy() {
stopWork();
Log.d("MyLog", "服务被销毁");
super.onDestroy();
}
private void startWork() {
timer.schedule(timerTask, 0, 3000);
}
private void stopWork() {
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
if (timer != null) {
timer.cancel();
timer = null;
}
}
}
MainActivity:只用来启动服务,不多赘述。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent(this, WorkService.class);
startService(intent);
}
native-lib.cpp:提供了开启子进程、子进程开启socket服务端、客户端连接socket方法。
#include <jni.h>
#include <string>
#include <filesystem>
#include <android/log.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <arpa/inet.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MyLog", __VA_ARGS__)
void guard_start_work();
bool guard_create();
void guard_read_msg();
const char *userId;
/**
* 用于创建一个守护进程
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_zyc_doubleguard_Guard_create(JNIEnv *env, jobject thiz, jstring user_id) {
userId = env->GetStringUTFChars(user_id, 0);
//开进程
pid_t pid = fork();
if (pid < 0) {
LOGE("Guard开进程失败");
} else if (pid == 0) {//子进程
guard_start_work();
} else if (pid > 0) {//父进程
}
env->ReleaseStringUTFChars(user_id, userId);
}
/**
* 守护进程 - 开始
*/
void guard_start_work() {
if (guard_create()) {
guard_read_msg();
}
}
const char *PATH = "/data/data/com.zyc.doubleguard/guard.socket";
int server_lfd = -1;
int server_cfd = -1;
/**
* 守护进程 - 创建socket
*/
bool guard_create() {
server_lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (server_lfd < 0) {
LOGE("Guard socket 初始化错误");
return false;
}
unlink(PATH);//把之前连接的服务端清空
struct sockaddr_un addr;
bzero(&addr, sizeof(sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, PATH);
int bind_res = bind(server_lfd, (const sockaddr *) &addr, sizeof(sockaddr_un));
if (bind_res < 0) {
LOGE("Guard bind错误");
return false;
}
listen(server_lfd, 5);//可以守护5个app
LOGE("Guard 开始listen");
while (true) {
server_cfd = accept(server_lfd, NULL, NULL);
if (server_cfd < 0) {
if (errno == EINTR) {
//client连接失败,重连
continue;
} else {
LOGE("Guard 读取错误");
return 0;
}
} else {
LOGE("进程 %d 连接上了 Guard", server_cfd);
break;
}
}
return true;
}
/**
* 守护进程 - 阻塞读取
*/
void guard_read_msg() {
LOGE("guard_read_msg");
fd_set set;
struct timeval timeout = {3, 0};//3秒超时
while (true) {
FD_ZERO(&set);
FD_SET(server_cfd, &set);
int select_res = select(server_cfd + 1, &set, NULL, NULL, &timeout);
LOGE("select_res:%d", select_res);
if (select_res > 0) {
if (FD_ISSET(server_cfd, &set)) {//保证是指定apk的连接
LOGE("userId: %d 断开", userId);
char temp[256] = {0};
//read是阻塞的,客户端断开之后才会往后执行
int res = read(server_cfd, temp, sizeof(temp));
LOGE("准备启动服务");
//执行启动服务
execlp("am", "am", "startservice", "--user", userId,"com.zyc.doubleguard/com.zyc.doubleguard.WorkService", (char *) NULL);
LOGE("启动服务完成");
break;
}
}
}
}
/**
* app连接守护进程
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_zyc_doubleguard_Guard_connect(JNIEnv *env, jobject thiz) {
LOGE("app准备连接守护进程");
int client_cfd;
struct sockaddr_un addr;
while (true) {
client_cfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (client_cfd < 0) {
LOGE("app连接守护进程启动失败");
return;
}
bzero(&addr, sizeof(sockaddr_un));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, PATH);
int connect_res = connect(client_cfd, (const sockaddr *) &addr, sizeof(sockaddr_un));
if (connect_res < 0) {
//连不上就关闭后睡1秒重连
LOGE("app连接守护进程失败");
close(client_cfd);
sleep(1);
} else {
LOGE("app连接守护进程成功");
break;
}
}
}
运行,发现这一方案看似高大上,但是在诸多国产ROM机型/模拟器上都无法正常运作,不知道是笔者代码有误还是这一方案已经被封杀得很彻底,要么无法fork()出子进程,要么子进程很快变僵尸进程,要么execlp()无法启动应用/服务…