本文最后更新于 535 天前,其中的信息可能已经有所发展或是发生改变。
在Android中,APP自身无法获得root权限(uid变成0)。
我尝试过在内核态修改APP的uid,但是这样做会导致APP进程卡死。
所以,需要使用root权限进行一些复杂操作时,最好的方法就是启动一个具备root权限的守护进程,由守护进程来操作。而这样启动的守护进程无法绘制UI,虽然有一些手段可以绘制,但是存在很多问题,比如触摸穿透。
所以,通行的方法是,由APP启动守护进程(下文称为Core),然后和守护进程之间建立某种连接,通过进程间通信来指挥Core行驶权限,数据在Core中分析完毕后,交由APP来呈现给用户。
进程间通信的手段有很多,共享内存,Unix Domain Socket,管道等等等等,但是这些方法都不太方便,需要自己手动维护连接,并且编码将变成C/S结构,代码中将大量使用回调,变得很不直观。
而libsu可以方便的自动完成以上操作,并且得益于Android的AIDL机制,可以像工作在同一个进程中一样编码。
libsu是由Magisk的主要维护者John Wu开发的一款开源库,旨在简化root开发流程。引入libsu库非常简单,只需要在项目设置中引入如下仓库
maven("https://jitpack.io")
然后在需要使用的模块中
dependencies {
val libsuVersion = "5.3.0"
// The core module that provides APIs to a shell
implementation("com.github.topjohnwu.libsu:core:$libsuVersion")
// Optional: APIs for creating root services. Depends on ":core"
implementation("com.github.topjohnwu.libsu:service:$libsuVersion")
// Optional: Provides remote file system support
implementation("com.github.topjohnwu.libsu:nio:$libsuVersion")
}
libsu本质上依然是在进程间进行通信,你必须先掌握AIDL的使用方法,本文不做赘述。
首先,定义如下AIDL
package ling.core;
interface IRootProcess {
int getUid();
}
然后需要先build一下项目,才会生成它的代理类。
你需要在运行配置中,启用 Always install with package manager 选项,否则更改可能不会生效。
实现IRootProcess,这将成为APP调用的接口。
public class RootProcess extends IRootProcess.Stub {
@Override
public int getUid() throws RemoteException {
//此处已经在root进程中
return Process.myUid();
}
}
然后,定义一个Service,继承自RootService。
RootService相较Android的Service有很多改动,不要将它用在其他地方!
public class RootService extends com.topjohnwu.superuser.ipc.RootService {
static {
//由于将要在APP中请求启动此服务,
//所以APP中的类加载器也会载入它
//仅在root进程中加载需要的库
if (Process.myUid() == 0) {
System.loadLibrary("CoreTask");
}
}
@Override
public IBinder onBind(@NonNull Intent intent) {
//此处已经在Root进程中!
//这是刚刚IRootProcess的实现类
//此对象将作为接口交由APP调用
return new RootProcess();
}
}
在连接Core之前,需要一个类来获取结果:
public class RootConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//连接成功,root就是刚刚返回的RootProcess的代理类
//像平常一样调用其中的方法,实际执行将在Core中完成。
IRootProcess root = IRootProcess.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//连接断开
}
}
然后,在一个合适的位置启动这一切:
RootService.bind(new Intent(this, RootService.class), new RootConnection());
大功告成!