在目前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实现与命令行(控制台)类似的调用可执行文件的效果。