Friday, May 09, 2014

Run native binaries on Android: or run cross-compiled NDK application on an Android device.

What if you would like to run a program you cross compile with the NDK on your android device?

As requirements / assumptions:
  • You have a nice standalone application (e.g. a prefered GNU application) written e.g. in C or C++
  • Usualy, you run it on Linux or Cygwin. 
  • You want it on your mobile phone or tablet, 
    • BUT your phone runs Android 
    • AND it should be programmed with Java.
  • You don't want to write it anew in Java
  • You have cross compiled it with NDK
  • You don't want to root your phone or to make people root their phone to use your application. 
In this article I describe the steps that let you run your cross compiled binaries from an Android application. Since I assume you already have a cross compiled binary, I use a shell script as a binary.

  1. create an Android application for our experiment
  2. make a shell script
  3. modify the application in order to copy binaries from assets
  4. run the script
Create an Android application for our experiment using eclipse:
File>New Project...
Android> Android Application Project
Application Name: Test3
NEXT
NEXT
NEXT
NEXT
FINISH

Wait while eclipse is preparing the project... may take a bit of time...

To test it on your phone, if it is correctly configured for USB debugging...
Right click on Test3 in the Project Explorer tab in eclipse.
Run As>1. Android Application

Make a shell script:
Right click on the assets directory of your Test3 project.
New>File...
Give the File name: go.sh
Close the window that may just have opened.
Rightclick on it, open with>Test editor
Write the following in the file:
#!/system/bin/sh
echo "Hello from script"
exit 0


Close the file.

Modify the application in order to copy binaries from assets:
Open MainActivity.java
Add the following method in MainActivity class:
    /**
     * copy a file from the assets directory to the application dedicated
     * directory on the phone
     */
    private void copyAssets(String filename) {
        AssetManager assetManager = getAssets();

        InputStream src = null;
        OutputStream dst = null;
        Log.d(USER_SERVICE, "Copy from asset: " + filename);

        try {
            // get the destination path
            // i.e. path to where the application has write permissions
            String appFileDirectory = getFilesDir().getPath();
            Log.d(USER_SERVICE, "to: " + appFileDirectory);

            // open the source file
            src = assetManager.open(filename);

            // create destination file
            File outFile = new File(appFileDirectory, filename);
            dst = new FileOutputStream(outFile);

            // copy the file
            byte[] buf = new byte[4096];
            int rd_status;
            // copy until read EOF
            while ((rd_status = src.read(buf)) != -1) {
                dst.write(buf, 0, rd_status);
            }

            src.close();
            src = null;
            dst.flush();
            dst.close();
            dst = null;
        } catch (IOException e) {
            Log.e(USER_SERVICE, "ERROR while copying asset file: " + filename, e);
        }
        Log.d(USER_SERVICE, "Copy ok: " + filename);
    }


In method onCreate, call the copyAssets function:
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment()).commit();
        }
       
        copyAssets("go.sh");
    }


This will copy the script from the assets directory to the directory where the application has write permission.

run the script 
Then we want to run that script.
To do that we need a Java method that runs system binaries. Add this method in the MainActivity class:
    public Boolean execProgram(String program) {
        Log.d(USER_SERVICE, "Start Exec: " + program);
        try {
            // load the program
            Runtime rt = Runtime.getRuntime();
            Process process = rt.exec(program);
          
            // catch input and output streams of the program
            DataOutputStream os = new DataOutputStream(process.getOutputStream());
            InputStreamReader is = new InputStreamReader(process.getInputStream());
            BufferedReader reader = new BufferedReader(is);
          
            // if necessary give stdin through
            // os.writeBytes( "some input\n" );
            os.flush();

            // execute the program
            process.waitFor();
          
            // get the stdout from the program
            String output = "[";
            int nb_words = 0;
            int read;
            char[] buffer = new char[4096];
            StringBuffer progOutput = new StringBuffer();
            while ((read = reader.read(buffer)) > 0) {
                progOutput.append(buffer, 0, read);
            }
            reader.close();
            output += progOutput;
            output += "]{" + Integer.toString(nb_words) + "}";

            Log.d(USER_SERVICE, "output=" + output);
            Log.d(USER_SERVICE, "finished Exec of " + program);
        } catch (IOException e) {
            Log.e(USER_SERVICE, "IO Error while running " + program, e);
            return false;
        } catch (InterruptedException e) {
            Log.e(USER_SERVICE, "IO Error while running " + program, e);
            return false;
        }
        return true;
    }


Modify again onCreate to make go.sh executable by calling chmod and then execute go.sh:
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment()).commit();
        }
       
        copyAssets("go.sh");
        // make go.sh executable for everyone 777 or for me 744
        execProgram("/system/bin/chmod 744 " + getFilesDir().getPath() + "/go.sh");
        // execute go.sh
        execProgram(getFilesDir().getPath() + "/go.sh");

    }


Then you'll see the output on the LogCat messages.

0 Comments:

Post a Comment

<< Home