0

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:

  1. The APK doesn't contain my WifiScanner.java class
  2. I've verified the Java class exists in my project
  3. Qt version: 6.9
  4. 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

Environmental Details

Project Structure:

project-root/
├── android/
│   ├── src/
│   │  └── org/
│   │       └── qtproject/
│   │           └── example/
│   │               └── WifiScanner.java
│   ├── res/
│   └── AndroidManifest.xml
├── CMakeLists.txt
├── main.cpp
└── ... (other files) ...

project-root image

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?

  1. Verified the Java package/class name matches exactly
  2. Confirmed the file exists at android/src/org/qtproject/example/WifiScanner.java
  3. Cleaned and rebuilt the project multiple times
  4. Checked the APK contents (no WifiScanner.class present)

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.