Android最强保活黑科技的最强技术实现,身为一个安卓程序员Context都没弄明白
Posted m0_66145060
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android最强保活黑科技的最强技术实现,身为一个安卓程序员Context都没弄明白相关的知识,希望对你有一定的参考价值。
Parcel parcel;
parcel.setData(data, data_size);
传输Parcel数据
// 获取ServiceManager
sp sm = defaultServiceManager();
// 获取ActivityManager binder
sp binder = sm->getService(String16(“activity”));
// 传输parcel
int result = binder.get()->transact(code, parcel, NULL, 0);
方式二 使用 ioctl 与 binder 驱动通信
方式一让我尝到了一点甜头,实现了大佬的思路,不禁让鄙人浮想联翩,感慨万千,鄙人的造诣已经如此之深,不久就会人在美国,刚下飞机,迎娶白富美,走向人生巅峰矣…
[图片上传中…(image-237bb2-1586154434812-4)]
咳咳。不禁想到ioctl的方式我也可以尝试着实现一下。ioctl是一个linux标准方法,那么我们就直奔主题看看,binder是什么,ioctl怎么跟binder driver通信。
Binder介绍
Binder是android系统提供的一种IPC机制。每个Android的进程,都可以有一块用户空间和内核空间。用户空间在不同进程间不能共享,内核空间可以共享。Binder就是一个利用可以共享的内核空间,完成高性能的进程间通信的方案。
Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及binder驱动,其中ServiceManager用于管理系统中的各种服务。如图:
可以看到,注册服务、获取服务、使用服务,都是需要经过binder通信的。
- Server通过注册服务的Binder通信把自己托管到ServiceManager
- Client端可以通过ServiceManager获取到Server
- Client端获取到Server后就可以使用Server的接口了
Binder通信的代表类是BpBinder(客户端)和BBinder(服务端)。
ps:有关binder的详细知识,大家可以查看Gityuan大佬的Binder系列文章。
ioctl函数
ioctl(input/output control)是一个专用于设备输入输出操作的系统调用,它诞生在这样一个背景下:
操作一个设备的IO的传统做法,是在设备驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,后面就跟着控制命令(socket编程中常常这样做)。但是这样做的话,会导致代码分工不明,程序结构混乱。所以就有了ioctl函数,专门向驱动层发送或接收指令。
Linux操作系统分为了两层,用户层和内核层。我们的普通应用程序处于用户层,系统底层程序,比如网络栈、设备驱动程序,处于内核层。为了保证安全,操作系统要阻止用户态的程序直接访问内核资源。一个Ioctl接口是一个独立的系统调用,通过它用户空间可以跟设备驱动沟通了。函数原型:
int ioctl(int fd, int request, …);
作用:通过IOCTL函数实现指令的传递
- fd 是用户程序打开设备时使用open函数返回的文件描述符
- request是用户程序对设备的控制命令
- 后面的省略号是一些补充参数,和cmd的意义相关
应用程序在调用ioctl
进行设备控制时,最后会调用到设备注册struct file_operations
结构体对象时的unlocked_ioctl
或者compat_ioctl
两个钩子上,例如Binder驱动的这两个钩子是挂到了binder_ioctl方法上:
static const struct file_operations binder_fops =
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
;
它的实现如下:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
/根据不同的命令,调用不同的处理函数进行处理/
switch (cmd)
case BINDER_WRITE_READ:
/读写命令,数据传输,binder IPC通信的核心逻辑/
ret = binder_ioctl_write_read(filp, cmd, arg, thread);
break;
case BINDER_SET_MAX_THREADS:
/设置最大线程数,直接将值设置到proc结构的max_threads域中。/
break;
case BINDER_SET_CONTEXT_MGR:
/设置Context manager,即将自己设置为ServiceManager,详见3.3/
break;
case BINDER_THREAD_EXIT:
/binder线程退出命令,释放相关资源/
break;
case BINDER_VERSION:
/获取binder驱动版本号,在kernel4.4版本中,32位该值为7,64位版本该值为8/
break;
return ret;
具体内核层的实现,我们就不关心了。到这里我们了解到,Binder在Android系统中会有一个设备节点,调用ioctl控制这个节点时,实际上会调用到内核态的binder_ioctl方法。
为了利用ioctl启动Android Service,必然是需要用ioctl向binder驱动写数据,而这个控制命令就是BINDER_WRITE_READ
。binder驱动层的一些细节我们在这里就不关心了。那么在什么地方会用ioctl 向binder写数据呢?
IPCThreadState.talkWithDriver
阅读Gityuan的Binder系列6—获取服务(getService)一节,在binder模块下IPCThreadState.cpp中有这样的实现(源码目录:frameworks/native/libs/binder/IPCThreadState.cpp):
status_t IPCThreadState::talkWithDriver(bool doReceive)
…
binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();
status_t err;
do
//通过ioctl不停的读写操作,跟Binder Driver进行通信
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
…
while
(err == -EINTR); //当被中断,则继续执行
…
return err;
可以看到ioctl跟binder driver交互很简单,一个参数是mProcess->mDriverFD,一个参数是BINDER_WRITE_READ,另一个参数是binder_write_read结构体,很幸运的是,NDK中提供了linux/android/binder.h
这个头文件,里面就有binder_write_read这个结构体,以及BINDER_WRITE_READ常量的定义。
[惊不惊喜意不意外]
#include<linux/android/binder.h>
struct binder_write_read
binder_size_t write_size;
binder_size_t write_consumed;
binder_uintptr_t write_buffer;
binder_size_t read_size;
binder_size_t read_consumed;
binder_uintptr_t read_buffer;
;
#define BINDER_WRITE_READ _IOWR(‘b’, 1, struct binder_write_read)
这意味着,这些结构体和宏定义很可能是版本兼容的。
那我们只需要到时候把数据揌到binder_write_read结构体里面,就可以进行ioctl系统调用了!
/dev/binder
再来看看mProcess->mDriverFD是什么东西。mProcess也就是ProcessState.cpp(源码目录:frameworks/native/libs/binder/ProcessState.cpp):
-
ProcessState::ProcessState(const char *driver)
-
mDriverName(String8(driver))
, mDriverFD(open_driver(driver))
, …
从ProcessState的构造函数中得知,mDriverFD由open_driver方法初始化。
static int open_driver(const char *driver)
int fd = open(driver, O_RDWR | O_CLOEXEC);
if (fd >= 0)
int vers = 0;
status_t result = ioctl(fd, BINDER_VERSION, &vers);
return fd;
ProcessState在哪里实例化呢?
sp ProcessState::self()
if (gProcess != nullptr)
return gProcess;
gProcess = new ProcessState(kDefaultDriver);
return gProcess;
可以看到,ProcessState的gProcess是一个全局单例对象,这意味着,在当前进程中,open_driver只会执行一次,得到的 mDriverFD 会一直被使用。
const char* kDefaultDriver = “/dev/binder”;
而open函数操作的这个设备节点就是/dev/binder。
纳尼?在应用层直接操作设备节点?Gityuan大佬不会骗我吧?一般来说,Android系统在集成SELinux的安全机制之后,普通应用甚至是系统应用,都不能直接操作一些设备节点,除非有SELinux规则,给应用所属的域或者角色赋予了那样的权限。
看看文件权限:
➜ ~ adb shell
chiron:/ $ ls -l /dev/binder
crw-rw-rw- 1 root root 10, 49 1972-07-03 18:46 /dev/binder
可以看到,/dev/binder设备对所有用户可读可写。
再看看,SELinux权限:
chiron:/ $ ls -Z /dev/binder
u:object_r:binder_device:s0 /dev/binder
查看源码中对binder_device角色的SELinux规则描述:
allow domain binder_device:chr_file rw_file_perms;
也就是所有domain对binder的字符设备有读写权限,而普通应用属于domain。
既然这样,肝它!
写个Demo试一下
验证一下上面的想法,看看ioctl给binder driver发数据好不好使。
1、打开设备
int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
if (fd < 0)
LOGE(“Opening ‘%s’ failed: %s\\n”, “/dev/binder”, strerror(errno));
else
LOGD(“Opening ‘%s’ success %d: %s\\n”, “/dev/binder”, fd, strerror(errno));
2、ioctl
Parcel *parcel = new Parcel;
parcel->writeString16(String16(“test”));
binder_write_read bwr;
bwr.write_size = parcel->dataSize();
bwr.write_buffer = (binder_uintptr_t) parcel->data();
int ret = ioctl(fd, BINDER_WRITE_READ, bwr);
LOGD(“ioctl result is %d: %s\\n”, ret, strerror(errno));
3、查看日志
D/KeepAlive: Opening ‘/dev/binder’ success, fd is 35
D/KeepAlive: ioctl result is -1: Invalid argument
打开设备节点成功了,耶✌️!但是ioctl失败了🤔,失败原因是Invalid argument
,也就是说可以通信,但是Parcel数据有问题。来看看数据应该是什么样的。
binder_write_read结构体数据封装
IPCThreadState.talkWithDriver方法中,bwr.write_buffer指针指向了mOut.data(),显然mOut是一个Parcel对象。
binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();
再来看看什么时候会向mOut中写数据:
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
binder_transaction_data tr;
tr.data.ptr.buffer = data.ipcData();
…
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
writeTransactionData方法中,会往mOut中写入一个binder_transaction_data结构体数据,binder_transaction_data结构体中又包含了作为参数传进来的data Parcel对象。
writeTransactionData方法会被transact方法调用:
status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
status_t err = data.errorCheck(); // 数据错误检查
flags |= TF_ACCEPT_FDS;
if (err == NO_ERROR)
// 传输数据
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
…
// 默认情况下,都是采用非oneway的方式, 也就是需要等待服务端的返回结果
if ((flags & TF_ONE_WAY) == 0)
if (reply)
//等待回应事件
err = waitForResponse(reply);
else
Parcel fakeReply;
err = waitForResponse(&fakeReply);
else
err = waitForResponse(NULL, NULL);
return err;
IPCThreadState是跟binder driver真正进行交互的类。每个线程都有一个IPCThreadState
,每个IPCThreadState
中都有一个mIn、一个mOut。成员变量mProcess保存了ProcessState变量(每个进程只有一个)。
接着看一下一次Binder调用的时序图:
Binder介绍一节中说过,BpBinder是Binder Client,上层想进行进程间Binder通信时,会调用到BpBinder的transact方法,进而调用到IPCThreadState的transact方法。来看看BpBinder的transact方法的定义:
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
if (mAlive)
status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
return DEAD_OBJECT;
BpBinder::transact方法的code/data/reply/flags这几个参数都是调用的地方传过来的,现在唯一不知道的就是mHandle是什么东西。mHandle是BpBinder(也就是Binder Client)的一个int类型的局部变量(句柄),只要拿到了这个handle就相当于拿到了BpBinder。
ioctl启动Service分几步?
下面是在依赖libbinder.so时,启动Service的步骤:
// 获取ServiceManager
sp sm = defaultServiceManager();
// 获取ActivityManager binder
sp binder = sm->getService(String16(“activity”));
// 传输parcel
int result = binder.get()->transact(code, parcel, NULL, 0);
1、获取到IServiceManager Binder Client;
2、从ServiceManager中获取到ActivityManager Binder Client;
3、调用ActivityManager binder的transact方法传输Service的Parcel数据。
通过ioctl启动Service也应该是类似的步骤:
1、获取到ServiceManager的mHandle句柄;
2、进行binder调用获取到ActivityManager的mHandle句柄;
3、进行binder调用传输启动Service的指令数据。
这里有几个问题:
1、不依赖libbinder.so时,ndk中没有Parcel类的定义,parcel数据哪里来,怎么封装?
2、如何获取到BpBinder的mHandle句柄?
如何封装Parcel数据
Parcel类是Binder进程间通信的一个基础的、必不可少的数据结构,往Parcel中写入的数据实际上是写入到了一块内部分配的内存上,最后把这个内存地址封装到binder_write_read结构体中。Parcel作为一个基础的数据结构,和Binder相关类是可以解耦的,可以直接拿过来使用,我们可以根据需要对有耦合性的一些方法进行裁剪。
c++ Parcel类路径:frameworks/native/libs/binder/Parcel.cpp
jni Parcel类路径:frameworks/base/core/jni/android_os_Parcel.cpp
如何获取到BpBinder的mHandle句柄
具体流程参考Binder系列4—获取ServiceManager。
1、获取ServiceManager的mHandle句柄
defaultServiceManager()方法用来获取gDefaultServiceManager
对象,gDefaultServiceManager是ServiceManager的单例。
sp defaultServiceManager()
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
while (gDefaultServiceManager == NULL)
gDefaultServiceManager = interface_cast(
ProcessState::self()->getContextObject(NULL));
return gDefaultServiceManager;
getContextObject方法用来获取BpServiceManager对象(BpBinder),查看其定义:
sp ProcessState::getContextObject(const sp& /caller/)
sp context = getStrongProxyForHandle(0);
return context;
可以发现,getStrongProxyForHandle是一个根据handle获取IBinder对象的方法,而这里handle的值为0,可以得知,ServiceManager的mHandle恒为0。
2、获取ActivityManager的mHandle句柄
获取ActivityManager的c++方法是:
sp binder = serviceManager->getService(String16(“activity”));
BpServiceManager.getService:
virtual sp getService(const String16& name) const
sp svc = checkService(name);
if (svc != NULL) return svc;
return NULL;
BpServiceManager.checkService:
virtual sp checkService( const String16& name) const
Parcel data, reply;
//写入RPC头
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
//写入服务名
data.writeString16(name);
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
return reply.readStrongBinder();
可以看到,CHECK_SERVICE_TRANSACTION这个binder调用是有返回值的,返回值会写到reply中,通过reply.readStrongBinder()方法,即可从reply这个Parcel对象中读取到ActivityManager的IBinder。每个Binder对象必须要有它自己的mHandle句柄,不然,transact操作是没办法进行的。所以,很有可能,Binder的mHandle的值是写到reply这个Parcel里面的。
看看reply.readStrongBinder()方法搞了什么鬼:
sp Parcel::readStrongBinder() const
sp val;
readNullableStrongBinder(&val);
return val;
status_t Parcel::readNullableStrongBinder(sp* val) const
return unflattenBinder(val);
调用到了Parcel::unflattenBinder方法,顾名思义,函数最终想要得到的是一个Binder对象,而Parcel中存放的是二进制的数据,unflattenBinder很可能是把Parcel中的一个结构体数据给转成Binder对象。
看看Parcel::unflattenBinder方法的定义:
status_t Parcel::unflattenBinder(sp* out) const
const flat_binder_object* flat = readObject(false);
if (flat)
…
sp binder =
ProcessState::self()->getStrongProxyForHandle(flat->handle);
return BAD_TYPE;
果然如此,从Parcel中可以得到一个flat_binder_object结构体,这个结构体重有一个handle变量,这个变量就是BpBinder中的mHandle句柄。
因此,在不依赖libbinder.so的情况下,我们可以自己组装数据发送给ServiceManager,进而获取到ActivityManager的mHandle句柄。
IPCThreadState是一个被Binder依赖的类,它是可以从源码中抽离出来为我们所用的。上一节中说到,Parcel类也是可以从源码中抽离出来的。
通过如下的操作,我们就可以实现ioctl获取到ActivityManager对应的Parcel对象reply:
Parcel data, reply;
// data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
// IServiceManager::getInterfaceDescriptor()的值是android.app.IActivityManager
data.writeInterfaceToken(String16(“android.app.IActivityManager”));
data.writeString16(String16(“activity”));
IPCThreadState::self()->transact(
0/ServiceManger的mHandle句柄恒为0/,
CHECK_SERVICE_TRANSACTION, data, reply, 0);
reply变量也就是我们想要的包含了flat_binder_object结构体的Parcel对象,再经过如下的操作就可以得到ActivityManager的mHandle句柄:
const flat_binder_object* flat = reply->readObject(false);
return flat->handle;
3、传输启动指定Service的Parcel数据
上一步已经拿到ActivityManger的mHandle句柄,比如值为1。这一步的过程和上一步类似,自己封装Parcel,然后调用IPCThreadState::transact方法传输数据,伪代码如下:
Parcel data;
// 把Service相关信息写到parcel中
writeService(data, packageName, serviceName, sdk_version);
IPCThreadState::self()->transact(
1/上一步获取的ActivityManger的mHandle句柄值是1/,
CHECK_SERVICE_TRANSACTION, data, reply,
1/TF_ONE_WAY/);
4、writeService方法需要做什么事情?
下面这段代码是Java中封装Parcel对象的方法:
Intent intent = new Intent();
ComponentName component = new ComponentName(context.getPackageName(), serviceName);
rvice的Parcel数据**
上一步已经拿到ActivityManger的mHandle句柄,比如值为1。这一步的过程和上一步类似,自己封装Parcel,然后调用IPCThreadState::transact方法传输数据,伪代码如下:
Parcel data;
// 把Service相关信息写到parcel中
writeService(data, packageName, serviceName, sdk_version);
IPCThreadState::self()->transact(
1/上一步获取的ActivityManger的mHandle句柄值是1/,
CHECK_SERVICE_TRANSACTION, data, reply,
1/TF_ONE_WAY/);
4、writeService方法需要做什么事情?
下面这段代码是Java中封装Parcel对象的方法:
Intent intent = new Intent();
ComponentName component = new ComponentName(context.getPackageName(), serviceName);
盘点im即时通讯开发中Android后台保活方案
参考技术A 对于IM应用和消息推送服务的开发者来说,在Android机型上的后台保活是个相当头疼的问题。老板一句:“为什么微信、QQ能收到消息,而你写的APP却不行?”,直接让人崩溃,话说老板你这APP要是整成微信、APP那么牛,直接进手机厂商白名单,还要程序员在这瞎忙活?
好了,抱怨归抱怨,活还得干,不然靠谁养活广大苦逼的程序员?
正因为Android系统版本的差异,也导致了各种保活黑科技的运行效果大相径庭,所以本文正好借此机会,盘点一下当前主流(截止2019年前)的保活黑科技在市面上各版本Android手机上的运行效果,希望能给大家提供一些客观的参考。
其实Android端APP搞保活的目的倒不是为了干什么见不得人的坏事(但不排除动机不纯的开发者),主要是像IM即时通讯应用和资讯类应用等需要搞后台消息推送、运动类应用需要在后台实时监测用户的运动数据等,因为现在越来越多的手机厂商为了省电策略考虑,基本上如果你的应用没有被加入白名单,一旦处于后台就会被系统限制甚至干掉,但使用APP的用户才不听你这些解释——反正“我”就要你的APP能如期正常运行,开发者也是不得已而为之。
以消息推送为例,当APP处于后台或关闭时,消息推送对于某些应用来说非常有用,比如:
1)IM即时通讯聊天应用:聊天消息通知、音视频聊天呼叫等,典型代表有:微信、QQ、易信、米聊、钉钉、Whatsup、Line;
2)新闻资讯应用:最新资讯通知等,典型代表有:网易新闻客户端、腾讯新闻客户端;
3)SNS社交应用:转发/关注/赞等通知,典型代表有:微博、知乎;
4)邮箱客户端:新邮件通知等,典型代表有:QQ邮箱客户端、Foxmail客户端、网易邮箱大师;
5)金融支付应用:收款通知、转账通知等,典型代表有:支付宝、各大银行的手机银行等;
.... ....
在上述的各种应用中,尤其对于用户接触最多、最平常的IM聊天应用或新闻资讯来说,保活和消息推送简直事关APP的“生死”,消息推送这种能力已经被越来越多的APP作为基础能力之一,因为移动互联网时代下,用户的“全时在线”能力非常诱人和强大,能随时随地即时地将各种重要信息推送给用户,无疑是非常有意义的。
题外话:实际上,对于后台消息推送能力,Android原版系统早就内置了系统级推送服务(跟iOS上的APNs服务是一个东西),它就是GCM服务(现在升级为FCM了),但众所周之的原因,谷哥的服务在国内都是用不了的(你懂的)——无奈啊!
主要黑科技方案有:
1)监听广播:监听全局的静态广播,比如时间更新的广播、开机广播、解锁屏、网络状态、解锁加锁亮屏暗屏(3.1版本),高版本需要应用开机后运行一次才能监听这些系统广播,目前此方案失效。可以更换思路,做APP启动后的保活(监听广播启动保活的前台服务);
2)定时器、JobScheduler:假如应用被系统杀死,那么定时器则失效,此方案失效。JobService在5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权);
3)双进程(NDK方式Fork子进程)、双Service守护:高版本已失效,5.0起系统回收策略改成进程组。双Service方案也改成了应用被杀,任何后台Service无法正常状态运行;
4)提高Service优先级:只能一定程度上缓解Service被立马回收。 即时通讯聊天软件app开发可以咨询蔚可云。
针对上述方案,具体的实现思路,通常是这样的:
1)进程拉活:AIDL方式单进程、双进程方式保活Service(最极端的例子就是推送厂商的互相唤醒复活:极光、友盟、以及各大厂商的推送,同派系APP广播互相唤醒:比如今日头条系、阿里系);
2)降低oom_adj的值:常驻通知栏(可通过启动另外一个服务关闭Notification,不对oom_adj值有影响)、使用”1像素“的Activity覆盖在getWindow()的view上(据传某不可言说的IM大厂用过这个方案,虽然他们从未正面承认过)、循环播放无声音频(黑科技,7.0下杀不掉);
3)监听锁屏广播:使Activity始终保持前台;
4)使用自定义锁屏界面:覆盖了系统锁屏界面;
5)创建子进程:通过android:process属性来为Service创建一个进程;
6)白名单:跳转到系统白名单界面让用户自己添加app进入白名单。
使用AIDL绑定方式新建2个Service优先级(防止服务同时被系统杀死)不一样的守护进程互相拉起对方,并在每一个守护进程的ServiceConnection的绑定回调里判断保活Service是否需要重新拉起和对守护线程进行重新绑定。
后台播放音乐这种保活方法,亲身经历过:
记得当时用的是某运动记步APP,它为了保活就是这么干的。之所以被我发现,是因为在我的Android手机上,每次打开这个APP居然总能莫名其妙听到若有若无的环境噪音样的声音,尤其安静的场所下更明显。我个人估计这个APP里用的保活音频文件,很可能就是程序员在简陋的条件下随手自已录制的,虽然也是不得以为之,但做法确实是有点粗糙。
总结一下,以上方案在当前主流手机上的运行效果
【1】双进程守护方案(基于onStartCommand() return START_STICKY):
1)原生5.0、5.1:原生任务栏滑动清理app,Service会被杀掉,然后被拉起,接着一直存活;
2)金立F100(5.1):一键清理直接杀掉整个app,包括双守护进程。不手动清理情况下,经测试能锁屏存活至少40分钟;
3)华为畅享5x(6.0):一键清理直接杀掉整个app,包括双守护进程。不手动清理下,锁屏只存活10s。结论:双进程守护方案失效;
4)美图m8s(7.1.1):一键清理直接杀掉整个app,包括双守护进程。不清理情况下,锁屏会有被杀过程(9分钟左右被杀),之后重新复活,之后不断被干掉然后又重新复活。结论:双守护进程可在后台不断拉起Service;
5)原生7.0:任务栏清除APP后,Service存活。使用此方案后Service照样存活;
6)LG V30+(7.1.2):不加双进程守护的时候,一键清理无法杀掉服务。加了此方案之后也不能杀掉服务,锁屏存活(测试观察大于50分钟);
7)小米8(8.1):一键清理直接干掉app并且包括双守护进程。不清理情况下,不加守护进程方案与加守护进程方案Service会一直存活,12分钟左右closed。结论:此方案没有起作用。
▲ 结论:除了华为此方案无效以及未更改底层的厂商不起作用外(START_STICKY字段就可以保持Service不被杀)。此方案可以与其他方案混合使用。
【2】监听锁屏广播打开1像素Activity(基于onStartCommand() return START_STICKY):
1)原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用);
2)华为畅享5x(6.0):锁屏只存活4s。结论:方案失效;
3)美图m8s(7.1.1):同原生5.0;
4)原生7.0:同美图m8s;
5)LG V30+(7.1.2):锁屏后情况跟不加情况一致,服务一致保持运行,结论:此方案不起作用;
6)小米8(8.1):关屏过2s之后app全部被干掉。结论:此方案没有起作用。
▲ 结论:此方案无效果。
【3】故意在后台播放无声的音乐(基于onStartCommand() return START_STICKY):
1)原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用);
2)华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务,锁屏8分钟后依然存活。结论:此方案适用;
3)美图m8s(7.1.1):同5.0;
4)原生7.0:任务管理器中关闭APP后服务被干掉,大概过3s会重新复活(同仅START_STICKY字段模式)。结论:看不出此方案有没有其作用;
5)LG V30+(7.1.2):使用此方案前后效果一致。结论:此方案不起作用;
6)小米8(8.1):一键清理可以杀掉服务。锁屏后保活超过20分钟。
▲ 结论:成功对华为手机保活。小米8下也成功突破20分钟。
【4】使用JobScheduler唤醒Service(基于onStartCommand() return START_STICKY):
1)原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。结论:此方案起作用;
2)华为畅享5x(6.0):一键清理直接杀掉APP,过12s左右会自动重启服务,JobScheduler起作用;
3)美图m8s(7.1.1):一键清理直接杀掉APP,无法自动重启;
4)原生7.0:同美图m8s(7.1.1);
5)小米8(8.1):同美图m8s(7.1.1)。
▲ 结论:只对5.0,5.1、6.0起作用。
【5】混合使用的效果,并且在通知栏弹出通知:
1)原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。锁屏超过11分钟存活;
2)华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务。结论:方案适用;
3)美图m8s(7.1.1):一键清理APP会被杀掉。正常情况下锁屏后服务依然存活;
4)原生7.0:任务管理器中关闭APP后服务被干掉,过2s会重新复活;
5)小米8(8.1):一键清理可以杀掉服务,锁屏下后台保活时间超过38分钟;
6)荣耀10(8.0):一键清理杀掉服务,锁屏下后台保活时间超过23分钟。
以上是关于Android最强保活黑科技的最强技术实现,身为一个安卓程序员Context都没弄明白的主要内容,如果未能解决你的问题,请参考以下文章
2023年Android黑科技保活方案,应用永生,拒绝强制杀死 最高适配Android 13 小米 华为 Oppo vivo 等最新机型 拒绝强杀 开机自启动 附demo apk 附研究资料