在android app中调用elf可执行文件 并实现输入输出、外部传参等功能

在目前android ndk的开发中,主流的调用方式是so库的编译与调用,但有时需要与可执行文件进行通信,例如与一个c语言的游戏引擎通信,以下方式可能会对你有帮助。

参考内容

在android12中通过apk调用可执行文件 - 知乎 (zhihu.com)

测试环境:AS中的Pixel 6 API 30 虚拟机

效果图

首先编写并编译一个可执行文件

在cmakelist同一个文件夹下创建一个call.c文件,在cmakelist中写以下内容

cmake_minimum_required(VERSION 3.18.1)

project("callelf")

add_library(
        call 

        SHARED

        call.c)

add_executable(call call.c)

若你使用android.mk请写以下内容

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := call
LOCAL_SRC_FILES := call.c
include $(BUILD_EXECUTABLE)

点击上方工具栏的Build--Refresh Linked C++ Projects实现动态库的链接,此后你的c文件中import各种库,在call.c中写以下代码

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#define MAX_LENGTH 100

int main(int argc, char *argv[]){

    char input[MAX_LENGTH];
    printf("进入elf\n");
    fflush(stdout);

    //模拟外部传参
    int opt;
    char *arg1 = NULL;
    char *arg2 = NULL;
    
    while ((opt = getopt(argc,argv,"a:b:"))!=-1){
        switch (opt) {
            case 'a':
                arg1 = optarg;
                break;
            case 'b':
                arg2 = optarg;
                break;
            case '?':
                if (optopt == 'a' || optopt == 'b'){
                    fprintf(stderr,"Option -%c 还需要一个参数",optopt);
                } else{
                    fprintf(stderr,"未知参数 -%c.\n",optopt);
                }
                return 1;
            default:
                abort();
        }
    }

    for (;;){
        while (fgets(input, MAX_LENGTH, stdin)){
            fprintf(stderr, "IN: %s", input);

            if(strcmp(input,"exit\n")==0){
                printf("\n程序已退出\n");
                break;
            }

            fprintf(stdout,"输出:%s",input);
            fflush(stdout);

        }

        return 0;
    }

}

上述代码的功能是不断循环监听stdin的内容,并将输入的内容通过stdout输出。
编译可执行文件,选择你的目标平台,在app级别的build.gradle中设置cmakelist/android.mk的路径,在android的大括号下加上以下代码

externalNativeBuild {
        ndkBuild {
            //以你的实际路径为准
            path "src/main/jni/Android.mk"
            //path "src/main/jni/CmakeLists.txt"
        }
    }

点击上方工具栏的Build--Make Project进行编译,编译后的可执行文件在app级别的moudle下的build--intermediates--cxx--Debug--3m486d2g(这是随机的,以你的实际为准)--obj下找到,可执行文件的名字应该是call,没有任何后缀,将obj下的所有文件夹复制到assets文件夹中。

通过assetsmanager读取可执行文件并拷贝到手机内存中,记得申请读写权限

 appFileDirectory = "/data/data/"+context.getPackageName();
 executableFilePath = appFileDirectory + "/executable";//目标路径
 cpuType = Build.CPU_ABI;
 assetManager = context.getAssets();

 public void copyAssets(String filename) throws Exception {

        if (!createFolder()){
            throw new Exception("文件夹不存在");
        }
        InputStream in;
        OutputStream out;
        try {
            in = assetManager.open(cpuType+"/"+filename);
            File outfile = new File(executableFilePath, filename);
            out = Files.newOutputStream(outfile.toPath());
            copyFile(in, out);
            new ProcessBuilder("chmod", "777",outfile.getAbsolutePath()).start().waitFor();//修改可执行文件的权限以便于后续的调用
            in.close();
            out.flush();
            out.close();
        } catch(IOException e) {
            Log.e(TAG, "failed to copy asset file: " + filename, e);
        }
        Log.d(TAG, "copy success: " + filename);
    }

拷贝完成后调起该可执行程序

try {
        String arg1 = "arg1";
        String arg2 = "arg2";
        String args = " -a"+arg1+" -b"+arg2;
        String command = "/system/bin/linker"+ce.getExecutableFilePath() + "/"+processName+args;//或使用"/system/bin/linker64"
            process = Runtime.getRuntime().exec(command);
            Log.i(TAG, "进程已经被调起");
       
} catch (IOException e) {
        throw new RuntimeException(e);
}

在oncreate中创建两个线程分别用于监听stdin与stdout


Thread stdinThread = new Thread(() -> {
            DataOutputStream stdin = new DataOutputStream(p.getOutputStream());
            //这里通过btn与edittext向stdin中写入数据,控件初始化部分省略了
            button.setOnClickListener(v -> {
                String input = editText.getText().toString();
                try {
                    stdin.writeBytes(input + "\n");
                    stdin.flush();
                    runOnUiThread(() -> editText.setText(""));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

        });

        Thread stdoutThread = new Thread(() -> {

            BufferedReader stdout = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line;
            try {
                while ((line = stdout.readLine()) != null) {
                    String finalLine = line;
                    //读取stdout并显示于textview上,控件初始化省略了
                    runOnUiThread(() -> textView.setText(finalLine));
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        });

        stdinThread.start();
        stdoutThread.start();

这样你就可以通过edittext实现与命令行(控制台)类似的调用可执行文件的效果。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值