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.