Android用命令行启动应用

2019年5月20日

Android是基于Linux内核的操作系统,用Java写的应用程序被Android运行时虚拟机运行。

因为Android是基于Linux的,而Linux执行ELF格式的可执行文件,所以用C++编写的ELF格式的可执行文件也可以在Android运行,但有些限制。首先,Android /sdcard目录不能给文件设置+x可执行位,而把文件复制到其他文件夹需要root权限。其次,把一般Linux里的ELF可执行文件复制到Android,是不能运行的,因为系统架构等不匹配。但是,从Android Open Source Project(AOSP)源代码里可以验证,Android操作系统里的许许多多的功能都是C++写的ELF格式的可执行程序,只要在AOSP里恰当地写一个C++程序,恰当地编译,是可以在Android运行的。

因为Android运行时(Android Runtime,ART)是一个Java虚拟机,所以Android可以运行Java程序。Java程序包括带有main函数的程序(我把它称为纯Java程序)和Android Studio生成的所谓的应用。

可以想到,Android运行时一定是个C++程序。要运行纯Java程序的代码类似art hello.class

在Android系统中,应用程序是由Launcher启动起来的,其实,Launcher本身也是一个应用程序,其它的应用程序安装后,就会Launcher的界面上出现一个相应的图标,点击这个图标时,Launcher就会对应的应用程序启动起来。[1]

通过学习Android OS编译流程可以知道,ART会被编译为两份,一份是host,一份是target。target指手机上运行的ART。host版本把Android预装的应用编译为ELF二进制文件(?)。既然有host ART,下文介绍的用命令行启动纯Java程序可以在电脑上运行。

运行方式为

out/host/linux-x86/bin/art --32 -cp ~/Project/Java/2/Hello.dex 
    -EnableRWProfiling:true -EnableHeapSizeProfiling:false Hello

[2]

用命令行启动纯Java程序

dalvikvm是Android 4.4以前就存在的命令,在4.4以后其内部调用art。[3]TODO:显示源代码

调用方式如下

dalvikvm -cp /sdcard/Hello.dex -EnableRWProfiling:true -EnableHeapSizeProfiling:true Hello

-EnableRWProfiling:true -EnableHeapSizeProfiling:true是我自己定义的两个art虚拟机选项。

app_process是Android 4.4加入的命令,在内部调用art。app_process的源代码在frameworks/base/cmds/app_process/app_main.cpp,文件开头有注释

/*
 * Main entry of app process.
 *
 * Starts the interpreted runtime, then starts up the application.
 *
 */

这样看来用app_process启动程序,一定会用解释方式执行,不会进行AOT静态编译,把代码编译成本地代码。我猜JIT即时编译仍然可能发生。

app_process的调用方式如下

app_process -Djava.class.path=Hello.dex -EnableRWProfiling:true -EnableHeapSizeProfiling:true /sdcard/ Hello
app_process是Android上所有Java应用进程的起源。init.zygote32.rc在系统启动阶段启动app_process,并把它命名为zygote。[4]

用命令行启动应用

用命令行启动应用需要指定应用的包名和活动名。

adb shell cmd package list packages可以列出所有包名:

...
package:com.android.managedprovisioning
package:com.android.dreams.phototable
package:com.facebook.katana
package:com.android.smspush
...

可见facebook的包名是com.facebook.katana。

注,Android 7.0以前需要使用命令pm list packages[5]

应用的AndroidManifest.xml文件写着默认活动名,就是在启动器上点击应用图标,启动的活动。但是apk是压缩文件,但直接解压缩的话,AndroidManifest.xml是乱码的。我们要用apktool解压缩apk文件。Android SDK里面没有这样的工具。

注意AndroidManifest.xml属于资源文件,运行apktool的时候不能加选项--no-res。如果apktool报错“Exception in thread “main” brut.androlib.AndrolibException: unsupported res type name for bags. Found: style2”,参见https://github.com/iBotPeaches/Apktool/issues/1719 ,可以用thejunkjon的fork

在解压后的AndroidManifest.xml里面搜索“android.intent.action.MAIN”,会找到

<activity android:name="com.facebook.katana.LoginActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>

说明com.facebook.katana.LoginActivity是默认activity。有的程序会把这个值写成”.LoginActivity“,前面的包名可以省略。[6]

知道了包名和活动名,就可以启动应用了。

am start -n com.facebook.katana/.LoginActivity

调用cat /system/bin/am,可以发现am是一个Bash脚本。

#!/system/bin/sh

if [ "$1" != "instrument" ] ; then
    cmd activity "$@"
else
    base=/system
    export CLASSPATH=$base/framework/am.jar
    exec app_process $base/bin com.android.commands.am.Am "$@"
fi

所以am其实调用app_process。这就表示如果要传入虚拟机参数,不能使用am命令。所以在Android用命令行启动应用的方法是

CLASSPATH=/system/framework/am.jar app_process -EnableRWProfiling:true -EnableHeapSizeProfiling:true  /system/bin com.android.commands.am.Am start -S -n com.facebook.katana/.LoginActivity

[7]

参考资料

  1. 罗升阳. Android应用程序启动过程源代码分析. . 2011-08-19 [2019-05-20].
  2. out/host/linux-x86/bin/art的源文件是art/tools/art。
  3. JesusFreke. How to execute the dex file in android with command?. . 2019-01-26 [2019-05-20].
  4. 罗升阳. Android系统进程Zygote启动过程的源代码分析. . 2011-09-19 [2019-06-02].
  5. Todd Kennedy. Implement shell commands for package and user services
    . . 2015-10-30 [2019-05-20].
  6. ccpat. Android应用的默认Activity配置. . 2017-01-24 [2019-05-20].
  7. 变量赋值和命令写在同一行的语法合法性在《Bash脚本解决一种问题的多种方法》有分析。