美文网首页Xposed
APP进程保活

APP进程保活

作者: 大苏打6815 | 来源:发表于2019-06-02 20:23 被阅读159次
进程优先级原理

https://developer.android.google.cn/guide/components/processes-and-threads.html?hl=zh-cn

LMK(LowMemoryKiller)
  • 为什么引入LMK?
    进程的启动分冷启动和热启动,当用户退出某一个进程的时候,并不会真正的将进程退出,而是将这个进程放到后台,以便下次启动的时候可以马上启动起来,这个过程名为热启动,这也是Android的设计理念之一。这个机制会带来一个问题,每个进程都有自己独立的内存地址空间,随着应用打开数量的增多,系统已使用的内存越来越大,就很有可能导致系统内存不足。为了解决这个问题,系统引入LowmemoryKiller(简称lmk)管理所有进程,根据一定策略来kill某个进程并释放占用的内存,保证系统的正常运行
  • LMK基本原理
    所有应用进程都是从zygote孵化出来的,记录在AMS中mLruProcesses列表中,由AMS进行统一管理,AMS中会根据进程的状态更新进程对应的oom_adj值,这个值会通过文件传递到kernel中去,kernel有个低内存回收机制,在内存达到一定阀值时会触发清理oom_adj值高的进程腾出更多的内存空间
  • LMK杀进程标准

minfree(存放6个数值,单位内存页面数(一个页面4kb))

adb shell
su
cat /sys/module/lowmemorykiller/parameters/minfree

adj

cat /sys/module/lowmemorykiller/parameters/adj

查看进程的adj值

/proc/<pid>/oom_adj
/proc/<pid>/oom_score_adj

内存阈值在不同的手机上不一样,一旦低于该值,Android便开始按顺序关闭进程. 因此Android开始结束优先级最低的空进程,即当可用内存小于180MB(46080)

常用的保活方式
  1. Activity提权
  2. Service提权
  3. 广播拉活
  4. “全家桶”拉活
  5. Service机制(Sticky)拉活
  6. 账户同步拉活
  7. JobScheduler拉活
  8. 推送拉活
  9. Native拉活(NDK)
  10. 双进程守护(JobScheduler和两个Service结合用)
Activity提权
  • 原理
    监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素透明的 Activity,在用户解锁时将 Activity 销毁掉,从而达到提高进程优先级的作用。
  • 代码实现
  1. 创建1个像素的Activity KeepActivity
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.width = 1;
attributes.height = 1;
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);

manifest注册
styles添加主题样式

  1. 创建广播接收者KeepReceiver
if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
    //灭屏 开启1px activity
    KeepManager.getInstance().startKeep(context);
} else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
    //亮屏 关闭
    KeepManager.getInstance().finishKeep();
}
  1. 创建广播注册管理单例类KeepManager
    注册/反注册广播
    启动/关闭keepActivity
    设置keepActivity弱引用
Service提权
广播拉活
“全家桶”拉活
  • 有多个app在用户设备上安装,只要开启其中一个就可以将其他的app也拉活。比如手机里装了手Q、QQ空间、兴趣部落等等,那么打开任意一个app后,其他的app也都会被唤醒。
Service机制(Sticky)拉活
  • 将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活
  1. START_STICKY:
    “粘性”。如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
  2. START_NOT_STICKY:
    “非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
  3. START_REDELIVER_INTENT:
    重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
  4. START_STICKY_COMPATIBILITY:
    START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
  • 只要 targetSdkVersion 不小于5,就默认是 START_STICKY。
    但是某些ROM 系统不会拉活。并且经过测试,Service 第一次被异常杀死后很快被重启,第二次会比第一次慢,第三次又会比前一次慢,一旦在短时间内 Service 被杀死4-5次,则系统不再拉起。
账户同步拉活
  • 手机系统设置里会有“帐户”一项功能,任何第三方APP都可以通过此功能将数据在一定时间内同步到服务器中去。系统在将APP帐户同步时,会将未启动的APP进程拉活https://github.com/googlesamples/android-BasicSyncAdapter.比较繁琐,效果还不一定好,这里就不研究了.
JobScheduler拉活(为后续双进程守护做准备)
  • JobScheduler允许在特定状态与特定时间间隔周期执行任务。可以利用它的这个特点完成保活的功能,效果即开启一个定时器,与普通定时器不同的是其调度由系统完成。
  • 创建一个JobService
    注意setPeriodic方法
    在7.0以上如果设置小于15min不起作用,可以使用setMinimumLatency设置延时启动,并且轮询
推送拉活
  • 根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送。
Native拉活(NDK)
  • Native fork子进程用于观察当前app主进程的存亡状态。对于5.0以上成功率极低。
双进程守护(最后一个当然是重点啦)
  • 两个进程共同运行,如果有其中一个进程被杀,那么另外一个进程就会将被杀的进程重新拉起,结合JobScheduler一起拉活,这种效率是最高的。会附上核心代码。
public class LocalService extends Service {
    private MyBinder myBinder;

    class MyBinder extends IMyAidlInterface.Stub{

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                               double aDouble, String aString) throws RemoteException {

        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        myBinder = new MyBinder();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("deamon", "deamon",
                    NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = (NotificationManager) getSystemService(
                    Context.NOTIFICATION_SERVICE);
            if (manager == null)
                return;
            manager.createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this,
                    "deamon").setAutoCancel(true).setCategory(
                    Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
                    NotificationManager.IMPORTANCE_LOW).build();
            startForeground(10, notification);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            //如果 18 以上的设备 启动一个Service startForeground给相同的id
            //然后结束那个Service
            startForeground(10, new Notification());
            startService(new Intent(this, InnnerService.class));
        } else {
            startForeground(10, new Notification());
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        bindService(new Intent(this, RemoteService.class), new MyServiceConnection(),
                BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //回调

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            //
            startService(new Intent(LocalService.this, RemoteService.class));
            bindService(new Intent(LocalService.this, RemoteService.class), new MyServiceConnection(),
                    BIND_AUTO_CREATE);
        }
    }

    public static class InnnerService extends Service {

        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(10, new Notification());
            stopSelf();
        }

        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
}
public class RemoteService extends Service {
    private MyBinder myBinder;

    class MyBinder extends IMyAidlInterface.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                               double aDouble, String aString) throws RemoteException {

        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        myBinder = new MyBinder();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("deamon", "deamon",
                    NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = (NotificationManager) getSystemService(
                    Context.NOTIFICATION_SERVICE);
            if (manager == null)
                return;
            manager.createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this,
                    "deamon").setAutoCancel(true).setCategory(
                    Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
                    NotificationManager.IMPORTANCE_LOW).build();
            startForeground(10, notification);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            //如果 18 以上的设备 启动一个Service startForeground给相同的id
            //然后结束那个Service
            startForeground(10, new Notification());
            startService(new Intent(this, InnnerService.class));
        } else {
            startForeground(10, new Notification());
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        bindService(new Intent(this, LocalService.class), new MyServiceConnection(),
                BIND_AUTO_CREATE);
        return super.onStartCommand(intent, flags, startId);
    }

    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            startService(new Intent(RemoteService.this, LocalService.class));
            bindService(new Intent(RemoteService.this, LocalService.class),
                    new MyServiceConnection(), BIND_AUTO_CREATE);
        }
    }

    public static class InnnerService extends Service {

        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(10, new Notification());
            stopSelf();
        }

        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
}
@SuppressLint("NewApi")
public class MyJobService extends JobService {
    private static final String TAG = "MyJobService";

    public static void startJob(Context context) {
        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
                Context.JOB_SCHEDULER_SERVICE);
        JobInfo.Builder builder = new JobInfo.Builder(10,
                new ComponentName(context.getPackageName(),
                        MyJobService.class.getName())).setPersisted(true);
        /**
         * I was having this problem and after review some blogs and the official documentation,
         * I realised that JobScheduler is having difference behavior on Android N(24 and 25).
         * JobScheduler works with a minimum periodic of 15 mins.
         *
         */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //7.0以上延迟1s执行
            builder.setMinimumLatency(1000);
        } else {
            //每隔1s执行一次job
            builder.setPeriodic(1000);
        }
        jobScheduler.schedule(builder.build());
    }

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.e(TAG, "开启job");
        //7.0以上轮询
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            startJob(this);
        }
        //判断服务是否在运行
        boolean isLocalServiceRun = isServiceRunning(this, LocalService.class.getName());
        boolean isRemoteServiceRun = isServiceRunning(this, RemoteService.class.getName());
        if (!isLocalServiceRun || !isRemoteServiceRun) {

            startService(new Intent(this, LocalService.class));
            startService(new Intent(this, RemoteService.class));
        }
        return false;
    }

    private boolean isServiceRunning(Context context, String serviceName) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(10);
        for (ActivityManager.RunningServiceInfo runningService : runningServices) {
            if (TextUtils.equals(runningService.service.getClassName(), serviceName)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        return false;
    }
}

总结:目前没有说百分之百保证能拉活的一些方法,除非你叫厂商给你开白名单。你不是大厂的APP并且有几千万用户的没这个必要,一般来说你给不起厂商这个钱。以上说的这些拉活其实是些流氓软件常常用到的,有风险。并且慎用,这些APP装在手机上面虽然功能实现了,但是可能会造成内存不足(保活了就释放不了了嘛,根据需求)。
应用场景:
比如你的APP对于用户地理位置很敏感,需要5分钟上传一次给服务器记录每个用户的地理位置,按照普通的做法没保活的话那肯定实现不了嘛,用这种方式是可以的。
自身经历:
几年前因为业务需要,跟着我师父他们公司做了一个短信扣费的功能的,也就是传说中的SP吧,属于打擦边球的,也算是属于和中国xx合作。我用的广播的形式,只要用户一启动APP,就自动获取手机信息,包括SIM卡,上传到服务器,然后服务器根据这些信息直接去扣费,或者服务器给客户端发送什么指令,要用户根据指令发送短信(目前也可以实现),如果是在2012年左右,这项业务是非常非常赚钱的(可惜那时候我还没学安卓).如果是4.4手机系统的话,收到扣费信息都可以屏蔽掉,现在都是高版本的手机一般来说屏蔽不了短信了。那时候各项机制都不是特别健全。所以很多这种。所以,少去不正经的网站下载小游戏,一不小心,你口袋里面20块可能就没了。

相关文章

  • 关于 Android 进程保活

    关于 Android 进程保活 Android进程保活手段主要分3种: 1:利用不同的app进程使用广播来进...

  • APP进程保活

    进程优先级原理 https://developer.android.google.cn/guide/compone...

  • 第十六周 进程保活

    话题:进程保活 这个问题时常在面试中被问到关键字:Android 进程保活招式大全 参考答案 1.进程保活方案 -...

  • 进程保活与拉活

    进程相关知识梳理 Activity 1像素保活 前台服务保活 账户同步拉活 JobScheduler 拉活 双进程...

  • 进程相关知识梳理

    进程保活与拉活 进程的一些常识 系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,...

  • 关于进程保活的两三事——新手升级经验卡

    首先,先搁下几个问题,什么是进程保活?为什么要有进程保活?最后才是进程保活要怎么实现??相信大家会迫不及待跳到最后...

  • Android进程保活实践总结

    Android进程保活

  • 深度剖析APP保活案例

    这是作者在去年处理的一个关于进程保活的案例 一. 引言 1.1 保活概述 什么是保活?保活就是在用户主动杀进程,或...

  • 进程保活方案学习

    进程保活方案 进程保活主要有两个方案 提高进程优先级,降低死亡几率 在进程被杀死后进行拉活 进程为什么会死亡 从L...

  • Android 黑科技保活实现原理揭秘

    一直以来,App 进程保活都是各大厂商,特别是头部应用开发商永恒的追求。 毕竟App 进程死了,就什么也干不了了;...

网友评论

    本文标题:APP进程保活

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