版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_37077539/article/details/88404401
adb 指令相信大家都用得不少,但是自定义 adb 指令不知道大家又试过没有?最近公司有一个需求,需要自定义 adb 指令来对手机硬件进行测试,这篇博客我们就来一起聊一聊我的实现方法,希望能帮助到有相似需求的朋友。
我们先来看看常用的 adb 指令,比如启动活动和服务等:
启动活动: adb shell am start启动服务: adb shell am startservice
具体指令和参数可参考官方文档:Android Debug Bridge (adb)
上面的指令中,我们通过 am 指令调用 AMS 的功能。该是放置在 system/bin 目录上的,我们可以将其打开:
#!/system/bin/shif [ "$1" != "instrument" ] ; thencmd activity "$@"elsebase=/systemexport CLASSPATH=$base/framework/am.jarexec app_process $base/bin com.android.commands.am.Am "$@"fi
可以发现,这其实就是一个 shell 文件,在该文件中通过 shell 语言调用 cmd activity 指令的功能,从而调用 AMS 的功能。在这里我们不过多去分析 am 指令的实现原理,因为重点并不在此。至于 shell 语言,我建议大家可以去 Shell 教程 稍微学一下,它其实并不难,特别是对于有编程基础的大家来说。
那么 am 文件是如何编译进手机目录的呢?源码的位置位于 frameworks\base\cmds\am,在这里面有一个 am 文件,和上面的代码是一模一样的。除此之外我们还能发现一些 src 文件夹和其他一些文件,其中我们需要重点关注的是 Android.mk 这个文件。
# Copyright 2008 The Android Open Source Project#LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_SRC_FILES := \$(call all-java-files-under, src) \$(call all-proto-files-under, proto)LOCAL_MODULE := amLOCAL_PROTOC_OPTIMIZE_TYPE := streaminclude $(BUILD_JAVA_LIBRARY)include $(CLEAR_VARS)LOCAL_MODULE := amLOCAL_SRC_FILES := amLOCAL_MODULE_CLASS := EXECUTABLESLOCAL_MODULE_TAGS := optionalinclude $(BUILD_PREBUILT)
上面的 mk 文件主要是做了两件事,第一件事就是通过 include $(BUILD_JAVA_LIBRARY) 语句将 src 和 proto 文件夹下的文件编译成了 am.jar。第二件事情就是通过 include $(BUILD_PREBUILT) 将 am 文件拷贝到手机目录中。并且通过 LOCAL_MODULE 属性将模块名定义为 am。
在编译 Android 源码的时候,系统会从 PRODUCT_PACKAGES 属性列表中读取需要编译合入的模块,在 build/make/target/product/base.mk 文件下,有以下语句将 am 加进了 PRODUCT_PACKAGES 属性当中,因此在源码编译时,会将 am 指令编译进去。当然,在源码环境下通过 mmm frameworks/base/cmds/am/ 指令也能对模块进行编译。
PRODUCT_PACKAGES += \20-dns.conf \95-configured \org.apache.http.legacy.boot \appwidget \appops \am \android.policy \android.test.base \android.test.mock \android.test.runner \app_process \applypatch \audioserver \bit \blkid \
有了上面的知识,要自定义 adb 指令就显得容易很多了。假设我们有一个需求,需要自定义一个 adb 指令模拟按键输入,那么我们就可以写一个名为 testinput 的 shell 文件:
#!/system/bin/shinput keyevent $1
接着写对应的 Android.mk:
# Copyright 2008 The Android Open Source Project#LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := testinputLOCAL_SRC_FILES := testinputLOCAL_MODULE_CLASS := EXECUTABLESLOCAL_MODULE_TAGS := optionalinclude $(BUILD_PREBUILT)
并且将 testinput 加入 build/make/target/product/base.mk 的 PRODUCT_PACKAGES 列表当中,当编译系统源码时,就会将 testinput 编译进 system/bin 中,我们就可以通过 adb 指令去使用它了。没有源码的朋友也可以用有 root 权限的机器或者模拟器来试验,通过以下指令将 testinput 文件 push 到 system/bin 目录进行使用。
adb rootadb remountadb push <path>/testinput /system/bin
当然,上面的例子其实并没有什么实际的用处,仅仅是拐了个弯调用了 input 指令而已,为的是方便大家理解。
在实际应用中,自定义 adb 指令能够使得测试更加方便快捷,提高测试效率。我们可以通过自定义 adb 非常方便地调用上层或底层实现的功能。
比如在我司要求的音频测试中,我的实现思路是根据输入的指令启动指定的服务,并且在服务中根据传入的参数设置音频的输入输出端口和模式等。这样做的好处在于能大大缩小 adb 指令的长度和复杂度,并且对于不懂代码的测试人员比较友好,容易理解。
在使用 adb 的时候我们还有一些细节需要注意。我们都知道在 Android 8.0 及以上,启动一个没有正在运行的进程的服务需要使用 Context 的 startForegroundService 方法,并且服务启动后需要调用该服务的 startForeground 方法,否则会导致报错,这个限制在 adb 启动服务的情景中也是一样的。通过查看frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
的以下代码我们很清楚的显示了这一点:
@Overridepublic 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);.....}}......}......int runStartService(PrintWriter pw, boolean asForeground) throws RemoteException {final PrintWriter err = getErrPrintWriter();Intent intent;try {intent = makeIntent(UserHandle.USER_CURRENT);} catch (URISyntaxException e) {throw new RuntimeException(e.getMessage(), e);}if (mUserId == UserHandle.USER_ALL) {err.println("Error: Can't start activity with user 'all'");return -1;}pw.println("Starting service: " + intent);pw.flush();ComponentName cn = mInterface.startService(null, intent, intent.getType(),asForeground, SHELL_PACKAGE_NAME, mUserId);......}
查看代码的 14 行和第 19 行我们可以发现,通过 adb 启动服务的时候都是通过 runStartService 方法启动的,而这个方法根据第二个布尔值参数来决定是否是启动前台服务,而第 45 行的 mInterface.startService 方法其实最终会调用到 AMS 的 startService 方法。
因此在启动一个没有运行中进程的服务时(比如在我司的音频测试需求中启动服务),我们需要通过 adb shell startforegroundservice 或者 adb shell startfgservice 才能正常运行。
再或者,在测试过程中,测试人员需要设置标志位对测试过的机器进行标记,此时我们一般会通过调用 adb shell setprop 和 adb shell getprop 方法设置属性。但是在设置的时候我们需要注意属性的开头需要是 persist. ,例如 persist.test。如果属性的开头为 ro,则该属性只能读,不能写,也就是 setprop 会无效。而如果没有特殊的开头,比如属性名仅仅是 test,那么该属性在重启之后将不会保留。
