Android 通过bin二进制程序调用jar原理

最近在研究monkey测试,发现monkey测试的代码都是JAVA编写的,通过编译生成jar包,而我们在执行测试时直接执行/system/bin/monkey这个二进制程序的,那么它是如何能调起java程序的呢?

先来看看monkey二进程程序的生成.

java 复制代码
development\cmds\monkey\Android.mk
LOCAL_PATH:= $(call my-dir)
 
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_MODULE := monkeylib
LOCAL_MODULE_STEM := monkey
include $(BUILD_JAVA_LIBRARY)
 
################################################################
include $(CLEAR_VARS)
LOCAL_MODULE := monkey
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_SRC_FILES := monkey
LOCAL_REQUIRED_MODULES := monkeylib
include $(BUILD_PREBUILT)

Android.mk分了两部分,第一部分是编译生成monkey.jar,第二部分是把monkey这个文件编译生成monkey的二进制程序,看看monkey这个文件。

java 复制代码
development\cmds\monkey\monkey
base=/system
export CLASSPATH=$base/framework/monkey.jar
.........
exec app_process $base/bin com.android.commands.monkey.Monkey "$@"

monkey这是一个shell文件,先设置把monkey.jar添加环境变量中,然后执行app_process这个二进制程序,同时传入monkey jar的路径和类名。

app_process是在frameworks\base\cmds\路径下,直接看它的main函数

java 复制代码
frameworks\base\cmds\app_process\app_main.cpp
int main(int argc, char* const argv[])
{
    .........
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));//创建AndroidRuntime
   ..............
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {//monkey测试时,传入的是monkey jar的路径和包名,所以进入这个分支
            className.setTo(arg);//className包含com.android.commands.monkey.Monkey
            break;
        } else {
            --i;
            break;
        }
    }
 
    Vector<String8> args;
    if (!className.isEmpty()) {
        ...............
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);//com.android.commands.monkey.Monkey添加到AndroidRuntime中
        .............
    } else {
       ............
 
    }
    。。。。。。。。。。。。。
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {//className为com.android.commands.monkey.Monkey
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    }
}

app_process.cpp的main方法先解析argv参数,创建AndroidRuntime的实例,并传入类名,最后调用AndroidRuntime.start

java 复制代码
framework/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ...........
    //当前环境是否在ANDROID_ROOT、ANDROID_RUNTIME_ROOT、ANDROID_TZDATA_ROOT
    const char* rootDir = getenv("ANDROID_ROOT");
    ...............
    const char* runtimeRootDir = getenv("ANDROID_RUNTIME_ROOT");
    ..................
    const char* tzdataRootDir = getenv("ANDROID_TZDATA_ROOT");
    .............
 
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);//jni初始化
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {//启动java虚拟机
        return;
    }
    onVmCreated(env);//java虚拟机创建
 
    if (startReg(env) < 0) {//注册jni
        ALOGE("Unable to register all android natives\n");
        return;
    }
    .............................................................................
    //jnv的环境设置和类型转换
    .............................................................................
    char* slashClassName = toSlashClassName(className != NULL ? className : "");//className为com.android.internal.os.RuntimeInit
    jclass startClass = env->FindClass(slashClassName);//得到java类,startClass为com.android.internal.os.RuntimeInit
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");//通过类名获得类名里的main方法
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);//由C调用JAVA,反射调起com.android.internal.os.RuntimeInit的main方法,进入到java世界
 
        }
    }
    ...............
}

start方法里面首先判断当前环境是否在ANDROID_ROOT、ANDROID_RUNTIME_ROOT、ANDROID_TZDATA_ROOT,然后调用startVm,startVm主要是设置虚拟机的一些参数,onVmCreated是通过jni的环境检查类名是否存在,即检查com.android.internal.os.RuntimeInit是否存在,最后通过反射调起java的main方法。

我们继续看com.android.internal.os.RuntimeInit的main方法

java 复制代码
frameworks\base\core\java\com\android\internal\os\RuntimeInit.java
public static final void main(String[] argv) {
   .........
    commonInit();//做些log的打印和异常的捕获
    nativeFinishInit();//又进入C/C++世界
    ...............
}
java 复制代码
framework/base/core/jni/AndroidRuntime.cpp
static void com_android_internal_os_RuntimeInit_nativeFinishInit(JNIEnv* env, jobject clazz)
{
    gCurRuntime->onStarted();//mCurRuntime是App_main.cpp里的AppRuntime
}
java 复制代码
virtual void onStarted()
    {
       ...............
        AndroidRuntime* ar = AndroidRuntime::getRuntime();
        ar->callMain(mClassName, mClass, mArgs);
        .....................
    }
java 复制代码
framework/base/core/jni/AndroidRuntime.cpp
status_t AndroidRuntime::callMain(const String8& className, jclass clazz,
    const Vector<String8>& args)
{
    ................
    methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");//通过类名找到静态方法名为main的id
    if (methodId == NULL) {
        ALOGE("ERROR: could not find method %s.main(String[])\n", className.string());
        return UNKNOWN_ERROR;
    }
    
     //args类型转换
    const size_t numArgs = args.size();
    stringClass = env->FindClass("java/lang/String");
    strArray = env->NewObjectArray(numArgs, stringClass, NULL);
 
    for (size_t i = 0; i < numArgs; i++) {
        jstring argStr = env->NewStringUTF(args[i].string());
        env->SetObjectArrayElement(strArray, i, argStr);
    }
 
    env->CallStaticVoidMethod(clazz, methodId, strArray);//调起类名里的main静态方法,即com.android.commands.monkey.Monkey的main方法
    return NO_ERROR;
}

总结:通过命令执行monkey时,先设置monkey.jar的环境变量,然后执行app_process的程序,并传入monkey二进制程序的路径和monkey的类名,这时就进入到了app_process程序。在app_process的main方法里,创建AndroidRuntime的实例,对参数进行校验,把com.android.internal.os.RuntimeInit包名传给AndroidRuntime;在AndroidRuntime里做jni的初始化,设置虚拟机参数,注册jni,然后反射com.android.internal.os.RuntimeInit的main静态方法,最后在AndroidRuntime里通过反射调起类名里的main方法。

app_process程序还是非常不错的,利用好它,我们可以仿照monkey程序,开发出一些特殊的程序。

👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀

相关推荐
_无_妄_13 分钟前
Android 使用 WebView 直接加载 PDF 文件,通过 JS 实现
android
VomPom18 分钟前
手写一个精简版Koin:深入理解依赖注入核心原理
android
IT乐手28 分钟前
Java 编写查看调用栈信息
android
Digitally2 小时前
如何轻松永久删除 Android 手机上的短信
android·智能手机
JulyYu2 小时前
Flutter混合栈适配安卓ActivityResult
android·flutter
Warren982 小时前
Appium学习笔记
android·windows·spring boot·笔记·后端·学习·appium
Kapaseker3 小时前
Compose 文本适配天花板?BasicText 自动调大小实战
android·kotlin
海的天空16616 小时前
Flutter旧版本升级-> Android 配置、iOS配置
android·flutter·ios
程序视点15 小时前
Escrcpy 3.0投屏控制软件使用教程:无线/有线连接+虚拟显示功能等
android
东京老树根17 小时前
Android - 用Scrcpy 将手机投屏到Windows电脑上
android