Native具体过程
Base on: Android T
以命令”adb shell am start -a android.intent.action.VIEW -t text/plain”为例,隐式打开可以读取text/plain类型的Activity。
am命令
以am命令为例,其位于ROM的路径为/system/bin/am,源码路径为frameworks/base/cmds/am/am。am是一个shell文件,其内容为:
#!/system/bin/sh
if [ "$1" != "instrument" ] ; then # 如果第一个参数非 instrument
cmd activity "$@" # $@ 表示am命令后的所有参数
else
base=/system
export CLASSPATH=$base/framework/am.jar
# app_process启动虚拟机执行 Am.java 类
exec app_process $base/bin com.android.commands.am.Am "$@"
fi
instrument为am命令的一个子命令,用于启动一个Instrumentation测试。除了am命令,还有其它常用的adb命令,如下。
wm命令:
#!/system/bin/sh
cmd window "$@"
pm命令:
#!/system/bin/sh
cmd package "$@"
主要看一下am命令执行流程,非instrument测试环境下执行cmd activity “$@”。”$@”表示命令后的所有参数,如果执行的命令为”adb shell am start -a android.intent.action.VIEW -t text/plain”,则”$@”内容为”start -a android.intent.action.VIEW -t text/plain”
shell脚本中首先调用了cmd命令,cmd插入的参数为activity “$@”,接下来看一下cmd的执行流程。
cmd命令
ROM中的cmd命令位于/system/bin/cmd,是一个二进制可执行文件。
源码中的cmd代码位于frameworks/native/cmds/cmd目录下。
从程序入口main.cpp开始执行:
int main(int argc, char* const argv[]) {
signal(SIGPIPE, SIG_IGN);
std::vector<std::string_view> arguments;
arguments.reserve(argc - 1);
// 0th argument is a program name, skipping.
for (int i = 1; i < argc; ++i) {
arguments.emplace_back(argv[i]);
}
return cmdMain(arguments, android::aout, android::aerr, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, RunMode::kStandalone);
}
插入的参数保存在argv[]中:
argv=[cmd, activity, start, -a, android.intent.action.VIEW, -t, text/plain]
然后将参数赋值到arguments中,最后arguments的内容为:
arguments=[activity, start, -a, android.intent.action.VIEW, -t, text/plain]
执行到cmd.cpp中的cmdMain()中,其主要方法如下:
int cmdMain(const std::vector<std::string_view> &argv, TextOutput &outputLog, TextOutput &errorLog,
int in, int out, int err, RunMode runMode) {
//...
// 获取到需要的服务名
// waitForService=0
bool waitForService = ((argc > 1) && (argv[0] == "-w"));
int serviceIdx = (waitForService) ? 1 : 0;
// cmd="activity"
const auto cmd = argv[serviceIdx];
Vector<String16> args;
String16 serviceName = String16(cmd.data(), cmd.size());
// 通过Binder获取服务对象
sp<IBinder> service;
if (waitForService) {
service = sm->waitForService(serviceName);
} else {
service = sm->checkService(serviceName);
}
// 在shellCommand()中与service通信
status_t error = IBinder::shellCommand(service, in, out, err, args, cb, result);
//...
}
然后调用到IBinder::shellCommand(),需要调用的服务引用service和参数列表args。
Native->Framework
IBinder::shellCommand()方法位于frameworks/native/libs/binder/Binder.cpp,在这里实现了与服务端的Binder通信。最终调用到Android Framework层,之后执行Java代码。
status_t IBinder::shellCommand(const sp<IBinder>& target, int in, int out, int err,
Vector<String16>& args, const sp<IShellCallback>& callback,
const sp<IResultReceiver>& resultReceiver)
{
Parcel send;
Parcel reply;
send.writeFileDescriptor(in);
send.writeFileDescriptor(out);
send.writeFileDescriptor(err);
const size_t numArgs = args.size();
send.writeInt32(numArgs);
for (size_t i = 0; i < numArgs; i++) {
send.writeString16(args[i]);
}
send.writeStrongBinder(callback != nullptr ? IInterface::asBinder(callback) : nullptr);
send.writeStrongBinder(resultReceiver != nullptr ? IInterface::asBinder(resultReceiver) : nullptr);
return target->transact(SHELL_COMMAND_TRANSACTION, send, &reply);
}
target即是要调用的服务代理,Binder通信具体过程略。
对am来说,其服务端代码位于ActivityManagerService;对wm来说,位于WindowManagerService,调用到了它们的onShellCommand()中。
接下来详细说一下Framework层的具体过程,以便可以在这里自定义shell命令。
Framework具体过程
Base on: Android 13
以命令”adb shell am start -a android.intent.action.VIEW -t text/plain”为例,隐式打开可以读取text/plain类型的Activity。
am命令在framework中首先执行AMS.onShellCommand()。
AMS.onShellCommand()
am命令在framework中首先执行AMS.onShellCommand()。
每执行一次shell命令,就new一个ActivityManagerShellCommand对象,通过该对象控制整个命令的流程。
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new ActivityManagerShellCommand(this, false)).exec(
this, in, out, err, args, callback, resultReceiver);
}
其中比较重要的参数是String[] args,它表示输入的命令参数数组。
String[] args = ["start", "-a", "android.intent.action.VIEW", "-t", "text/plain"]
这里的ActivityManagerShellCommand.exec()是有返回值的,但实际上没做什么处理。
AMSC.exec()
AMSC=ActivityManagerShellCommand
BasicShellCommandHandler源码位置:frameworks/libs/modules-utils/java/com/android/modules/utils/BasicShellCommandHandler.java
ActivityManagerShellCommand的继承关系为:
final class ActivityManagerShellCommand extends ShellCommand {...}
public abstract class ShellCommand extends BasicShellCommandHandler {...}
所以实际调用如下:
ShellCommand.exec() ->
BasicShellCommandHandler.exec()
BasicShellCommandHandler中初始化参数:
public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args) {
String cmd;
int start;
if (args != null && args.length > 0) {
cmd = args[0];
start = 1;
} else {
cmd = null;
start = 0;
}
init(target, in, out, err, args, start);
mCmd = cmd;
int res = -1;
try {
res = onCommand(mCmd);
if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
} catch (Throwable e) {
//...
}
//...
}
// 这里主要进行对象的初始化
public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, int firstArgPos) {
mTarget = target;
mIn = in;
mOut = out;
mErr = err;
mArgs = args;
mCmd = null;
mArgPos = firstArgPos;
mCurArgData = null;
mFileIn = null;
mFileOut = null;
mFileErr = null;
mOutPrintWriter = null;
mErrPrintWriter = null;
mInputStream = null;
}
大致流程:
- 调用init()初始化成员对象,将参数列表args赋给属性mArgs。
String[] mArgs = [“start”, “-a”, “android.intent.action.VIEW”, “-t”, “text/plain”]
-
获取命令mCmd,mCmd = “start”
-
调用onCommand(mCmd)
AMSC.onCommand()
AMSC=ActivityManagerShellCommand
这里的onCommand(String cmd)就是上例中的cmd = “start”,所以接下来执行runStartActivity(pw)。
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
case "start":
case "start-activity":
return runStartActivity(pw);
case "startservice":
case "start-service":
return runStartService(pw, false);
case "startforegroundservice":
case "startfgservice":
case "start-foreground-service":
case "start-fg-service":
return runStartService(pw, true);
case "stopservice":
case "stop-service":
return runStopService(pw);
case "broadcast":
return runSendBroadcast(pw);
//...
default:
return handleDefaultCommands(cmd);
}
} catch (RemoteException e) {
pw.println("Remote exception: " + e);
}
return -1;
}
注:pw是一个PrintWriter对象,通过getOutPrintWriter()获取输出流对象,该对象实际上表示输出的终端对象。
AMSC.runStartActivity()
这里主要进行以下几步:
- 在这里通过参数列表构造intent
Intent intent;
try {
intent = makeIntent(UserHandle.USER_CURRENT);
} catch (URISyntaxException e) {
throw new RuntimeException(e.getMessage(), e);
}
- 进一步处理intent
- 通过intent打开Activity并返回结果
if (mWaitOption) {
result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
mimeType, null, null, 0, mStartFlags, profilerInfo,
options != null ? options.toBundle() : null, mUserId);
res = result.result;
} else {
res = mInternal.startActivityAsUserWithFeature(null, SHELL_PACKAGE_NAME, null,
intent, mimeType, null, null, 0, mStartFlags, profilerInfo,
options != null ? options.toBundle() : null, mUserId);
}
- 通过res作一些提示,输出到终端
AMSC.makeIntent()
private Intent makeIntent(int defUser) throws URISyntaxException {
mStartFlags = 0;
mWaitOption = false;
mStopOption = false;
mRepeat = 0;
mProfileFile = null;
mSamplingInterval = 0;
mAutoStop = false;
mStreaming = false;
mUserId = defUser;
mDisplayId = INVALID_DISPLAY;
mWindowingMode = WINDOWING_MODE_UNDEFINED;
mActivityType = ACTIVITY_TYPE_UNDEFINED;
mTaskId = INVALID_TASK_ID;
mIsTaskOverlay = false;
mIsLockTask = false;
mAsync = false;
mBroadcastOptions = null;
return Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {...}
这里主要调用Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler),该方法中除了Intent类自己需要处理的参数外,还可以自定义CommandOptionHandler,解释自己的参数列表。
Intent.parseCommandArgs()
public static Intent parseCommandArgs(ShellCommand cmd, CommandOptionHandler optionHandler)
throws URISyntaxException {
Intent intent = new Intent();
Intent baseIntent = intent;
boolean hasIntentInfo = false;
Uri data = null;
String type = null;
String opt;
while ((opt=cmd.getNextOption()) != null) {
switch (opt) {
case "-a":
intent.setAction(cmd.getNextArgRequired());
if (intent == baseIntent) {
hasIntentInfo = true;
}
break;
case "-d":
data = Uri.parse(cmd.getNextArgRequired());
if (intent == baseIntent) {
hasIntentInfo = true;
}
break;
case "-t":
type = cmd.getNextArgRequired();
if (intent == baseIntent) {
hasIntentInfo = true;
}
break;
//...
default:
if (optionHandler != null && optionHandler.handleOption(opt, cmd)) {
// Okay, caller handled this option.
} else {
throw new IllegalArgumentException("Unknown option: " + opt);
}
break;
}
}
intent.setDataAndType(data, type);
//...
return intent;
}
自定义shell命令
需求说明
Resolver端在请求Provider时,执行过程为:
(1) Resolver端通过authority请求Provider引用;
(2) system_server处理请求并返回Provider引用;
(3) Resolver端获取引用后调用具体方法如query()等,访问Provider。
需要在Resolver端监控指定的Provider(根据authority属性区分)并打印相关信息,主要在ActivityThread.acquireProvider()中执行关键过程,
需要在system_server监控指定的Provider(根据authority属性区分)并打印相关信息,主要在ContentProviderHelper.getContentProviderImpl()中执行关键过程。
定义变量
在ActivityThread中需要实现的功能如下,其中通过shell控制的变量有两个,即AUTH和isPrintStack。
public static String AUTH = null;
public static boolean isPrintStack = false;
// 监听auth为ActivityThread.AUTH的Provider的情况
// 如果ActivityThread.AUTH=all,则监控所有
public void printAuth(String curAuth) {
if (ActivityThread.AUTH == null) {
return;
}
if ("all".equals(ActivityThread.AUTH) ||
curAuth.equals(ActivityThread.AUTH)) {
String TestTAG = "AMS Provider Test";
Slog.w(TestTAG, "CallerPackage" + "{" +
"Uid=" + Process.myUid() + "," +
"Pid=" + Process.myPid() + "} " +
"start execute acquireProvider() for " + ActivityThread.AUTH);
if (isPrintStack) {
new Exception(TestTAG).printStackTrace();
}
}
}
命令格式
我的shell命令格式如下:
adb shell am monitor-auth --auth com.demoapp.mydemo.provider --uid all --stack true --end
monitor-auth:即命令标识
--auth:后面接具体的authority,all表示所有authority都打印
--uid:Resolver端的uid,all表示所有客户端的请求都会被打印
--stack:标识是否需要print堆栈,yes或者no
--end:结束标识,调用函数处理
# 打印系统中所有进程的所有authority日志
adb shell am monitor-auth --auth all --uid all --stack no --end
# 打印uid=10340的Resolver端请求com.demoapp.provider时的日志,并打印请求端堆栈
adb shell am monitor-auth --auth com.demoapp.provider --uid 10340 --stack yes --end
# 关闭日志打印
adb shell am monitor-auth --auth null --uid null --stack no --end
自定义命令代码
在AMSC.onCommand(String cmd)中通过不同命令执行不同流程,所以在switch中添加:
// AMS Test Log
case "monitor-auth":
runMonitorAuth();
return 0;
然后在AMSC中自定义处理流程,即runMonitorAuth()函数,如下:
// AMS Test Log
private void runMonitorAuth() throws RemoteException {
String opt;
String authority = null;
int uid = 0;
boolean isPrintStack = false;
// getNextOption()获取类似-a的参数
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--auth": //获取auth
String auth = getNextArg(); // 获取--auth后面的参数值
if (auth == null || auth.equals("null")) {
authority = null;
} else {
authority = auth;
}
break;
case "--uid":
String resUid = getNextArg();
if ("all".equals(resUid)) {
uid = -1;
} else {
uid = Integer.parseInt(resUid);
}
break;
case "--stack": //是否打印堆栈
String resStack = getNextArg();
isPrintStack = resStack.equals("yes");
break;
case "--end":
mInterface.setMonitorAuth(authority, uid, isPrintStack);
break;
default:
break;
}
}
}
通过mInterface.setMonitorAuth()调用到AMS,需要在IActivityManager.aidl中添加该函数。
在AMS中添加代码并调用到APP端:
public void setMonitorAuth(String authority, int uid, boolean isPrintStack) {
// 设置系统进程的AUTH
ActivityThread.AUTH = authority;
// 设置指定Uid进程的AUTH
final int numOfNames = getProcessNamesLOSP().getMap().size();
for (int ip = 0; ip < numOfNames; ip++) {
//获取某个进程名下的所有进程
SparseArray<ProcessRecord> procs = getProcessNamesLOSP().getMap().valueAt(ip);
if (procs == null) {
return;
}
for (int ia = 0, size = procs.size(); ia < size; ia++) {
ProcessRecord app = procs.valueAt(ia);
IApplicationThread thread;
// 比较uid,如果uid=-1则对所有正在运行的进程执行操作
if ((uid == -1 || app.uid == uid) && (thread = app.getThread()) != null) {
try {
thread.setMonitorAuth(authority, isPrintStack);
} catch (RemoteException e) {
// If the other end already died, then our work here is done.
}
}
}
}
}
通过IApplicationThread调用到APP端,所以还需要在IApplicationThread.aidl找那个添加接口。
最后在ActivityThread.ApplicationThread类中给APP端赋值:
public void setMonitorAuth(String authority, boolean isPrint) {
AUTH = authority;
isPrintStack = isPrint;
}
最后源码中重新编译framework和services模块即可。
make services -j8 # 编译services
make framework-minus-apex -j8 # Android 11(R) 版本开始用此命令
然后push到指定目录即可验证效果。
其它
ADB命令格式
执行到framework后,adb命令可看作由cmd、option、args三部分组成,一条指令只有一个cmd,但option和args可以有多组。
如:adb shell am start -a android.intent.action.VIEW -t text/plain
其中adb shell表示进入手机环境,这里不讨论这个。am标识处理这条命令的相应service,”am”表示ActivityManagerService。进入到实际处理类后,实际执行的是”start -a android.intent.action.VIEW -t text/plain”,
在各个ShellCommand类中如ActivityManagerShellCommand,这条指令被存储为一个数组String[] mArgs = [“start”, “-a”, “android.intent.action.VIEW”, “-t”, “text/plain”]。
(1) am:即是上述说的cmd
(2) -a和-t:就是option,意为选项
(3) option后面跟的就是实际参数
其中option可以用”-“和”–“表示,具体如下:
| 格式 | 说明 |
|---|---|
| ”-“+单字符 | 常见格式。如-a value,后面跟具体参数值 |
| ”–“+多字符 | 常见格式。如–key value,后面也跟具体参数值 |
| ”–“+单字符 | 同”–“+多字符 |
| ”-“+多字符 | 较少用。如-avalue,-a后面的表示参数值,没有空格 |
处理命令的函数
在ActivityManagerShellCommand中,通过String getNextOption()每次读取一个option。
/**
* Return the next option on the command line -- that is an argument that
* starts with '-'. If the next argument is not an option, null is returned.
*/
public String getNextOption() {
if (mCurArgData != null) {
String prev = mArgs[mArgPos - 1];
throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
}
if (mArgPos >= mArgs.length) {
return null;
}
String arg = mArgs[mArgPos];
if (!arg.startsWith("-")) {
return null;
}
mArgPos++;
if (arg.equals("--")) {
return null;
}
if (arg.length() > 1 && arg.charAt(1) != '-') {
if (arg.length() > 2) { //这里处理-avalue类型的参数
mCurArgData = arg.substring(2); //返回值value
return arg.substring(0, 2); //返回option即-a
} else {
mCurArgData = null;
return arg;
}
}
mCurArgData = null;
return arg; //返回形如-a或--key的option
}
使用getNextArgRequired()或getNextArg()获取参数值。getNextArgRequired()多了个判空流程并输出异常。实际上只要调用就会从mArgs读取一个值,无论是option还是args。
/**
* Return the next argument on the command line, whatever it is; if there are
* no arguments left, throws an IllegalArgumentException to report this to the user.
*/
public String getNextArgRequired() {
String arg = getNextArg();
if (arg == null) {
String prev = mArgs[mArgPos - 1];
throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
}
return arg;
}
/**
* Return the next argument on the command line, whatever it is; if there are
* no arguments left, return null.
*/
public String getNextArg() {
if (mCurArgData != null) {
String arg = mCurArgData;
mCurArgData = null;
return arg;
} else if (mArgPos < mArgs.length) {
return mArgs[mArgPos++];
} else {
return null;
}
}