前言

强烈建议不要这么做,不仅仅从用户角度考虑,它只会滋生更多的流氓应用,拖垮Android 平台的流畅性(假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)没错,我们的Android手机就是一步一步的被上面这些场景给拖卡机的。)。作为Android开发者也有责任去维护Android的生态环境。现在很多Android开发工程师,主力机居然是iPhone而不是Android设备,感到相当悲哀。

这种做法其实是捡了芝麻丢了西瓜,最终倒霉的还是安卓开发者!越来越多的人转向苹果阵营,到时候你想写点良心代码都不会有人来买你帐了!看看现在公交车上的情景,10个里有7、8个人是苹果机,此情此景那部分写流氓代码的安卓程序员还能为自己写了一个用户怎么关都关不了的程序而自豪么?!

本文只作技术探讨,如果希望找到进程永生的方法,可能要失望了。

支付宝、微信如何实现保活常驻系统后台?

1、与手机厂商沟通好,把它们app放进系统白名单,降低omm_adj值,尽量保证进程不被系统杀死。
2、常用方法:

  • 开启前台Service(效果好,推荐)
  • Service中循环播放一段无声音频(效果较好,但耗电量高,谨慎使用
  • 双进程守护(Android 5.0前有效)
  • JobScheduler(Android 5.0后引入,8.0后失效)
  • 1 像素activity保活方案(不推荐)
  • 广播锁屏、自定义锁屏(不推荐)
  • 第三方推送SDK唤醒(效果好,缺点是第三方接入)

PS:不以节能来维持进程保活的手段,都是耍流氓。

什么是omm_adj?

Android有一个oom的机制,系统会根据进程的优先级,给每个进程一个oom权重值,当系统内存紧张时,系统会根据这个优先级去选择将哪些进程杀掉,以腾出空间保证更高优先级的进程能正常运行。要想让进程长期存活,提高优先级是个不二之选。这个可以在adb中,通过以下命令查看:su cat /proc/pid/oom_adj   这个值越小,说明进程的优先级越高,越不容易被进程kill掉。

如果是负数,表示该进程为系统进程,肯定不会被杀掉,
如果是0,表示是前台进程,即当前用户正在操作的进程,除非万不得已,也不会被杀掉,
如果是1,表示是可见进程,通常表示有一个前台服务,会在通知栏有一个划不掉的通知,比如放歌,下载文件什么的。
再增大,则优先级逐渐降低,顺序为服务进程,缓存进程,空进程等等。

常见的保活手段:

1)开启前台Service

原理:通过使用 startForeground()方法将当前Service置于前台来提高Service的优先级。需要注意的是,对API大于18而言 startForeground()方法需要弹出一个可见通知,如果你觉得不爽,可以开启另一个Service将通知栏移除,其oom_adj值还是没变的。实现代码如下:

a) DaemonService.java
/**前台Service,使用startForeground
 * 这个Service尽量要轻,不要占用过多的系统资源,否则
 * 系统在资源紧张时,照样会将其杀死
 */
public class DaemonService extends Service {
    private static final String TAG = "DaemonService";
    public static final int NOTICE_ID = 100;

@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
        if(Contants.DEBUG)
            Log.d(TAG,"DaemonService---->onCreate被调用,启动前台service");
        //如果API大于18,需要弹出一个可见通知
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            builder.setContentTitle("KeepAppAlive");
            builder.setContentText("DaemonService is runing...");
            startForeground(NOTICE_ID,builder.build());
            // 如果觉得常驻通知栏体验不好
            // 可以通过启动CancelNoticeService,将通知移除,oom_adj值不变
            Intent intent = new Intent(this,CancelNoticeService.class);
            startService(intent);
        }else{
            startForeground(NOTICE_ID,new Notification());
        }
    } 
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 如果Service被终止,当资源允许情况下,重启service
        return START_STICKY;
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
        // 如果Service被杀死,干掉通知
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
            NotificationManager mManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
            mManager.cancel(NOTICE_ID);
        }
        if(Contants.DEBUG)
            Log.d(TAG,"DaemonService---->onDestroy,前台service被杀死");
        // 重启自己
        Intent intent = new Intent(getApplicationContext(),DaemonService.class);
        startService(intent);
    }
}
讲解一下:
       这里还用到了两个技巧:一是在onStartCommand方法中返回START_STICKY,其作用是当Service进程被kill后,系统会尝试重新创建这个Service,且会保留Service的状态为开始状态,但不保留传递的Intent对象,onStartCommand方法一定会被重新调用。其二在onDestory方法中重新启动自己,也就是说,只要Service在被销毁时走到了onDestory这里我们就重新启动它。

b) CancelNoticeService.java
/** 移除前台Service通知栏标志,这个Service选择性使用 */
public class CancelNoticeService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2){
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(DaemonService.NOTICE_ID,builder.build());
            // 开启一条线程,去移除DaemonService弹出的通知
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 延迟1s
                    SystemClock.sleep(1000);
                    // 取消CancelNoticeService的前台
                    stopForeground(true);
                    // 移除DaemonService弹出的通知
                    NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
                    manager.cancel(DaemonService.NOTICE_ID);
                    // 任务完成,终止自己
                    stopSelf();
                }
            }).start();
        }
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

c) AndroidManifest.xml
<service android:name=".service.DaemonService"
         android:enabled="true"
          android:exported="true"
          android:process=":daemon_service"/>
<service android:name=".service.CancelNoticeService"
            android:enabled="true"
            android:exported="true"
            android:process=":service"/>

补充:
同时启动两个service,共享同一个NotificationID,并且将他们同时置为前台状态,此时会出现两个前台服务,但通知管理器里只有一个关联的通知。 这时我们在其中一个服务中调用 stopForeground(true),这个服务前台状态会被取消,同时状态栏通知也被移除。另外一个服务并没有受到影响,还是前台服务状态,但是此时,状态栏通知已经没了! 这就是支付宝的黑科技。

2)循环播放无声音频

a) PlayerMusicService.java
/**循环播放一段无声音频,以提升进程优先级*/
public class PlayerMusicService extends Service {
    private final static String TAG = "PlayerMusicService";
    private MediaPlayer mMediaPlayer;
 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    @Override
    public void onCreate() {
        super.onCreate();
        if(Contants.DEBUG)
            Log.d(TAG,TAG+"---->onCreate,启动服务");
        mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
        mMediaPlayer.setLooping(true);
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                startPlayMusic();
            }
        }).start();
        return START_STICKY;
    }
 
    private void startPlayMusic(){
        if(mMediaPlayer != null){
            if(Contants.DEBUG)
                Log.d(TAG,"启动后台播放音乐");
            mMediaPlayer.start();
        }
    }
 
    private void stopPlayMusic(){
        if(mMediaPlayer != null){
            if(Contants.DEBUG)
                Log.d(TAG,"关闭后台播放音乐");
            mMediaPlayer.stop();
        }
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
        stopPlayMusic();
        if(Contants.DEBUG)
            Log.d(TAG,TAG+"---->onCreate,停止服务");
        // 重启
        Intent intent = new Intent(getApplicationContext(),PlayerMusicService.class);
        startService(intent);
    }
}
b) AndroidManifest.xml
<service android:name=".service.PlayerMusicService"
          android:enabled="true"
          android:exported="true"
          android:process=":music_service"/>

3)双进程守护

  • 开启2个服务分别在不同的进程里面,根据AIDL进行进程之间通信
  • 本地服务跟远程服务互相绑定,当本地服务开启成功,开启远程服务,然后跟远程服务绑定。
  • 反之,当其中一个进程出现异常,另一个进程会马上把这个出现异常的进程重新启动。

首先是一个AIDL接口,两边的Service都要通过继承Service_1.Stub来实现AIDL接口中的方法,这里做一个空实现,目的是为了实现进程通信。接口声明如下:
package com.ph.myservice;
interface Service_1 {
    String getName();
}

然后是两个Service,为了保持连接,内部写一个内部类实现ServiceConnection的接口,当外部杀了其中一个进程的时候,会进入onDisConnection中,那么此时要做的就是start和bind另一个进程,因为Service的启动是可以多次的,所以这样是没问题的,代码如下:

package com.ph.myservice;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
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.os.RemoteException;
import android.widget.Toast;

import java.util.List;

public class LocalService extends Service {
    private ServiceConnection conn;
    private MyService myService;

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

@Override
    public void onCreate() {
        super.onCreate();
        init();
    }

private void init() {
        if (conn == null) {
            conn = new MyServiceConnection();
        }
        myService = new MyService();
    }

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(getApplicationContext(), "本地进程启动", Toast.LENGTH_LONG).show();
        Intent intents = new Intent();
        intents.setClass(this, RemoteService.class);
        bindService(intents, conn, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

class MyService extends Service_1.Stub {
        @Override
        public String getName() throws RemoteException {
            return null;
        }
    }

class MyServiceConnection implements ServiceConnection {

@Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            System.out.println("获取连接");
        }

@Override
        public void onServiceDisconnected(ComponentName name) {
            Toast.makeText(LocalService.this, "远程连接被干掉了", Toast.LENGTH_SHORT).show();
            LocalService.this.startService(new Intent(LocalService.this, RemoteService.class));
            LocalService.this.bindService(new Intent(LocalService.this, RemoteService.class), conn, Context.BIND_IMPORTANT);
         }
       }
    }

远程服务类如下:

package com.ph.myservice;
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.os.RemoteException;
import android.widget.Toast;

public class RemoteService extends Service {
    private MyBinder binder;
    private ServiceConnection conn;

@Override
    public void onCreate() {
        super.onCreate();
        // System.out.println("远程进程开启");
        init();
  }

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(getApplicationContext(), "远程进程启动", Toast.LENGTH_LONG).show();
        Intent intents = new Intent();
        intents.setClass(this, LocalService.class);
        bindService(intents, conn, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

private void init() {
        if (conn == null) {
            conn = new MyConnection();
        }
        binder = new MyBinder();
    }

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

static class MyBinder extends Service_1.Stub {
        @Override
        public String getName() throws RemoteException {
            return "远程连接";
        }
    }

class MyConnection implements ServiceConnection {

@Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            System.out.println("获取远程连接");
        }

@Override
        public void onServiceDisconnected(ComponentName nme) {
            Toast.makeText(RemoteService.this, "本地连接被干掉了", Toast.LENGTH_SHORT).show();
            RemoteService.this.startService(new Intent(RemoteService.this,
                    LocalService.class));
            RemoteService.this.bindService(new Intent(RemoteService.this,
                    LocalService.class), conn, Context.BIND_IMPORTANT);
        }
    }

}

布局文件里要加上声明

<service android:name=".LocalService" />
<service android:name=".RemoteService" android:process=":remote" />

实际情况我个人测试,在5.0以下的模拟器上是没问题的,不管多次从系统的进程里kill掉,也还是会重新启动tos,但是5.0以上这种方法是无效的,5.0以上Android应该是意识到了这种双进程守护的方式,因此修改了一下源码,让这种双进程保活应用的方式无效。因此,针对5.0以上,我们采用另一种方案。

4)JobScheduler执行任务调度保活

JobScheduler这个类是21版本google新提供的api,参考链接

5)一个像素activity保活方案

一个像素的方案网上也非常多不做过多的解释,就是在屏幕关闭的时候打开一个1px的透明的activity据说是QQ的保活方案,屏幕开启的时候再去finsh掉这个activty即可,大致代码如下:
 /** 一个像素的保活界面 */
public class LiveActivity extends BaseActivity {

public static final String TAG="LiveActivity";
    private BroadcastReceiver endReceiver=null;

@Override
    public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        Window window = getWindow();
        window.setGravity(Gravity.LEFT | Gravity.TOP);
        WindowManager.LayoutParams params = window.getAttributes();
        params.x = 0;
        params.y = 0;
        params.height = 1;
        params.width = 1;
        window.setAttributes(params);
        //结束该页面的广播
        endReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                finish();
            }
        };
        registerReceiver(endReceiver, new IntentFilter("finish"));
        //检查屏幕状态
        checkScreen();
    }

@Override
    protected void onResume() {
        super.onResume();
        checkScreen();
    }

/**  检查屏幕状态  isScreenOn为true  屏幕“亮”结束该Activity */
    private void checkScreen() {
        PowerManager pm = (PowerManager) LiveActivity.this.getSystemService(Context.POWER_SERVICE);
        boolean isScreenOn = pm.isScreenOn();
        if (isScreenOn) {
            finish();
        }
    }

@Override
    protected void onDestroy() {
        super.onDestroy();
        if (endReceiver!=null){
            unregisterReceiver(endReceiver);
        }
    }
}

当然还需要在设置1像素Activity的样式
    <style name="LiveActivity" parent="android:Theme.Holo.Light.NoActionBar">
        <item name="android:windowFrame">@null</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:backgroundDimEnabled">false</item>
        <item name="android:windowBackground">@null</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

广播的代码:

public class LiveBroadcastReceiver extends BroadcastReceiver {
    public static final String TAG="LiveActivity";
    private KeepLiveManager mKeepLiveManager;

private HomeActivity mHomeActivity;
    public LiveBroadcastReceiver(HomeActivity homeActivity){
              this.mHomeActivity=homeActivity;
        mKeepLiveManager=mHomeActivity.getKeepLiveManger();
    }

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onReceive(Context context, Intent intent) {
        switch (intent.getAction()){
            case Intent.ACTION_SCREEN_OFF://屏幕被关闭
                Intent it=new Intent(context, LiveActivity.class);
                it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(it);
                break;
            case Intent.ACTION_SCREEN_ON://屏幕被打开
                context.sendBroadcast(new Intent("finish"));
                /**
                 * 以下的代码会导致屏幕解锁后会出现返回主界面的情况
                 */
//                Intent main = new Intent(Intent.ACTION_MAIN);
               main.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
//                //注释掉这段避免在再次打开APP界面时出现的返回主界面的问题
//                //main.addCategory(Intent.CATEGORY_HOME);
//                context.startActivity(main);
                break;
        }
    }
}

本文综合以下资料所得:
https://blog.csdn.net/andrexpert/article/details/75045678
http://zhoujianghua.com/2015/07/28/black_technology_in_alipay/
https://blog.csdn.net/zhoukongxiao/article/details/80611059 
https://blog.csdn.net/pan861190079/article/details/72773549 
https://www.zhihu.com/question/29826231/answer/71956560

Android之APP保活相关推荐

  1. android 音乐app 保活,aggregationProject聚合项目

    aggregationProject聚合项目 介绍 Android聚合项目,包含自定义分享模块.自定义音频播放模块.okhttp的封装模块.加载图片工具模块.app保活模块.自定义视频模块等 软件架构 ...

  2. Android 自用 App保活——音乐播放保活适配8.0 (贼好用)

    又是好久没有积累东西了.惭愧,惭愧...手动哭泣.闲话说到这里,下面我介绍一种新的 App 保活方式哈,目前用小米家族手机 涵盖 Android 5.0 到 Android 8.1家族的测试.结论是, ...

  3. 关于Android安卓APP保活 - 安卓消息推送详解 - 安卓端外推送离线推送

    转自:http://zhangtielei.com/posts/blog-android-push.html 说Android端外推送比较烦,实际有两层意思:首先是说实现上比较麻烦,至今业界也没有找到 ...

  4. android音乐播放器保活,Android 自用 App保活——音乐播放保活适配8.0 (贼好用) | 韩小呆...

    /** * Content:后台播放音乐达到保活目的 * Actor:韩小呆 ヾ(✿゚▽゚)ノ * Time: 2018/10/12 10:47 * Update: * Time: */ public ...

  5. Android安卓进程保活(二)

    Android进程保活·设置前台Service,提升App进程优先级 Android进程 此文章代码Github上有提交:https://github.com/NorthernBrain/proces ...

  6. Android平台App进程优先级

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/52351047 文章出自:薛瑄的博客 你也可以查看我的其他同类文章,也会让你有一定的 ...

  7. Android最强保活黑科技的最强技术实现

    大家好,我是老玩童.今天来跟大家分享TIM最强保活思路的几种实现方法.这篇文章我将通过ioctl跟binder驱动交互,实现以最快的方式唤醒新的保活服务,最大程度防止保活失败.同时,我也将跟您分享,我 ...

  8. Android安卓进程保活(一)1像素且透明Activity

    Android进程保活·1像素且透明Activity提升App进程优先级 Android进程 此文章代码Github上有提交:https://github.com/NorthernBrain/proc ...

  9. 从framework角度看app保活问题

    问题背景 最近在群里看到群友在讨论app保活的问题,回想之前做应用(运动类)开发时也遇到过类似的需求,于是便又来了兴趣,果断加入其中,和群友展开了激烈的讨论 不少群友的想法和我当初的想法一样,这特么保 ...

最新文章

  1. 详解计算机视觉中的特征点检测:Harris / SIFT / SURF / ORB
  2. R 绘制 GWAS 研究的 Manhattan 图
  3. bestcoder #56 div 2 B Clarke and problem(dp)
  4. WPF中桌面屏保的制作(主要代码)
  5. 简述python程序的基本构成_(一)Python入门-2编程基本概念:01程序的构成
  6. VTK:相互作用之RubberBand2D
  7. 给刚开始学习Linux的小白们的福利——资源已经分享,可随时下载
  8. 郑州大学计算机科学复试分数线,2021郑州大学考研复试线发布,计算机大涨,部分热门专业达406分...
  9. CSS每日学习笔记(2)
  10. 【珍藏】 2012.NET开发必看资料53个+经典源码77个—下载目录
  11. devops的重要性_为什么DevOps是当今最重要的技术战略
  12. 周末总是被工作打扰_如何在不打扰任何人的情况下问为什么在工作中
  13. 让电脑说话代码_让您的代码为您说话
  14. 参考平面及其高度_施工现场平面布置关键点分析
  15. keras系列︱Sequential与Model模型、keras基本结构功能(一)
  16. Java实现常见的排序算法
  17. JSP九大内置对象以及作用
  18. 爬虫抓取百度指数思路总结
  19. opencv c++ 检测红色HSV 和RGB
  20. AWS实例修改时区及数据库实例修改时区

热门文章

  1. 【介绍】好用的网页查词插件(greasy fork 插件):iciba划词翻译
  2. 一点资讯CEO辞职:任旭阳接任 曾任百度公司副总裁
  3. java婚纱影楼管理系统论文_Springboot+mybatis+html婚纱摄影网站,包括后台管理系统...
  4. 程序人生 | C语言字节对齐问题详解 - 对齐/字节序/位序/网络序等(上)
  5. 【十万字的SpringCloud,你不来看看】
  6. 关联与下钻:快速定位MySQL性能瓶颈的制胜手段
  7. 【多线程】线程池里边都有些什么东西呢
  8. Mangos T1-T6大全
  9. python pandas判断excel某处存在空值,并处理该空值
  10. Codeforces Round #411 (Div. 2) A-F