Android进程保活主流方案

作者: 今日Android | 来源:发表于2020-10-19 11:44 被阅读0次

    简介

    进程保活对资讯类的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提示。
    [图片上传失败...(image-3f45a3-1603077932504)]

    单进程广播守护

    本方案主要是通过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下拉起服务。
    [图片上传失败...(image-cf605e-1603077932501)]

    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%),但无法抵抗应用–强制终止。
    [图片上传失败...(image-2d95ad-1603077932499)]

    NDK双进程守护

    由于NDK进程优先级非常高,所以我们可以把上面的Java双进程守护放到NDK实现。本方案主要是使用C++ fork()一个子进程来保护APP进程,两者通过socket连接,一旦APP进程被杀死,socket连接断开,守护进程就知道该启动APP进程了。PS:采用socket方案要比轮询更节省资源。


    在这里插入图片描述

    项目结构如下。
    [图片上传失败...(image-f5fb9c-1603078827743)]

    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()无法启动应用/服务…

    本文在开源项目:https://github.com/Android-Alvin/Android-LearningNotes 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...

    相关文章

      网友评论

        本文标题:Android进程保活主流方案

        本文链接:https://www.haomeiwen.com/subject/trjkmktx.html