I'm developing an Android app using Qt 6.9 that needs to scan WiFi networks. I've created a custom Java class to handle the WiFi scanning, but when I try to call it from C++ using QJniObject, I get a ClassNotFoundException.
Error:
W/default : java.lang.ClassNotFoundException: Didn't find class "org.qtproject.example.WifiScanner" on path: DexPathList[[zip file "/data/app/~~UP-gr_qyeyNz7-Yy5qowag==/org.qtproject.example.appble_demo-EXVcLfqQlNUZsaYXDEE0KQ==/base.apk"],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64]]
... [stack trace] ...
D/default : Scan failed or no networks found
Key facts:
- The APK doesn't contain my WifiScanner.java class
- I've verified the Java class exists in my project
- Qt version: 6.9
- Using CMake for build system
Environment
- Windows 11 23H2 Pro
- Qt 6.9 with Qt Creator 16.0.2
- Android SDK: Android-35 (API level 35)
- Build: Clang, Arm64-v8a, Debug
- NDK: 27.2.12479018
Project Structure:
project-root/
├── android/
│ ├── src/
│ │ └── org/
│ │ └── qtproject/
│ │ └── example/
│ │ └── WifiScanner.java
│ ├── res/
│ └── AndroidManifest.xml
├── CMakeLists.txt
├── main.cpp
└── ... (other files) ...
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(hs_app VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTORCC ON)
find_package(Qt6 REQUIRED COMPONENTS Quick Bluetooth Core)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(appble_demo
main.cpp
)
qt_add_qml_module(appble_demo
URI ble_demo
VERSION 1.0
QML_FILES
Main.qml
RESOURCES res/img.qrc
QML_FILES BleDev.qml
SOURCES bledevlistmodel.h bledevlistmodel.cpp
SOURCES wifiListModel.h wifiListModel.cpp
RESOURCES android/src/org/qtproject/example/WifiScanner.java
RESOURCES android/AndroidManifest.xml android/build.gradle android/res/values/libs.xml android/res/xml/qtprovider_paths.xml
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(appble_demo PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appble_demo
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(appble_demo
PRIVATE Qt6::Quick
Qt${QT_VERSION_MAJOR}::Bluetooth #添加蓝牙
Qt6::Core
)
include(GNUInstallDirs)
install(TARGETS appble_demo
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
AndroidManifest.xml
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.qtproject.example" android:installLocation="auto" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:versionName="-- %%INSERT_VERSION_NAME%% --">
<!-- %%INSERT_PERMISSIONS -->
<!-- %%INSERT_FEATURES -->
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
<application android:name="org.qtproject.qt.android.bindings.QtApplication" android:hardwareAccelerated="true" android:label="-- %%INSERT_APP_NAME%% --" android:requestLegacyExternalStorage="true" android:allowBackup="true" android:fullBackupOnly="false">
<activity android:name="org.qtproject.qt.android.bindings.QtActivity" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:launchMode="singleTop" android:screenOrientation="unspecified" android:exported="true" android:label="">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.arguments" android:value="-- %%INSERT_APP_ARGUMENTS%% --"/>
</activity>
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.qtprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths"/>
</provider>
</application>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
</manifest>
build.gradle
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
//noinspection AndroidGradlePluginVersion
classpath 'com.android.tools.build:gradle:8.8.0'
}
}
repositories {
google()
mavenCentral()
}
apply plugin: qtGradlePluginType
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
//noinspection GradleDependency
implementation 'androidx.core:core:1.13.1'
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
* - qtGradlePluginType - whether to build an app or a library
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
namespace androidPackageName
compileSdkVersion androidCompileSdkVersion
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
tasks.withType(JavaCompile) {
options.incremental = true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion
ndk.abiFilters = qtTargetAbiList.split(",")
}
}
WifiScanner.java
package org.qtproject.example;
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.util.Log;
import org.qtproject.qt.android.QtNative;
import java.util.List;
public class WifiScanner {
private static final String TAG = "WifiScanner";
public static String[] scanWifiNetworks() {
//mycode...
}
C++ code
void wifiListModel::scanWifi()
{
// 调用 Java 静态方法
QJniObject resultArray = QJniObject::callStaticObjectMethod(
"org/qtproject/example/WifiScanner",
"scanWifiNetworks",
"()[Ljava/lang/String;" // JNI 签名:返回 String 数组
);
//QJniObject resultArray = WFSCAN::callStaticMethod<QJniObject>("scanWifiNetworks");
if (resultArray.isValid()) {
jobjectArray arr = static_cast<jobjectArray>(resultArray.object<jarray>());
QJniEnvironment env;
int len = env->GetArrayLength(arr);
for (int i = 0; i < len; i += 3) {
QJniObject ssid = env->GetObjectArrayElement(arr, i);
QJniObject level = env->GetObjectArrayElement(arr, i + 1);
QJniObject security = env->GetObjectArrayElement(arr, i + 2);
qDebug() << "Network:" << ssid.toString()
<< "Strength:" << level.toString() << "dBm"
<< "Security:" << security.toString();
}
} else {
qDebug() << "Scan failed or no networks found";
//app in to this
}
}
Why isn't my custom Java class being included in the APK despite having the correct directory structure? What's missing in my CMake configuration to properly package the Java files?
- Verified the Java package/class name matches exactly
- Confirmed the file exists at android/src/org/qtproject/example/WifiScanner.java
- Cleaned and rebuilt the project multiple times
- Checked the APK contents (no WifiScanner.class present)