Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create JNI/NDK apk only command line without gradle, ant or cmake

I'm created one app to android that use only the JNI and NDK, without use Java code beyond what is necessary for the connection my code with the JNI, because is one application that use opengl es, it's to use one code for linux and android. But I don't understand how to create one apk file without the many things that I don't want it bite me for now. So, I try create one app without gradle, ant, android-studio... Only command line as well.

What Am I have still now?

I can build my project with ndk-build with success and it create one "obj/" directory with files .o, but and now?

What is the way?

Use aapt and after zipalign?

Are the object files all what I need to use aapt?

like image 711
PerduGames Avatar asked Dec 27 '19 18:12

PerduGames


People also ask

How do you compile ndk?

To have the ndk-build script build an application, first create a project folder. In the folder, create a folder named jni. In the jni folder, place the Android.mk file, Application.mk file, and source files. Then navigate to the project folder in a terminal and execute ndk-build.

What is ndk and JNI?

JNI is just the way that Java handles calling into native/C++ code, and calling back into Java from there. It has nothing to say about Android - it is a Java language feature. The Android NDK is a way to write Android applications using code called by JNI.

How do I know if android ndk is installed?

You'll need to point to your NDK in your eclipse by adding the path of ndk-build to Window > preferences > android > NDK. Right click on your project folder. Choose android tools -> add native support (the bottom one) and click finish. Now it will ask for a name for your .


1 Answers

I even gave Gradle a chance, but I was disturbed not to know what he was doing and decided to put him aside and do it myself to learn, here it is.

Tutorial build java and JNI/NDK APK

1. Install JDK8 and the android SDK with NDK, build-tools, platform-tools and the android platform 23.

2. Set the environment variables.

$ export SDK="${HOME}/Programs/Android" \
  export BUILD_TOOLS="${SDK}/build-tools/29.0.2" \
  export PLATFORM="${SDK}/platforms/android-23" \
  export ANDROID_API=23 \
  export APK_NAME="APKName" \
  export PACKAGE_NAME="some.some.some" \
  export ORG_DIRS="${PACKAGE_NAME//./\/}" \
  export NDK="${SDK}/ndk-bundle"
  export ANDROID_TOOLCHAIN="${NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang"

3. Create project directories and files.

Create directories tree:

$ mkdir -p  src/"${ORG_DIRS}" res/layout build/gen build/obj build/apk jni

Create ./AndroidManifest.xml file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="some.some.some"
          versionCode="1"
          versionName="0.1">
    <uses-sdk android:minSdkVersion="23"/>
    <application android:label="Hello">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

Create ./res/layout/main.xml file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/my_text"/>
</LinearLayout>

Create src/some/some/some/MainActivity.java file:

package some.some.some;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TextView text = (TextView)findViewById(R.id.my_text);
        text.setText("Hello, world!");
    }
}

4. Generate R.java file with Android Asset Packaging Tool (aapt).

$ "${BUILD_TOOLS}/aapt" package -f -m -J build/gen/ -S res \
       -M AndroidManifest.xml -I "${PLATFORM}/android.jar"

The -f flag serves to aapt to overwrite any existing output file.

The -m causes it to create package directories under the output directory.

The -J makes it generate the R.java file and sets the output directory.

The -S points out the resource directory. The -M specifies the manifest.

The -I adds the platform .jar as an "include file".

It all creates: build/gen/"${ORG_DIRS}"/R.java.

5. Compile the java files with javac.

$ javac -bootclasspath "${JAVA_HOME}/jre/lib/rt.jar" \
      -classpath "${PLATFORM}/android.jar" -d build/obj \
      build/gen/"${ORG_DIRS}"/R.java src/"${ORG_DIRS}"/MainActivity.java

If you see compile errors about JDK version, try use the -source 1.7 -target 1.7 tags in javac command.

6. Translate the .class files in build/obj/ to Dalvik byte code with dx tool.

$ "${BUILD_TOOLS}/dx" --dex --output=build/apk/classes.dex build/obj/

7. Package to create APK using the aapt tool again.

$ "${BUILD_TOOLS}/aapt" package -f -M AndroidManifest.xml -S res/ \
      -I "${PLATFORM}/android.jar" \
      -F build/"${APK_NAME}".unsigned.apk build/apk/

8. Using zipalign tool in the APK.

It serves to aligns uncompressed files in the APK on 4-byte boundaries for easier memory mapping.

$ "${BUILD_TOOLS}/zipalign" -f -p 4 \
      build/"${APK_NAME}".unsigned.apk build/"${APK_NAME}".aligned.apk

9. Create a key store and key for signing with the Java keytool.

$ keytool -genkeypair -keystore keystore.jks -alias androidkey \
      -validity 10000 -keyalg RSA -keysize 2048

10. Sign the APK with apksigner tool.

$ "${BUILD_TOOLS}/apksigner" sign --ks keystore.jks \
      --ks-key-alias androidkey --out build/"${APK_NAME}".apk \
      build/"${APK_NAME}".aligned.apk

11. Test your app with the adb tool.

$ "${SDK}/platform-tools/adb" install -r build/"${APK_NAME}".apk
$ "${SDK}/platform-tools/adb" shell am start -n "${PACKAGE_NAME}"/.MainActivity

You can use adb logcat before adb shell... to debug.

Until the step 11 you built the APK from your java code. Now let's see how to make a APK of the JNI/NDK code.

12. Change the MainActivity.java file and recompile and translate again.

Change the src/some/some/some/MainActivity.java file:

package some.some.some;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {
    static {
        System.loadLibrary("hello");
    }

    public native String getMessage();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TextView text = (TextView)findViewById(R.id.my_text);
        text.setText(getMessage());
    }
}

Recompile:

$ javac -bootclasspath "${JAVA_HOME}/jre/lib/rt.jar" \
      -classpath "${PLATFORM}/android.jar" -d build/obj \
      build/gen/"${ORG_DIRS}"/R.java src/"${ORG_DIRS}"/MainActivity.java

And Translate to Dalvik byte code again:

$ "${BUILD_TOOLS}/dx" --dex --output=build/apk/classes.dex build/obj/

13. Search the C function signature that corresponds to the Java method with javah tool.

$ javah -classpath "${PLATFORM}/android.jar:build/obj" \
      -o /tmp/jni.h "${PACKAGE_NAME}".MainActivity
$ grep -A1 _getMessage /tmp/jni.h
JNIEXPORT jstring JNICALL Java_some_some_some_MainActivity_getMessage
  (JNIEnv *, jobject);

14. Create hello.c file.

#include <stdlib.h>
#include <jni.h>
#include <time.h>

static const char *const messages[] = {
  "Hello, world!",
  "Hej världen!",
  "Bonjour, monde!",
  "Hallo Welt!"
};

JNIEXPORT jstring JNICALL
Java_net_hanshq_hello_MainActivity_getMessage(JNIEnv *env, jobject obj) {
  int i;
  srand(time(NULL));
  i = rand() % (sizeof(messages) / sizeof(messages[0]));
  return (*env)->NewStringUTF(env, messages[i]);
}

15. Create the libhello.so with arm toolchain

First, create the build/apk/lib/armeabi-v7a directory:

$ mkdir -p build/apk/lib/armeabi-v7a

Build the libhello.so:

$ ${ANDROID_TOOLCHAIN} -shared -o build/apk/lib/armeabi-v7a/libhello.so jni/hello.c

16. Package the APK again.

$ "${BUILD_TOOLS}/aapt" package -f -M AndroidManifest.xml -S res/ \
      -I "${PLATFORM}/android.jar" \
      -F build/"${APK_NAME}".unsigned.apk build/apk/

17. Zipalign the APK again.

$ "${BUILD_TOOLS}/zipalign" -f -p 4 \
      build/"${APK_NAME}".unsigned.apk build/"${APK_NAME}".aligned.apk

18. Sign the APK again.

$ "${BUILD_TOOLS}/apksigner" sign --ks keystore.jks \
      --ks-key-alias androidkey --out build/"${APK_NAME}".apk \
      build/"${APK_NAME}".aligned.apk

19. Check the APK content with aapt tool or jar tool.

with aapt:

$ "${BUILD_TOOLS}/aapt" list build/"${APK_NAME}".apk

or aapt with more details:

$ "${BUILD_TOOLS}/aapt" list -v build/"${APK_NAME}".apk

or with jar:

$ jar tf build/"${APK_NAME}"

20. Test your app with the adb tool again.

$ "${SDK}/platform-tools/adb" install -r build/"${APK_NAME}".apk
$ "${SDK}/platform-tools/adb" shell am start --activity-clear-top -n "${PACKAGE_NAME}"/.MainActivity

The --activity-clear-top is for clean the task avoiding the warning "Warning: Activity not started, its current task has been brought to the front".

This tutorial was based on: https://www.hanshq.net/command-line-android.html

like image 156
PerduGames Avatar answered Oct 25 '22 12:10

PerduGames