├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── shihoo │ │ └── daemonlibrary │ │ ├── ApkHelper.java │ │ ├── App.java │ │ ├── MainActivity.java │ │ └── MainWorkService.java │ └── res │ ├── drawable │ ├── ic_launcher.png │ └── icon1.png │ ├── layout │ └── activity_main.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── daemonlibrary ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── shihoo │ │ └── daemon │ │ ├── AbsServiceConnection.java │ │ ├── DaemonEnv.java │ │ ├── ForegroundNotificationUtils.java │ │ ├── IntentWrapper.java │ │ ├── singlepixel │ │ ├── ScreenManager.java │ │ ├── ScreenReceiverUtil.java │ │ └── SinglePixelActivity.java │ │ ├── sync │ │ ├── Authenticator.java │ │ ├── AuthenticatorService.java │ │ ├── StubProvider.java │ │ ├── SyncAdapter.java │ │ └── SyncService.java │ │ ├── watch │ │ ├── JobSchedulerService.java │ │ ├── PlayMusicService.java │ │ ├── WakeUpReceiver.java │ │ ├── WatchDogService.java │ │ └── WatchProcessPrefHelper.java │ │ └── work │ │ └── AbsWorkService.java │ └── res │ ├── drawable │ └── icon1.png │ ├── raw │ └── no_notice.mp3 │ └── values │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | .idea/ 13 | gradle/ 14 | gradlew 15 | gradlew.bat 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DaemonLibrary 2 | 3 | ## 使用方式 4 | #### Step 1. Add the JitPack repository to your build file 5 | 6 | ``` 7 | allprojects { 8 | repositories { 9 | ... 10 | maven { url 'https://jitpack.io' } 11 | } 12 | } 13 | ``` 14 | 15 | #### Step 2. Add the dependency 16 | [![](https://jitpack.io/v/ShihooWang/DaemonLibrary.svg)](https://jitpack.io/#ShihooWang/DaemonLibrary) 17 | ~~~ 18 | dependencies { 19 | implementation 'com.github.wangshihu123:DaemonLibrary:v1.2.1' 20 | } 21 | ~~~ 22 | 23 | ## Android后台保活,这里有你需要的所有姿势。2019,基于API26 Android O 最新版本。 24 | 老规矩,先上项目地址: 25 | https://github.com/wangshihu123/DaemonLibrary 26 | 结合网上各路大神及自己的项目保活实战(在此不方便透露项目),给出了最新的保活姿势。(如有雷同,纯属巧合) 27 | 28 | ## 1.为什么要做Android保活? 29 | 首先我个人并不推荐也不喜欢手机应用通过各种手段后台保活,当我们确定一定以及肯定地需要这个功能的时候, 30 | 也就只能硬着头皮去与各个手机的后台管理机制做斗争了。 31 | (一句话,产品需求呗) 32 | 33 | ## 2.故事背景 34 | 我们的需求是:开启户外运动,需要永驻后台,采集收据,使用过咕咚、悦跑圈的都知道。 35 | 但是不同的机型及厂家,让我们的应用无时无刻地在后台被杀。泪牛满面。。。 36 | 渡尽劫波兄弟在-----做IM推送的小伙伴同样有这样的情况: 37 | 38 | http://www.52im.net/forum.php?mod=viewthread&tid=429&highlight=%B1%A3%BB%EE 39 | 40 | 还有应用保活终极系列(1-3):(分析了咕咚,乐动力,悦动圈。捎带着科普了微信和QQ) 41 | 42 | http://www.52im.net/forum.php?mod=viewthread&tid=1135&highlight=%B1%A3%BB%EE 43 | 44 | 十八路神仙,各显神通。 45 | 46 | 47 | ## 3.保活总结 48 | 对于后台保活的各种手段,在网络上比比皆是,总结下来基本是如下几种: 49 | 1.开启服务,设置服务杀死重生; 50 | 2.开启服务,发送通知,设置为前台服务; 51 | 3.双进程保活; 52 | 4.检测各种系统广播启动应用; 53 | 5.息屏打开1像素点Activity;(QQ这么干的) 54 | 6.开启服务,播放无声音乐(七伤拳,定制OS出现锁屏 显示音乐播放界面,及其恶心,比如 miui); 55 | 7.优化应用内存(敲黑板,划重点); 56 | 以上这些方式在网上都可以查询到,但是因为android版本不同rom不同,不一定都能派上用场,可自行查找。 57 | 58 | 这七种方法,最优秀的无非是最后一种,但我总是不去考虑他,真是坏习惯。 59 | 60 | ## 4.保活战 61 | 62 | 在这次保活战中一共涉及了个品牌的手机: 63 | ##### 1).随意蹂躏系: 64 | Nexus5、Nexus6、Sony Z5、LG G4、LG G5、Samsung S6 S7(未升级到最新版本) 65 | ##### 2).尚有尊严系: 66 | 小米5X、魅族Note6、OPPO R11、VIVO X9柔光双摄照亮你的美(...洗脑真可怕)、Samsung J3 J5(升级到最新版本)、华为P9 P10、荣耀8(当你在后台啥都不做的时候,或者稍微动了一下) 67 | ##### 3).宁死不屈系: 68 | 华为P9 P10 mate10 v20 荣耀8(当你在后台动个不停的时候) 69 | 70 | 对于随意蹂躏系,请你一定要好好照顾它。它们提供了原生或者接近原生的后台管理机制,是因为相信每个应用都是善良的,所以,不到万不得已,不要欺负他们; 71 | 72 | 对于尚有尊严系,多为定制程度较高的第三方ROM,杀死后台也多处于其定制的功耗管理机制,尝试过很多灵性方法,很难做到一招鲜吃遍天,但这些ROM都留下了功耗管理白名单,他们需要保证自己系统地流畅运行,同时他们也考虑到了有些应用有他们不得不说的苦(交)衷(易),所以尊重ROM厂商的限制,不要作妖,有需求,打开白名单,你好,我好,大家好。 73 | 74 | 最后是宁死不屈系,这也是遇到的最大的难题,前面有提到我的应用不仅需要常驻后台,更需要在后台接收设备发出的蓝牙数据,也就是说我需要在后台搞事情。 75 | 76 | ## 5.围攻光明顶 77 | 以下的故事发生于我按照华为的显示开启了功耗管理白名单、后台清理白名单、忽略电量优化白名单。 78 | 于是号称是18个月不卡顿的华为出现了,也成功制裁了我: 79 | 首先是蓝牙广播模式,当你息屏五分钟之后,由后台发起的蓝牙扫描就被休眠了,GG; 80 | 然后是连接模式,息屏后运行一小时,凉凉; 81 | 定位和请求网络,也是被限制的不要不要; 82 | 服务重生+前台服务+双进程守护,六神装+复活甲在手,依旧被华为按在地上摩擦。 83 | 直到最后,武林中流传着这样一套拳谱,伤敌一千自损八百,名曰七伤拳:无声音乐保活大法; 84 | 也就是在服务中循环播放一段无声的音乐,cosplay正在播放的音乐播放器。 85 | 没错,确实在华为18个月不卡顿的后台管理下活了下来,但代价是飙升的功耗,以及多任务菜单提示的音乐播放icon。 86 | 但对于我这种特殊的应用来说,能够常驻后台,持续监测和记录,才是最重要的。 87 | 88 | ## 6.再续前缘 89 | At last,还是想聊一下各个rom做出的后台限制。 90 | 对于开发者来说,最欢迎的当然是原生这种随意蹂躏系,但是汝之蜜糖,彼之砒霜,这种策略如果在流氓肆虐的国内市场,估计早被啃得渣都不剩了。 91 | 所以我个人觉得在国内市场环境下,尚有尊严系的做法挺好的,有需求就手动开启,各取所需,一切由用户决定; 92 | 至于宁死不屈的华为,为了达到18个月不卡顿的效果,做出这种惨绝人寰的后台三光策略,有点不近人情,有点过分。 93 | 94 | 希望国内的应用市场流氓越来越少,Android手机越来越好用。 95 | 96 | 97 | ## 7.关于监听推送通知拉活的办法 98 | 消息推送(华为):https://blog.csdn.net/qingjiao233/article/details/79914893 99 | Android端外推送到底有多烦: 100 | https://mp.weixin.qq.com/s?__biz=MzA4NTg1MjM0Mg==&mid=2657261350&idx=1&sn=6cea730ef5a144ac243f07019fb43076%23rd 101 | 102 | 我想到市面上的推送(排名不分先后): 103 | 小米推送(MiPush) 104 | 华为推送(华为Push) 105 | 友盟推送(U-Push) 106 | 个推 107 | 极光推送 108 | 阿里云移动推送(Alibaba Cloud Channel Service) 109 | 腾讯信鸽推送 110 | 百度云推送 111 | 112 | 如果每一个都接入,是不是要疯#24 113 | 114 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.shihoo.daemonlibrary" 7 | minSdkVersion 18 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation fileTree(include: ['*.jar'], dir: 'libs') 22 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 23 | // implementation 'com.github.wangshihu123:DaemonLibrary:v0.0.3' 24 | implementation project(':daemonlibrary') 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/shihoo/daemonlibrary/ApkHelper.java: -------------------------------------------------------------------------------- 1 | package com.shihoo.daemonlibrary; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by shihoo ON 2018/12/13. 10 | * Email shihu.wang@bodyplus.cc 451082005@qq.com 11 | */ 12 | public class ApkHelper { 13 | 14 | public static String getProcessName(Context context){ 15 | int pid = android.os.Process.myPid(); 16 | ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 17 | if (am == null) { 18 | return null; 19 | } 20 | List processes = am.getRunningAppProcesses(); 21 | if (processes == null){ 22 | return null; 23 | } 24 | for (ActivityManager.RunningAppProcessInfo info : processes){ 25 | if (info.pid == pid){ 26 | return info.processName; 27 | } 28 | } 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/shihoo/daemonlibrary/App.java: -------------------------------------------------------------------------------- 1 | package com.shihoo.daemonlibrary; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.shihoo.daemon.DaemonEnv; 7 | import com.shihoo.daemon.ForegroundNotificationUtils; 8 | import com.shihoo.daemon.watch.WatchProcessPrefHelper; 9 | 10 | /** 11 | * Created by shihoo ON 2018/12/13. 12 | * Email shihu.wang@bodyplus.cc 451082005@qq.com 13 | */ 14 | public class App extends Application { 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | //需要在 Application 的 onCreate() 中调用一次 DaemonEnv.initialize() 20 | // 每一次创建进程的时候都需要对Daemon环境进行初始化,所以这里没有判断进程 21 | 22 | 23 | String processName = ApkHelper.getProcessName(this.getApplicationContext()); 24 | if ("com.shihoo.daemonlibrary".equals(processName)){ 25 | // 主进程 进行一些其他的操作 26 | Log.d("wsh-daemon", "启动主进程"); 27 | 28 | }else if ("com.shihoo.daemonlibrary:work".equals(processName)){ 29 | Log.d("wsh-daemon", "启动了工作进程"); 30 | }else if ("com.shihoo.daemonlibrary:watch".equals(processName)){ 31 | // 这里要设置下看护进程所启动的主进程信息 32 | WatchProcessPrefHelper.mWorkServiceClass = MainWorkService.class; 33 | // 设置通知栏的UI 34 | ForegroundNotificationUtils.setResId(R.drawable.ic_launcher); 35 | ForegroundNotificationUtils.setNotifyTitle("我是"); 36 | ForegroundNotificationUtils.setNotifyContent("渣渣辉"); 37 | Log.d("wsh-daemon", "启动了看门狗进程"); 38 | } 39 | 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/shihoo/daemonlibrary/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.shihoo.daemonlibrary; 2 | 3 | import android.app.Activity; 4 | import android.app.Notification; 5 | import android.app.NotificationChannel; 6 | import android.app.NotificationManager; 7 | import android.content.Context; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Color; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.view.View; 13 | 14 | import com.shihoo.daemon.DaemonEnv; 15 | import com.shihoo.daemon.IntentWrapper; 16 | import com.shihoo.daemon.watch.WatchProcessPrefHelper; 17 | 18 | public class MainActivity extends Activity { 19 | 20 | //是否 任务完成, 不再需要服务运行? 最好使用SharePreference,注意要在同一进程中访问该属性 21 | public static boolean isCanStartWorkService; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | } 28 | 29 | public void onClick(View v) { 30 | switch (v.getId()) { 31 | case R.id.btn_start: 32 | 33 | DaemonEnv.sendStartWorkBroadcast(this); 34 | v.postDelayed(new Runnable() { 35 | @Override 36 | public void run() { 37 | isCanStartWorkService = true; 38 | DaemonEnv.startServiceSafely(MainActivity.this,MainWorkService.class); 39 | } 40 | },1000); 41 | 42 | // buildNotify(this); 43 | break; 44 | case R.id.btn_white: 45 | IntentWrapper.whiteListMatters(this, "轨迹跟踪服务的持续运行"); 46 | break; 47 | case R.id.btn_stop: 48 | value ++; 49 | // buildNotify(this); 50 | DaemonEnv.sendStopWorkBroadcast(this); 51 | isCanStartWorkService = false; 52 | break; 53 | } 54 | } 55 | 56 | //防止华为机型未加入白名单时按返回键回到桌面再锁屏后几秒钟进程被杀 57 | public void onBackPressed() { 58 | IntentWrapper.onBackPressed(this); 59 | } 60 | 61 | private static final String CHANNEL_ID = "保活图腾"; 62 | private static final int CHANNEL_POSITION = 1; 63 | private int value; 64 | 65 | private void buildNotify(Context service){ 66 | NotificationManager manager = (NotificationManager)service.getSystemService(Context.NOTIFICATION_SERVICE); 67 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ 68 | NotificationChannel channel = new NotificationChannel(CHANNEL_ID,"主服务", 69 | NotificationManager.IMPORTANCE_DEFAULT); 70 | //是否绕过请勿打扰模式 71 | channel.canBypassDnd(); 72 | //闪光灯 73 | channel.enableLights(true); 74 | //锁屏显示通知 75 | channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); 76 | //闪关灯的灯光颜色 77 | channel.setLightColor(Color.RED); 78 | //桌面launcher的消息角标 79 | channel.canShowBadge(); 80 | //是否允许震动 81 | channel.enableVibration(true); 82 | //获取系统通知响铃声音的配置 83 | channel.getAudioAttributes(); 84 | //获取通知取到组 85 | channel.getGroup(); 86 | //设置可绕过 请勿打扰模式 87 | channel.setBypassDnd(true); 88 | //设置震动模式 89 | channel.setVibrationPattern(new long[]{100, 100, 200}); 90 | //是否会有灯光 91 | channel.shouldShowLights(); 92 | manager.createNotificationChannel(channel); 93 | Notification notification = new Notification.Builder(service,CHANNEL_ID) 94 | .setContentTitle("我是通知哦哦")//设置标题 95 | .setContentText("我是通知内容..."+value)//设置内容 96 | .setWhen(System.currentTimeMillis())//设置创建时间 97 | .setSmallIcon(com.shihoo.daemon.R.drawable.icon1)//设置状态栏图标 98 | .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), com.shihoo.daemon.R.drawable.icon1))//设置通知栏图标 99 | .build(); 100 | manager.notify(CHANNEL_POSITION,notification); 101 | }else { 102 | Notification notification = new Notification.Builder(service) 103 | .setContentTitle("我是通知哦哦")//设置标题 104 | .setContentText("我是通知内容..."+value)//设置内容 105 | .setWhen(System.currentTimeMillis())//设置创建时间 106 | .setSmallIcon(com.shihoo.daemon.R.drawable.icon1)//设置状态栏图标 107 | .setLargeIcon(BitmapFactory.decodeResource(service.getResources(), com.shihoo.daemon.R.drawable.icon1))//设置通知栏图标 108 | .build(); 109 | manager.notify(CHANNEL_POSITION,notification); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/shihoo/daemonlibrary/MainWorkService.java: -------------------------------------------------------------------------------- 1 | package com.shihoo.daemonlibrary; 2 | 3 | import android.content.Intent; 4 | import android.os.Handler; 5 | import android.os.IBinder; 6 | import android.os.Messenger; 7 | import android.util.Log; 8 | 9 | import com.shihoo.daemon.work.AbsWorkService; 10 | 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import io.reactivex.Observable; 14 | import io.reactivex.disposables.Disposable; 15 | import io.reactivex.functions.Action; 16 | import io.reactivex.functions.Consumer; 17 | 18 | 19 | /** 20 | * Created by shihoo ON 2018/12/13. 21 | * Email shihu.wang@bodyplus.cc 451082005@qq.com 22 | */ 23 | public class MainWorkService extends AbsWorkService { 24 | 25 | private Disposable mDisposable; 26 | private long mSaveDataStamp; 27 | 28 | /** 29 | * 是否 任务完成, 不再需要服务运行? 30 | * @return 应当停止服务, true; 应当启动服务, false; 无法判断, 什么也不做, null. 31 | */ 32 | @Override 33 | public Boolean needStartWorkService() { 34 | return MainActivity.isCanStartWorkService; 35 | } 36 | 37 | /** 38 | * 任务是否正在运行? 39 | * @return 任务正在运行, true; 任务当前不在运行, false; 无法判断, 什么也不做, null. 40 | */ 41 | @Override 42 | public Boolean isWorkRunning() { 43 | //若还没有取消订阅, 就说明任务仍在运行. 44 | return mDisposable != null && !mDisposable.isDisposed(); 45 | } 46 | 47 | @Override 48 | public IBinder onBindService(Intent intent, Void v) { 49 | // 此处必须有返回,否则绑定无回调 50 | return new Messenger(new Handler()).getBinder(); 51 | } 52 | 53 | @Override 54 | public void onServiceKilled() { 55 | saveData(); 56 | Log.d("wsh-daemon", "onServiceKilled --- 保存数据到磁盘"); 57 | } 58 | 59 | @Override 60 | public void stopWork() { 61 | //取消对任务的订阅 62 | if (mDisposable !=null && !mDisposable.isDisposed()){ 63 | mDisposable.dispose(); 64 | } 65 | saveData(); 66 | } 67 | 68 | @Override 69 | public void startWork() { 70 | Log.d("wsh-daemon", "检查磁盘中是否有上次销毁时保存的数据"); 71 | mDisposable = Observable 72 | .interval(3, TimeUnit.SECONDS) 73 | //取消任务时取消定时唤醒 74 | .doOnDispose(new Action() { 75 | @Override 76 | public void run() throws Exception { 77 | Log.d("wsh-daemon", " -- doOnDispose --- 取消订阅 .... "); 78 | saveData(); 79 | } 80 | }) 81 | .subscribe(new Consumer() { 82 | @Override 83 | public void accept(Long aLong) throws Exception { 84 | Log.d("wsh-daemon", "每 3 秒采集一次数据... count = " + aLong); 85 | if (aLong > 0 && aLong % 18 == 0){ 86 | saveData(); 87 | Log.d("wsh-daemon", " 采集数据 saveCount = " + (aLong / 18 - 1)); 88 | } 89 | } 90 | }); 91 | } 92 | 93 | 94 | private void saveData(){ 95 | long stamp = System.currentTimeMillis()/1000; 96 | if (Math.abs(mSaveDataStamp - stamp) >= 3){ 97 | // 处理业务逻辑 98 | Log.d("wsh-daemon", "保存数据到磁盘。"); 99 | } 100 | mSaveDataStamp = stamp; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShihooWang/DaemonLibrary/HEAD/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShihooWang/DaemonLibrary/HEAD/app/src/main/res/drawable/icon1.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |