Android进程保活主流方案

简介

进程保活对资讯类的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()无法启动应用/服务…

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值