【问题标题】:Hosting an executable within Android application在 Android 应用程序中托管可执行文件
【发布时间】:2011-04-07 15:20:44
【问题描述】:

我正在开发一个依赖于 ELF 二进制文件的 Android 应用程序: 我们的 Java 代码与这个二进制文件交互以完成任务。这 运行时需要在应用程序启动时启动和终止 应用程序退出/按需。

问题:

  1. 我假设我们将能够使用 Runtime.exec() API。我在哪里有什么限制吗 需要将我的库放在文件夹结构中吗?系统运行时如何定位这个可执行文件?是否有某种类路径设置?

  2. 由于应用程序依赖于这个运行时,我是 考虑将其包装在服务周围,以便可以启动或 按要求停止。处理此类可执行文件的最佳方法是什么 在 Android 项目中?

  3. 如果我没有此可执行文件的源代码,还有哪些其他选择?

请指教。

谢谢。

【问题讨论】:

    标签: java android


    【解决方案1】:

    1) 不,应该没有限制,除了那些访问系统文件并因此需要 root 的限制。最好的地方是直接到 /data/data/[your_package_name] 以避免污染其他地方。

    2) 可以在此处找到有关针对本机库进行编译的非常详尽的讨论:http://www.aton.com/android-native-libraries-for-java-applications/。另一种选择是 arm 的交叉编译器(这是用于编译内核的交叉编译器,它是免费的:http://www.codesourcery.com/sgpp/lite/arm)。如果您计划维护一个执行您的指令的服务,请注意,android 可以随时停止和重新启动服务。

    3) 现在,如果您没有源代码,我希望您的文件至少可以编译为 arm 可执行文件。如果没有,我看不出你怎么能运行它。


    您将通过在您的 java 类中运行以下命令来执行该文件:

    String myExec = "/data/data/APPNAME/FILENAME";
    Process process = Runtime.getRuntime().exec(myExec);
    DataOutputStream os = new DataOutputStream(process.getOutputStream());
    DataInputStream osRes = new DataInputStream(process.getInputStream());
    

    我对您的可执行文件一无所知,因此您可能需要也可能不需要实际获取 inputStream 和 outputStream。


    我假设运行 adb 来推送二进制文件是不可能的,所以 我一直在寻找一种巧妙的包装方式。我发现了一篇关于在您的应用程序中包含可执行文件的好帖子。在这里查看: http://gimite.net/en/index.php?Run%20native%20executable%20in%20Android%20App

    重要的部分是这个(强调我的):

    来自 Android Java 应用,使用 assets 文件夹

    • 在资产文件夹中包含二进制文件。
    • 使用getAssets().open(FILENAME) 获取InputStream
    • 将其写入/data/data/APPNAME(例如/data/data/net.gimite.nativeexe),您的应用程序有权写入文件并使其可执行。
    • 使用上面的代码运行/system/bin/chmod 744 /data/data/APPNAME/FILENAME
    • 使用上面的代码运行您的可执行文件。

    该帖子使用assets 文件夹,而不是android 建议用于静态文件的raw 文件夹:

    提示:如果您想在编译时在应用程序中保存静态文件,请将文件保存在项目 res/raw/ 目录中。您可以使用 openRawResource() 打开它,传递 R.raw。资源标识。此方法返回一个 InputStream,您可以使用它来读取文件(但您不能写入原始文件)。

    要访问数据文件夹,您可以按照此处的说明进行操作: http://developer.android.com/guide/topics/data/data-storage.html#filesInternal 另外,File#setExecutable(boolean); 方法应该可以代替 shell 命令。

    所以,把所有东西放在一起,我会尝试:

    InputStream ins = context.getResources().openRawResource (R.raw.FILENAME)
    byte[] buffer = new byte[ins.available()];
    ins.read(buffer);
    ins.close();
    FileOutputStream fos = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
    fos.write(buffer);
    fos.close();
    
    
    File file = context.getFileStreamPath (FILENAME);
    file.setExecutable(true);
    

    当然,所有这些都应该在安装后只进行一次。您可以在onCreate() 或任何检查文件是否存在的地方进行快速检查,如果文件不存在则执行所有这些命令。

    让我知道它是否有效。祝你好运!

    【讨论】:

    • @Aleadam:感谢您的回复。是的,源代码被编译为 ARM 可执行文件,我尝试从命令行在有根的 Android 手机上执行它并且它有效。我需要一种将这个可执行文件打包到我的 Android 项目中的方法,将其复制到内部文件系统(/data/data/),然后从那里执行它。
    • 酷,那我觉得你状态不错。您是否需要让该可执行文件一直运行,或者它只会执行特定的快速工作?
    • @Aleadam:我一直在尝试的与您所说的一致。但是上述方法的问题是它需要root权限。当然,SDK 不支持从 Java 代码运行可执行文件(例如,File 上的 setExecutable 返回 NoSuchMethodError)。
    • 再次感谢您提供如此详细的回答!
    • @Samuh 不客气。 chmod 如果您要更改应用数据文件夹中的文件,则不需要 root ...另一种方法是使用 FilePermission 类(如本答案所述:stackoverflow.com/questions/571783/file-permissions-in-android/…
    【解决方案2】:

    这里是关于如何打包和运行可执行文件的完整指南。我基于我在这里找到的内容和其他链接,以及我自己的反复试验。

    1.) 在您的 SDK 项目中,将可执行文件放在您的 /assets 文件夹中

    2.) 像这样以编程方式获取该文件目录 (/data/data/your_app_name/files) 的字符串

    String appFileDirectory = getFilesDir().getPath();
    String executableFilePath = appFileDirectory + "/executable_file";
    

    3.) 在应用程序的项目 Java 代码中:将可执行文件从 /assets 文件夹复制到应用程序的“文件”子文件夹(通常是 /data/data/your_app_name/files),函数如下:

    private void copyAssets(String filename) {
    
    AssetManager assetManager = getAssets();
    
    InputStream in = null;
    OutputStream out = null;
    Log.d(TAG, "Attempting to copy this file: " + filename); // + " to: " +       assetCopyDestination);
    
    try {
        in = assetManager.open(filename);
        Log.d(TAG, "outDir: " + appFileDirectory);
        File outFile = new File(appFileDirectory, filename);
        out = new FileOutputStream(outFile);
        copyFile(in, out);
        in.close();
        in = null;
        out.flush();
        out.close();
        out = null;
    } catch(IOException e) {
    Log.e(TAG, "Failed to copy asset file: " + filename, e);
    } 
    
    Log.d(TAG, "Copy success: " + filename);
    }
    

    4.) 更改 executable_file 上的文件权限以使其真正可执行。使用 Java 调用来实现:

    File execFile = new File(executableFilePath);
    execFile.setExecutable(true);
    

    5.) 像这样执行文件:

    Process process = Runtime.getRuntime().exec(executableFilePath);
    

    请注意,此处引用的任何文件(例如输入和输出文件)都必须构造完整的路径字符串。这是因为这是一个独立的衍生进程,它不知道“pwd”是什么。

    如果你想读取命令的标准输出,你可以这样做,但到目前为止,它只适用于系统命令(如“ls”),而不是可执行文件:

    BufferedReader reader = new BufferedReader(
    new InputStreamReader(process.getInputStream()));
    int read;
    char[] buffer = new char[4096];
    StringBuffer output = new StringBuffer();
    while ((read = reader.read(buffer)) > 0) {
        output.append(buffer, 0, read);
    }
    reader.close();
    process.waitFor();
    

    Log.d(TAG, "输出:" + output.toString());

    【讨论】:

    • 你使用的是哪个 copyFile() 方法?
    • IOUtils.copy(in,out) 在 commons-io 包中工作:stackoverflow.com/a/51753/134761
    • 如果有人澄清所需的参数代码会很好。你能在“executableFilePath”之后直接写一个参数吗..示例
    【解决方案3】:

    对于从 Android 10 开始执行二进制文件,只能从只读文件夹执行。这意味着您应该在您的应用程序中打包二进制文件。 Android doc

    1. android:extractNativeLibs="true"放入AndroidManifest;
    2. 把你的二进制文件放到src/main/resources/lib/*目录下,*——代表CPU架构,比如armeabi-v7a
    3. 使用这样的代码来执行:

      private fun exec(command: String, params: String): String {
      try {
          val process = ProcessBuilder()
              .directory(File(filesDir.parentFile!!, "lib"))
              .command(command, params)
              .redirectErrorStream(true)
              .start()
          val reader = BufferedReader(
              InputStreamReader(process.inputStream)
          )
          val text = reader.readText()
          reader.close()
          process.waitFor()
          return text
      } catch (e: Exception) {
          return e.message ?: "IOException"
      }
      }
      

    这里是与reddit 上的 android 团队回答的讨论。

    【讨论】:

    • 这对我不起作用。我已经完成了列出的所有步骤,还尝试在 gradle.properties 中设置 android.bundle.enableUncompressedNativeLibraries=false,但仍然在我的设备上,我最终得到了一个空的 <myapp>/lib 文件夹。
    • @mjaggard 你如何检查你的 /lib 文件夹?您是否尝试使用 ProcessBuilder 运行代码?
    • 我一直在设备上使用 ls 检查。我很确定更改代码将无济于事,除非对代码进行静态分析以找出要复制的内容,这似乎不太可能。
    • @HB 运行时的任何提取都涉及写入文件,这意味着它肯定不能在 Android 10 中执行?
    • @HB 是的,您可以编写它,但您不能执行它。 Android 10 实现en.wikipedia.org/wiki/W%5EX
    【解决方案4】:

    我使用 NDK 做过类似的事情。我的策略是使用 NDK 重新编译程序并编写一些调用程序的 main 函数的包装 JNI 代码。

    我不确定 NDK 代码的生命周期是什么样的。即使是打算长时间运行的服务也可以在方便时由系统启动和停止。您可能必须关闭 NDK 线程并在必要时重新启动它。

    【讨论】:

    • 我同意这一点——JNI 包装器是正常的前进方式。如果可能的话,我还会重新设计,使 ELF 不再是一个普通的守护进程——长时间运行的进程不利于电池寿命。
    • 我没有这个二进制文件的源代码;只是特定机器架构的可执行文件。 :-(
    猜你喜欢
    • 2020-09-15
    • 1970-01-01
    • 2016-08-15
    • 2018-05-21
    • 2012-12-11
    • 2011-04-25
    • 2015-07-22
    • 1970-01-01
    • 2016-04-13
    相关资源
    最近更新 更多