0

I'm trying to compile an Android application with AAPT, javac, and DX commands only, so without Android Studio, Gradle, and CMake. For context, I'm also using Java 8, Arch Linux, and compiling for SDK version 28. It should also be noted I am also using the NDK (version 21.1.6352462) to compile Raylib in the example, although it's highly unlikely to be related to the problem.

With this setup, I can compile and build an APK without external JAR files (any extra dependencies not in android.jar).

If I try to build with external JAR files, I am still able to produce an APK. However, upon running the application, this error is produced, with logcat output shown here:

02-12 00:48:13.878  3657  3657 D AndroidRuntime: Shutting down VM
02-12 00:48:13.881  3657  3657 E AndroidRuntime: FATAL EXCEPTION: main
02-12 00:48:13.881  3657  3657 E AndroidRuntime: Process: com.raylib.game, PID: 3657
02-12 00:48:13.881  3657  3657 E AndroidRuntime: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/appcompat/R$drawable;
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at androidx.appcompat.widget.AppCompatDrawableManager$1.<init>(AppCompatDrawableManager.java:63)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at androidx.appcompat.widget.AppCompatDrawableManager.preload(AppCompatDrawableManager.java:57)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at androidx.appcompat.app.AppCompatDelegateImpl.<init>(AppCompatDelegateImpl.java:336)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at androidx.appcompat.app.AppCompatDelegateImpl.<init>(AppCompatDelegateImpl.java:286)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at androidx.appcompat.app.AppCompatDelegate.create(AppCompatDelegate.java:230)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at androidx.appcompat.app.AppCompatActivity.getDelegate(AppCompatActivity.java:554)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at androidx.appcompat.app.AppCompatActivity.attachBaseContext(AppCompatActivity.java:107)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.Activity.attach(Activity.java:7893)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3384)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3596)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2067)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.os.Handler.dispatchMessage(Handler.java:106)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.os.Looper.loop(Looper.java:223)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at android.app.ActivityThread.main(ActivityThread.java:7666)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at java.lang.reflect.Method.invoke(Native Method)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
02-12 00:48:13.881  3657  3657 E AndroidRuntime: Caused by: java.lang.ClassNotFoundException: androidx.appcompat.R$drawable
02-12 00:48:13.881  3657  3657 E AndroidRuntime:    ... 20 more

This is the build shell file I use (using *.jar or specifiying the path doesn't work for me when compiling NativeLoader.java, so I had to specify each file):

#!/bin/sh
# ______________________________________________________________________________
#
#  Compile raylib project for Android
# ______________________________________________________________________________
#
# NOTE: If you excluded any ABIs in the previous steps, remove them from this list too

# TODO: arm64-v8a building doesn't work, ARM64 devices can still run the 32 bit version:
#       /usr/bin/ld: /tmp/main-08f12a.o: Relocations in generic ELF (EM: 183)
#       /usr/bin/ld: /tmp/main-08f12a.o: Relocations in generic ELF (EM: 183)
#       /usr/bin/ld: /tmp/main-08f12a.o: error adding symbols: file in wrong format
ABIS="armeabi-v7a"

BUILD_TOOLS=${ANDROID_SDK_ROOT}/build-tools/30.0.2
TOOLCHAIN=android/ndk/toolchains/llvm/prebuilt/linux-x86_64
NATIVE_APP_GLUE=android/ndk/sources/android/native_app_glue

FLAGS="-ffunction-sections -funwind-tables -fstack-protector-strong -fPIC -Wall \
    -Wformat -Werror=format-security -no-canonical-prefixes \
    -DANDROID -DANDROID_STL=c++_shared -DPLATFORM_ANDROID -D__ANDROID_API__=28 -DWITH_ITT=OFF"

INCLUDES="-I. -Iinclude -I../include -I$NATIVE_APP_GLUE -I$TOOLCHAIN/sysroot/usr/include"

# Copy icons
cp assets/icon_ldpi.png android/build/res/drawable-ldpi/icon.png
cp assets/icon_mdpi.png android/build/res/drawable-mdpi/icon.png
cp assets/icon_hdpi.png android/build/res/drawable-hdpi/icon.png
cp assets/icon_xhdpi.png android/build/res/drawable-xhdpi/icon.png

# Copy other assets
cp assets/* android/build/assets

# Clean
rm game.apk

# ______________________________________________________________________________
#
#  Compile
# ______________________________________________________________________________
#
for ABI in $ABIS; do
    case "$ABI" in
        "armeabi-v7a")
            CCTYPE="armv7a-linux-androideabi"
            ABI_FLAGS="-std=c++11 -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16"
            ;;

        "arm64-v8a")
            CCTYPE="aarch64-linux-android"
            ABI_FLAGS="-std=c++11 -target aarch64 -mfix-cortex-a53-835769"
            ;;

        "x86")
            CCTYPE="i686-linux-android"
            ABI_FLAGS=""
            ;;

        "x86_64")
            CCTYPE="x86_64-linux-android"
            ABI_FLAGS=""
            ;;
    esac
    CC="$TOOLCHAIN/bin/${CCTYPE}28-clang++"

    # Compile native app glue
    # .cpp -> .o
    $CC -c $NATIVE_APP_GLUE/android_native_app_glue.c -o $NATIVE_APP_GLUE/native_app_glue.o \
        $INCLUDES -I$TOOLCHAIN/sysroot/usr/include/$CCTYPE $FLAGS $ABI_FLAGS

    # .o -> .a
    $TOOLCHAIN/bin/llvm-ar rcs lib/$ABI/libnative_app_glue.a $NATIVE_APP_GLUE/native_app_glue.o

    # Compile project
    $CC src/*.cpp -o android/build/lib/$ABI/libmain.so -shared \
        $INCLUDES -I$TOOLCHAIN/sysroot/usr/include/$CCTYPE $FLAGS $ABI_FLAGS \
        -Wl,-soname,libmain.so -Wl,--exclude-libs,libatomic.a -Wl,--build-id \
        -Wl,--no-undefined -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now \
        -Wl,--warn-shared-textrel -Wl,--fatal-warnings -u ANativeActivity_onCreate \
        -L. -Landroid/build/obj -Llib/$ABI \
        -lc++ -lraylib -lnative_app_glue -llog -landroid -lEGL -lGLESv2 -lOpenSLES -latomic -lc -lm -ldl
done

# ______________________________________________________________________________
#
#  Build APK
# ______________________________________________________________________________
#
$BUILD_TOOLS/aapt package -f -m \
    -S android/build/res -J android/build/src -M android/build/AndroidManifest.xml \
    -I android/sdk/platforms/android-28/android.jar

# Compile NativeLoader.java
javac -verbose -source 1.8 -target 1.8 -d android/build/obj \
    -bootclasspath jre/lib/rt.jar \
    -classpath "android/sdk/platforms/android-28/android.jar:runtime/core-common-2.1.0.jar:runtime/activity-1.2.4.jar:runtime/annotation-1.2.0.jar:runtime/annotation-experimental-1.1.0.jar:runtime/annotations-13.0.jar:runtime/appcompat-1.2.0.jar:runtime/appcompat-resources-1.2.0.jar:runtime/auto-value-annotations-1.6.3.jar:runtime/camera-camera2-1.0.0-alpha05.jar:runtime/camera-core-1.0.0-alpha05.jar:runtime/collection-1.1.0.jar:runtime/concurrent-futures-1.0.0.jar:runtime/constraintlayout-2.0.4.jar:runtime/constraintlayout-solver-2.0.4.jar:runtime/core-1.6.0.jar:runtime/core-runtime-2.1.0.jar:runtime/cursoradapter-1.0.0.jar:runtime/customview-1.1.0.jar:runtime/drawerlayout-1.1.0.jar:runtime/exifinterface-1.3.2.jar:runtime/fragment-1.3.6.jar:runtime/interpolator-1.0.0.jar:runtime/lifecycle-common-2.3.1.jar:runtime/lifecycle-livedata-2.1.0.jar:runtime/lifecycle-livedata-core-2.3.1.jar:runtime/lifecycle-runtime-2.3.1.jar:runtime/lifecycle-viewmodel-2.3.1.jar:runtime/lifecycle-viewmodel-savedstate-2.3.1.jar:runtime/listenablefuture-1.0.jar:runtime/loader-1.0.0.jar:runtime/savedstate-1.1.0.jar:runtime/tracing-1.0.0.jar:runtime/vectordrawable-1.1.0.jar:runtime/vectordrawable-animated-1.1.0.jar:runtime/versionedparcelable-1.1.1.jar:runtime/viewbinding-7.2.2.jar:runtime/viewpager-1.0.0.jar:android/build/obj" \
    -sourcepath src android/build/src/com/raylib/game/R.java \
    android/build/src/com/raylib/game/NativeLoader.java

$BUILD_TOOLS/dx --verbose --dex --min-sdk-version=26 --output=android/build/dex/classes.dex runtime/*.jar android/build/obj

# Add resources and assets to APK
$BUILD_TOOLS/aapt package -f \
    -M android/build/AndroidManifest.xml -S android/build/res -A assets \
    -I android/sdk/platforms/android-28/android.jar \
    -F game.apk android/build/dex

cd android/build
for ABI in $ABIS; do
    $BUILD_TOOLS/aapt add ../game.apk lib/$ABI/libmain.so
    $BUILD_TOOLS/aapt add ../game.apk lib/$ABI/libc++_shared.so
done
cd ../..

# Sign and zipalign APK
# NOTE: If you changed the storepass and keypass in the setup process, change them here too
jarsigner -keystore android/raylib.keystore -storepass raylib -keypass raylib \
    -signedjar game.apk game.apk projectKey

$BUILD_TOOLS/zipalign -f 4 game.apk game.final.apk
mv -f game.final.apk game.apk

# Install to device or emulator
android/sdk/platform-tools/adb install -r game.apk

This is the NativeLoader.java file I use as the main activity:

package com.raylib.game;
import com.raylib.game.R;

import androidx.activity.ComponentActivity;
import androidx.appcompat.app.*;
import androidx.core.app.*;
import androidx.camera.core.*;
import androidx.camera.core.PreviewConfig;
import androidx.fragment.app.*;
import androidx.lifecycle.LifecycleOwner;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.os.Bundle;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;

import java.nio.ByteBuffer;

public class NativeLoader extends AppCompatActivity
{
    private TextureView textureView;

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

        textureView = findViewById(R.id.textureView);

        textureView.post((Runnable)(new Runnable()
        {
            public final void run()
            {
                startCamera();
            }
        }));

        textureView.addOnLayoutChangeListener(new View.OnLayoutChangeListener()
        {
            @Override
            public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)
            {
                updateTransform();
            }
        });
    }

    private void startCamera()
    {
        PreviewConfig.Builder previewConfig = new PreviewConfig.Builder();

        Preview preview = new Preview(previewConfig.build());

        preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener()
        {
            @Override
            public void onUpdated(Preview.PreviewOutput output)
            {
                ViewGroup parent = (ViewGroup) textureView.getParent();
                parent.removeView(textureView);
                parent.addView(textureView, 0);

                textureView.setSurfaceTexture(output.getSurfaceTexture());
                updateTransform();
            }
        });

        ImageCaptureConfig.Builder imageCaptureConfig = new ImageCaptureConfig.Builder();

        final ImageCapture imageCapture = new ImageCapture(imageCaptureConfig.build());

        ImageButton button = findViewById(R.id.imageButton);
        button.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                imageCapture.takePicture(new ImageCapture.OnImageCapturedListener()
                {
                    @Override
                    public void onCaptureSuccess(ImageProxy image, int rotationDegrees)
                    {
                        ImageView imageView = findViewById(R.id.imageView);
                        imageView.setImageBitmap(imageProxyToBitmap(image));
                        imageView.setRotation(rotationDegrees);

                        image.close();
                    }
                });
            }
        });

        CameraX.bindToLifecycle(this, preview, imageCapture);
    }


    private void updateTransform()
    {
        Matrix matrix = new Matrix();

        float centerX = textureView.getWidth() / 2f;
        float centerY = textureView.getHeight() / 2f;

        float rotationDegrees = 0f;
        switch (textureView.getDisplay().getRotation())
        {
            case Surface.ROTATION_0:
                rotationDegrees = 0f;
                break;

            case Surface.ROTATION_90:
                rotationDegrees = 90f;
                break;

            case Surface.ROTATION_180:
                rotationDegrees = 180f;
                break;

            case Surface.ROTATION_270:
                rotationDegrees = 270f;
                break;

            default:
                return;
        }

        matrix.postRotate(-rotationDegrees, centerX, centerY);

        textureView.setTransform(matrix);
    }


    private Bitmap imageProxyToBitmap(ImageProxy image)
    {
        ImageProxy.PlaneProxy planeProxy = image.getPlanes()[0];
        ByteBuffer buffer = planeProxy.getBuffer();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);

        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
}

I have tried looking at existing questions: This question and this question was solved as an issue with realm-java here. This hints to the problem being related to DEX directories not being detected by Gradle, but I am not sure how I would implement this solution to my code.

Other related questions on StackOverflow also refer to software-specific solutions, such as Gradle, Xamarin, or Unity. Other questions were fixed by either upgrading packages (which I have avoided thus far, although a working solution using this is welcome), or are either without a solution.

LATL9
  • 1

0 Answers0