2019-06-27
关键字:Android reboot
1、前言
今天这篇文章啊,来源于笔者手里的一个项目需求。
话说笔者手里有一块运行着 Android 4.4 的开发板,这其中就有一个需求:当满足某种条件的时候就在后台悄悄摸地给系统来一个重启操作。诶,这就有意思了啊,由于是悄悄摸地重启,那当然不能给用户发现我们触发重启的方式,不然人家找个稍微懂点 Android 的就能给你把这个重启机制给破了。
那怎么办呢?其实,我们只需要把几种常见的重启机制的流程给搞清楚了,这个需求就很好实现了。
2、一般怎么做?
在 Android 上,想重启系统,一般可以有 3 种方式:
1、通过发送系统广播
2、通过调用 PowerManager 的 reboot 方法
3、通过执行 reboot 串口命令
这三种方式虽然各自走的路线不一样,但最终都是殊途同归,会调用到同一个汇编程序以实现重启的功能的。第 1 种和第 2 种都是发生在 framework 层,第 3 种则是发生在 C 可执行程序上。
3、我们要如何达到目的?
我们的根本目的是要实现系统的重启动作,由于在 Android 系统上实际上只有一种重启方式,所以该执行的重启流程还是绕不开的。
我们的次要目的是实现一种不可被拦截的重启操作,那何谓不可拦截呢?一个自然是除了我们自己,谁也无法改动我们的程序(如删除、替换)。另一个,则是他人可以删除和替换我们的重启机制程序,但是所要付出的代价过高。我们一定要明确,这里所谓的 “不可拦截” 并不一定就是说我们的程序是不可撼动的,其实只要我们能做到让对方 “伤敌一千,自损八百” 我们也可以说是做到了这种 “不可拦截” 的目的了。
那,前面铺垫了这么多,到底该如何来实现呢?
答案很简单:通过设置系统属性 "sys.powerctl" 的值为 "reboot" 来实现。
为什么呢?
其实在上面第 2 节中讲到的三种传统重启系统方式,它们最终都会去做这种设置 "sys.powerctl" 属性的值为 "reboot" 的事情。但上面三种方式就是做不到我们需要的 “不可拦截” 性,因为这三种方式太容易被替换了。别人只需要简单自定义一个 framework.jar 或者是 reboot C 程序,让传统的重启流程置空,就能拦截掉我们的重启请求了。所以它们不行。
而至于直接设置 "sys.powerctl",我们去看看这条属性在 Android 中是如何来响应的,即这条属性的处理流程。
首先在 init.rc 中,我们可以发现有这样一条属性监听语句
即只要 "sys.powerctl" 属性的值发生了改变,那么就会去执行这个 powerctl 程序,并将属性的值作为参数传递过去。
那这个 powerctl 程序又在哪呢?
它在这
.\system\core\init\builtins.c
在这个代码中有一个 do_powerctl 函数,它专门用来处理上面的命令。
int do_powerctl(int nargs, char **args)
而在这个 do_powerctl 函数里,我们可以发现它会去调用另外一个函数 android_reboot。这个函数在这
.\system\core\libcutils\android_reboot.c
而在这个 android_reboot 函数里呢,我们又可以发现它又去调用了另外一个 reboot 函数
reboot(RB_AUTOBOOT)
上面这个函数呢其实就是定义在一段汇编程序里的函数了
.\bionic\libc\arch-arm\syscalls\__reboot.S
到这,就真正去重启系统了。
那有的同学就会说了,这只是知道了它的调用流程而已,并不能说明它的 “不可拦截” 性啊!
说的没错!现在,我们再到回去看这个 builtins.c 程序,不过这次我们来看看它的 Android.mk 的内容。
.\system\core\init\Android.mk
通过它的 Android.mk 我们可以发现这个 builtins.c 程序最终是会被编译成 init C程序的,就是我们开发板根目录下的那个 init 程序
关于这个程序,熟悉 Android 系统开发的同学可能就知道了,它就是属于那种不可替换不可修改的程序。唯一一个替换它的方式就是重新烧写 system 分区。而让他人自己弄一个适配于我们的硬件平台的 system 分区出来,那代价可就大了~
到这,我们明白了,在前面 init.rc 中看到的那个调用 powrctl 命令的方式是一种很靠谱的方式,它就是具有 “不可替换”、“不可删除” 性的。
那接下来,我们再来看看这个 builtins.c 重启流程的下游程序,即 android_reboot.c 程序的 Android.mk 文件的内容
.\system\core\libcutils\Android.mk
在这里,我们又意外地发现这个程序是会被编译成静态库的~ 静态库的特性是什么?是不生成外部库文件,所有要引用到该静态库的程序都会将该库中的程序打包进自己的程序中。
说白了,就是这个 android_reboot.c 中的代码,会被完全嵌到 init 程序中去。再说白一点就是,你想换 android_reboot.c 那就相当于要换 init 程序。而换 init 程序又已经在前面被我们证明了是不可行的了。因此,我们又知道了这个 android_reboot.c 也具有 “不可替换”、“不可删除” 的特性。
再往下游,就是汇编程序了。就不说了~
4、结论
因此,我们如果想实现一种不可被拦截的重启系统的方式,最最简单的一种方式就是通过设置系统属性 "sys.powerctl" 的值为 "reboot" 就好了。
在 Java 上可以像这样做
import android.os.SystemProperties
void myReboot(){
SystemProperties.set("sys.powerctl", "reboot");
}
在 C 语言上可以像这样做
#include <cutils/properties.h>
property_set("sys.powerctl", "reboot");
我们的开发板,由于有很多的自定义硬件,这些外围硬件的适配工作不是那么好做的。如果有其他人想要将他们自己的 Android 系统烧进去,那这些外围硬件都是不能正常工作的,这样一来,他们即使绕开了我们的重启机制,也无法正常使用我们的板子。除非他们有足够的耐心完全自己适配外围硬件。让他人感觉到破解代价太大也是一种 “防止破解” 的有效方式。