Добрый день, уважаемые хабровчане! В этой статье я хочу рассказать о своём опыте использования qt и android studio. А именно о том, как мне надо было в qt нарисовать текст и передать в андроид студию. Несмотря на простоту задачи, решение её заняло у меня довольно много времени и может быть кому-нибудь когда-нибудь где-нибудь сэкономит массу времени. Статья в каком-то смысле претендует на изобретение велосипеда, но в интернете я не нашёл решения. Кому интересно — добро пожаловать под кат!

Немного о самой задаче


Недавно встала передо мною задача портировать приложение с ios на андроид. Основной болью при портировании была работа с SDK приложения. Оно было написано на Qt и исопльзовалось для рисования текста/стрелочек/областей и всего прочего. То есть, приложение было написано на objective c, и использовало qt библиотеку, а не было qt проектом. Поэтому, первым делом встал вопрос среды разработки. Поскольку, я совсем в этом деле новичок, мой выбор пал на андроид студию. Всё-таки весь графический интерфейс, как мне показалось, лучше делать в андроид студии, а вычислительные задачи пущай делает наше qtшное SDK. В интернете не так уж много пишут об использовании qt под андроид, а здесь была задача подружить qt и андроид студию. Для работы с плюсами используется Android NDK и реализуется всё через использование JNI. Работа с JNI — вещь сама по себе довольно интересная. В нете можно найти массу статей на эту тему (например этот замечательный цикл). Однако меня интересует JNI в разрезе использования его с Qt. Опять же, в чём проблема, спросите вы? Берём сишные сорсы, делаем шаред либу, подключаем к проекту в андроид студии и получаем profit! Вот, как например тут. А вот здесь и начинается самое интересное…

Использование qt в андроид студии


Как вы помните, я указал выше, что
оно было написано на Qt и исопльзовалось для рисования текста/стрелочек/областей и всего прочего
.
Чтобы нарисовать графический примитив в QT, нам не требуется создавать экземпляр QApplication или QGuiApplication. Даже QCoreApplication — и тот не нужен! А вот для рисования текста без QApplication или QGuiApplication уже никак нельзя обойтись. Так в чём проблема, спросите вы? Проблема наступает как раз на момент вызова конструктора:

QApplication a(argc, argv);

Если вы создадите билиблиотеку, в ней какую-либо функцию, вызывающую конструктор QApplication, а затем вызовите её через JNI из приложения андроид студии, то сразу же словите:
This application failed to start because it could not find or load the Qt platform plugin «android».

Кто винов Что делать?


Вариант классический. Учить матчасть!


Первое, что я решил сделать — нагуглить решение проблемы в интернете. Точного совпадения я не нашёл, но в довольно большом количестве постов люди жаловались на похожие проблемы для плагина под винду. Вот и я перепробовал всё, что было указано здесь, но, увы, решения (работающего для меня! ) не было найдено.

В поисках ответа на свои вопросы, я наткнулся на такой довольно любопытный блог, как я понял автора qt под андроид. Блог весьма интересный, но в нём автор делает акцент (опять же, моё имхо) на разработку со стороны с++ и запуска всего добра из qt creator. Меня такой подход, если честно, не очень устраивал по одной причине: отладка Java части из Qt практически невозможна (можно только компилировать код, потом ждать приаттачивания из андроид студии и уже оттуда наблюдать происходящее), а также у меня довольно большое количество различных layoutov, кастомных вьюх, асинхронных задач, а как это добро засунуть в qt проект и нормально отлаживать? Честно говоря, я не знаю.

Эксперименты


Я попробовал создать также Qt приложение и запустить его на андроиде. Запускал я его через qt-creator и как ни странно оно благополучно запустилось. Я стал смотреть более подробно как устроен манифест, граддл, код приложения. Я обнаружил такую интересную вещь в манифесте:
<!-- Deploy Qt libs as part of package -->
            <meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
            <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
            <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
            <!-- Run with local libs -->
            <meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
            <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
            <meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so"/>
            <meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidAccessibility.jar:jar/QtAndroid-bundled.jar:jar/QtAndroidAccessibility-bundled.jar"/>
            <meta-data android:name="android.app.static_init_classes" android:value=""/>

Вкратце смысл его понятен. Когда я собирал apk приложения, я указал, что qt библиотеки должны находиться внутри apk и именно оттуда надо их грузить своему приложению. Подключение соответстсвующих jar-ов в проект на андроиде, прописывание в андроидовском манифесте того, что было в qt, размещение qtшных .so плагинов в папке jniLibs не дало никакого эффекта.

Изучение плагинов


Я попробовал уже наконец грузить самостоятельно со стороны java этот несчастный плагин libqtforandroid.so (до создания QApplication) путём
System.loadLibrary(«plugins_platforms_android_libqtforandroid»);
, но всё равно падало! Правда, здесь исключение было уже другое и более интересное:
I/Qt: qt start
05-17 11:12:33.975 11084-11084/имя проекта A/libc: Fatal signal 11 (SIGSEGV) at 0x00000000 (code=1), thread 11084 (ndroid.gribview)
05-17 11:12:33.978 11084-11084/имя проекта A/libc: Send stop signal to pid:11084 in void debuggerd_signal_handler(int, siginfo_t, void)

По крайней мере есть у нас зацепка, где можно смотреть. Оперативно по qt start находим интересующий нас метод:

Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
{
    QT_USE_NAMESPACE
    typedef union {
        JNIEnv *nativeEnvironment;
        void *venv;
    } UnionJNIEnvToVoid;
    __android_log_print(ANDROID_LOG_INFO, "Qt", "qt start");
    UnionJNIEnvToVoid uenv;
    uenv.venv = Q_NULLPTR;
    m_javaVM = Q_NULLPTR;
    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        __android_log_print(ANDROID_LOG_FATAL, "Qt", "GetEnv failed");
        return -1;
    }
    JNIEnv *env = uenv.nativeEnvironment;

    if (!registerNatives(env)
           || !QtAndroidInput::registerNatives(env)
            || !QtAndroidMenu::registerNatives(env)
            || !QtAndroidAccessibility::registerNatives(env)
            || !QtAndroidDialogHelpers::registerNatives(env)) {
        __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
        return -1;
    }
    m_javaVM = vm;
    return JNI_VERSION_1_4;
}

Судя по логу, он упал где-то в каком-то из registerNatives.Так и было (я прописал логи в каждом из registerNatives). Он падал в

registerNatives(env)

А именно:

jmethodID methodID;
    GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "activity", "()Landroid/app/Activity;");
         __android_log_print(ANDROID_LOG_INFO, "Check Class 8", "activity ");
    jobject activityObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
         __android_log_print(ANDROID_LOG_INFO, "Check Class 9 ", " methodID ");
    GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "classLoader", "()Ljava/lang/ClassLoader;");
    __android_log_print(ANDROID_LOG_INFO, "Check Class 10", " classLoader ");

    if(activityObject!=nullptr)
    {
        __android_log_print(ANDROID_LOG_INFO, "No tull activityObject", " Not Null ");
    }



    if(methodID!=nullptr)
    {
        __android_log_print(ANDROID_LOG_INFO, "No tull methodID", " Not Null ");
    }

    m_classLoaderObject = env->NewGlobalRef(env->CallStaticObjectMethod(m_applicationClass, methodID));

    if(m_classLoaderObject!=nullptr)
    {
        __android_log_print(ANDROID_LOG_INFO, "No tull m_classLoaderObject", " Not Null ");
    }
    
    clazz = env->GetObjectClass(m_classLoaderObject);

Падение произошло на последней строчке. classLoaderObject оказался равен null. А это произошло, что activityObject тоже равен null. Окей. Перед тем, как грузить этот злосчастный плагин попробуем создать активити для JNI. Для этого пропишем в Java коде следующие строчки:

        QtNative.setActivity(this, null);
        QtNative.setClassLoader(getClassLoader());

Небольшое отступление. Класс QtNative лежит в jar файлах, которые мы подключаем к проекту. Более того, это весьма любопытный класс. В нём есть методы:

       QtNative.loadBundledLibraries();
       QtNative.loadQtLibraries();

которые и должны подгружать требуемые плагины. Пока запомним это, и вернёмся к подключению нашего плагина вручную. Вызов методов QtNative setActivity и setClassLoader помог проскочить:

registerNatives(env)

но засада была уже в QtAndroidInput::registerNatives(env). Не совпадали сигнатуры функций для события keyDown. В принципе, мне ничего не нужно кроме шрифтов и я закомментировал следующий участок кода:

  if (!registerNatives(env)
          /* || !QtAndroidInput::registerNatives(env)
            || !QtAndroidMenu::registerNatives(env)
            || !QtAndroidAccessibility::registerNatives(env)
            || !QtAndroidDialogHelpers::registerNatives(env)*/) {
        __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
        return -1;
    }

и вроде как-бы благополучно загрузил этот плагин. Запускаем приложение, грузим плагин, вызываем QApplication и… ловим наше остознакомое исключение:
This application failed to start because it could not find or load the Qt platform plugin «android».

Более того, вызов

       QtNative.loadBundledLibraries();
       QtNative.loadQtLibraries();

тоже не решил проблемы. Хорошо. Ладно. Полезем в сорсы создания конструктора. По исключению быстро находим метод:

static void init_platform(const QString &pluginArgument, const QString &platformPluginPath, const QString &platformThemeName, int &argc, char **argv)
{
    // Split into platform name and arguments
    QStringList arguments = pluginArgument.split(QLatin1Char(':'));
    const QString name = arguments.takeFirst().toLower();
    QString argumentsKey = name;
    argumentsKey[0] = argumentsKey.at(0).toUpper();
    arguments.append(QLibraryInfo::platformPluginArguments(argumentsKey));

   // Create the platform integration.
    QGuiApplicationPrivate::platform_integration = QPlatformIntegrationFactory::create(name, arguments, argc, argv, platformPluginPath);
    if (QGuiApplicationPrivate::platform_integration) {
        QGuiApplicationPrivate::platform_name = new QString(name);
    } else {
        QStringList keys = QPlatformIntegrationFactory::keys(platformPluginPath);

        QString fatalMessage
                = QStringLiteral("This application failed to start because it could not find or load the Qt platform plugin \"%1\".\n\n").arg(name);
       ....

Хорошо. Ищем, откуда вызываем сей метод:

void QGuiApplicationPrivate::createPlatformIntegration()
{
    // Use the Qt menus by default. Platform plugins that
    // want to enable a native menu implementation can clear
    // this flag.
    QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true);

    // Load the platform integration
    QString platformPluginPath = QLatin1String(qgetenv("QT_QPA_PLATFORM_PLUGIN_PATH"));


    QByteArray platformName;
#ifdef QT_QPA_DEFAULT_PLATFORM_NAME
    platformName = QT_QPA_DEFAULT_PLATFORM_NAME;
#endif
    QByteArray platformNameEnv = qgetenv("QT_QPA_PLATFORM");
    if (!platformNameEnv.isEmpty()) {
        platformName = platformNameEnv;
    }

    QString platformThemeName = QString::fromLocal8Bit(qgetenv("QT_QPA_PLATFORMTHEME"));

    // Get command line params

    QString icon;

    int j = argc ? 1 : 0;
    for (int i=1; i<argc; i++) {
        if (argv[i] && *argv[i] != '-') {
            argv[j++] = argv[i];
            continue;
        }
        const bool isXcb = platformName == "xcb";
        QByteArray arg = argv[i];
        if (arg.startsWith("--"))
            arg.remove(0, 1);
        if (arg == "-platformpluginpath") {
            if (++i < argc)
                platformPluginPath = QLatin1String(argv[i]);
        } else if (arg == "-platform") {
            if (++i < argc)
                platformName = argv[i];
        } else if (arg == "-platformtheme") {
            if (++i < argc)
                platformThemeName = QString::fromLocal8Bit(argv[i]);
        } else if (arg == "-qwindowgeometry" || (isXcb && arg == "-geometry")) {
            if (++i < argc)
                windowGeometrySpecification = QWindowGeometrySpecification::fromArgument(argv[i]);
        } else if (arg == "-qwindowtitle" || (isXcb && arg == "-title")) {
            if (++i < argc)
                firstWindowTitle = QString::fromLocal8Bit(argv[i]);
        } else if (arg == "-qwindowicon" || (isXcb && arg == "-icon")) {
            if (++i < argc) {
                icon = QString::fromLocal8Bit(argv[i]);
            }
        } else {
            argv[j++] = argv[i];
        }
    }

    if (j < argc) {
        argv[j] = 0;
        argc = j;
    }

    init_platform(QLatin1String(platformName), platformPluginPath, platformThemeName, argc, argv);

    if (!icon.isEmpty())
        forcedWindowIcon = QDir::isAbsolutePath(icon) ? QIcon(icon) : QIcon::fromTheme(icon);
}

То бишь, нам можно через argc и argv передать аргументы, где надо искать этот плагин. Сразу оговорюсь, я пробовал в дебаггере qt запускать приложение под андроид, и там argc и argv соответственно равны: 1 и имя_нашей_библиотеки_которую_собирает_qt, но никак не плагин. Попробуем присвоить argc и argv соответствующие значения:

char *SDKEnvironment::argv[] = {"-platform libplugins_platforms_android_libqtforandroid.so:plugins/platforms/android/libqtforandroid.so -platformpluginpath /data/app-lib/папка_для_jniLibs"};

Неа, не сработало.

Решение


Честно говоря, сроки поджимают, а дальше заниматься изучением что и где не сработало — у меня нету силвремени. Решение, которое мне помогло, следующее:

  1. Создадим в qt не apk, не so, а aar. Для этого идём в qt creator и находим gradle файл, а в нём меняем строчку apply plugin: 'com.android.applicatioin' на apply plugin: 'com.android.library' . Таким образом мы создаём aar файл, а не apk
  2. Теперь добавим его в наше приложение в андроид студии. Идём в New->Module выбираем import aar, затем правой кнопкой мышки щёлкаем на наш модуль, выбиараем Open Module Settings, идём во вкладку dependency и добавляем зависимость к qtному модулю

Затем я перенёс всё jni, которое было у меня в андроид студии, в qt. Попробовал снова создать QApplication — и всё заработало.

Резюме


Я почти что уверен, что есть и другой способ решить эту проблему. Если кто-нибудь укажет, где я ошибся — то было бы просто замечательно. В интернете я не нашёл решения проблемы, поэтому предлагаю своё.
Поделиться с друзьями
-->

Комментарии (19)


  1. vladest
    26.05.2016 17:21
    +1

    Не пойму, что мешало загрузить проект в QtCreator и добавить поддержку андроида там


    1. Rogvold91
      26.05.2016 18:15

      Поддержка в QtCreator Java очень слабая. Хотелось просто всё отлаживать через андроид студию и работать именно с ней, а не в Qt


      1. MaxQwerty
        27.05.2016 08:07
        +1

        Извините, но разве Вам не нужно было уже написанное на Qt/C++ приложение под iOS портировать на Android? То есть, нужно было решить задачу настройки проекта для сборки под Android? Ну и разрешить некоторые возможные проблемы, но делать это не с помощью JAVA, а с использованием C++ или если повезёт, то QML?


        1. Rogvold91
          27.05.2016 08:22
          +1

          не. приложение на objective c. Оно использовало SDK, написанное на qt (процентов 10 всего приложения), Поэтому, так сходу сделать нельзя было. Smog1on1the1water постом следующим комментом более точно описал проблему


        1. Smog1on1the1water
          27.05.2016 08:56
          +1

          >> Извините, но разве Вам не нужно было уже написанное…

          Нет, не нужно.

          Прикладная библиотека реализует общую для всех платформ бизнес-логику. При этом UI под каждой платформой в общем случае отличается, поскольку решает различные задачи, создается для различных условий и различных/различными сторонними компаниями.

          Поэтому речь не о портировании UI вообще, а о создании нового UI свойственными платформе средствами с использованием кросс-платформенной библиотеки, содержащей реализацию бизнес-логики.


    1. Smog1on1the1water
      27.05.2016 08:08
      +1

      Ничего не мешало, так и было сделано. Автор статьи просто акцентировался на злоключениях, поэтому некоторые другие моменты описания исходной задачи были недостаточно ацентированы.

      SDK, о которой идет речь — это прикладная библиотека, которая работает со сложными данными и выполняет достаточно интенсивные вычисления в single precision float. Она оптимизирована с использованием SIMD SSE/NEON и работает в (разных!) реальных проектах под Windows, OS X/iOS, Fedora x64/ARM и др. Использование Qt исторически обусловлено рисованием графических 2D примитивов (плюс сторонние ограничения, но речь не о них), и вот сейчас подошла очередь Android, под который данная прикладная библиотека и была безо всяких проблем собрана. Да, проект был загружен в QtCreator, нажаты «те самые две кнопки» для поддержки андроида, внесены необходимые изменения под специфику runtime libraries, и все благополучно собралось.

      Так что проблема не с ней, а с UI в виде конечного приложения. Этот UI, как и ранее на других платформах, был выполнен посредством естественных средств, предоставляемых самой средой. Т.е. в данном случае на Java/Android Studio. Повторю еще раз: весь UI сделан на Java, а не на QT/C++, методы прикладной библиотеки благополучно вызываются через JNI.

      Такая связка прекрасно работает (и прекрасно зарекомендовала себя на всех предыдущих платформах), но за исключением тех случаев в Android (только в нем и только для текста), когда библиотеке необходимо вывести текст (QPainter::drawText). Для этого требуется наличие созданного экземпляра QApplication, который, в свою очередь, хочет иметь загруженным плагин платформы (в данном случае libqtforandroid). Который и не хочет грузиться. QtCore, QtGui грузятся, а libqtforandroid — нет. Причем QGuiApplication (именно в нем вызывается загрузка платформенного плагина) не может никак увидеть плагин загруженным даже в том случае, если он предварительно был принудительно и успешно загружен «руками» посредством средств самого Qt.

      Изучение проблемы выявило, что андроидная реализация Qt отличается своей спецификой от других платформ. В частности, Qt неявно предполагает, что оно было загружено посредством специального Java Qt Activity, которое через JNI загружает .so библиотеку от собранной C++ части, ищет в нем символ main и передает в него управление. Поэтому, если UI выполнено на С++ и собрано в QtCreator — QtActivity.class там неявно присутствует, и загрузка libqtforandroid проходит благополучно. А вот в случае, если UI собирается в Android Studio и Java Activity свое собственное — инициализацию выполнить не удается (по крайней мере, нам пока не удалось найти правильного решения).


      1. vladest
        27.05.2016 08:58
        +1

        Возможно настало время переписать UI на Qt/QML и решить проблему раз и навсегда?


        1. Smog1on1the1water
          27.05.2016 09:22
          +1

          К сожалению, в данном случае это будет сродни заметанию сора под ковер:

          1. Android UI уже написано, работает (за исключением ситуации с отрисовкой текста), и тратить ресурсы на его переделку на данном конкретном цикле разработки бессмысленно
          2. В следующих циклах разработки планируется отказаться от Qt части в принципе (можно было бы поперечислять причины, но тема совсем не об этом).


  1. Zifix
    26.05.2016 18:10

    Чукча купил себе Камаз, приехал домой, собрал родню у себя в чуме, сидит, рассказывает:
    — Хорошая машина Камаз. В кабине тепло, фары тайгу освещают… одно плохо — собаки быстро устают.


    1. Smog1on1the1water
      27.05.2016 09:42
      -3

      Забавный анекдот, вот только не разобравшись в ситуации, ошибочно интерпретировав слова автора и посчитав его идиотом, кем в итоге вы показали себя?

      Минус в карму можете себе добавить самостоятельно.


      1. Zifix
        27.05.2016 10:09
        +1

        Господа, вы уж извините, но отредактировать статью, а потом отвечать на комментарии к первоначальной версии в таком ключе — это грубое нарушение сетевого этикета, если говорить мягко. Гугл все помнит, я сейчас даже скрин сделал с кеша, раз такие претензии появились.

        Было:

        Недавно встала передо мною задача портировать приложение с ios на андроид. Основной болью при портировании была работа с SDK приложения. Оно было написано на Qt и исопльзовалось для рисования текста/стрелочек/областей и всего прочего.
        Речь идет о портировании приложения, мельком замечание про какое-то загадочное SDK и дальше некое «оно». Логично предположить, что речь о портировании приложения, которое написано на Qt, хотя по факту это вообще написание нативного приложения, которое просто использует библиотеку на Qt

        Сейчас добавилось предложение:
        То есть, приложение было написано на objective c, и использовало qt библиотеку, а не было qt проектом.
        Это меняет картину, и конечно, выставляет меня человеком который даже читать не умеет, не то что думать.

        Так что если кому и минус в карму, так точно не мне.


        1. Smog1on1the1water
          27.05.2016 10:26
          -1

          Ну если для вас является нормой считать окружающих идиотами (ну да, ведь это же логично предположить, правильно?), то я вас понял, да. Голова и язык — они же только для этого и предназначены, чтобы травить анекдоты на хабре.


  1. Zifix
    26.05.2016 19:05

    Статья в каком-то смысле претендует на изобретение велосипеда, но в интернете я не нашёл решения… В интернете не так уж много пишут об использовании qt под андроид, а здесь была задача подружить qt и андроид студию.
    Конечно, не нашли, так просто никто не делает.
    Недавно встала передо мною задача портировать приложение с ios на андроид.
    Для этого нужно нажать две кнопки в QtCreator
    Всё-таки весь графический интерфейс, как мне показалось, лучше делать в андроид студии
    Что? Зачем? Чем лучше? Что значит показалось? Лучше потратить месяц чем нажать две кнопки?

    Вы сами себе на ровном месте создали проблемы и героически их преодолели, статья о том, как нельзя делать ни в коем случае.


    1. Rogvold91
      26.05.2016 19:07

      Для этого нужно нажать две кнопки в QtCreator

      Был бы весьма признателен, если бы сказали какие. Не хочу в будущем повторять ошибок.


      1. Zifix
        26.05.2016 19:23
        +1

        Открываем .pro файл в QtCreator, появляется нечто вроде этого:

        Настройка проекта


        1. Rogvold91
          27.05.2016 08:13

          Вы не поняли меня. У меня на Qt небольшая библиотека, которая занимается расчётом и рисованием, а не весь проект. Весь гуи написан был на чистом objective c. Когда я начинал портирование, то передо мною стоял вопрос: на чём реализовывать основную часть? Либо Android Studio, либо всё писать в Qt. Я выбрал андроид студию потому, что по ней масса литературы и я знаком с Java. Знал бы я, что так трудно подключить qt в неё, то сразу бы делал всё в qt.
          Очень хорошо смысл проблемы описал Smog1on1the1water немного выше


  1. bitterman
    26.05.2016 20:16

    Для Qt под Android, особенно на момент начального запуска, в итоге сам пришёл к использованию Ministro. Тогда Qt получается отдельно и некоторые вещи, типа по какой поверхности рисовать приложение и что там с потоками обработки событий — Ministro это решает лучше, чем встраиваемый в apk Qt.

    Другое дело, что Ministro — совсем внешняя зависимость и недавно у download.qt-project.org кончился SSL-сертификат, и Ministro поэтому отказался запускать наше приложение :-)

    Вопрос решился созданием своего зеркала Ministro, о чём и хочу предупредить коллег :-)


    1. Smog1on1the1water
      27.05.2016 09:31

      >> download.qt-project.org кончился SSL-сертификат, и Ministro поэтому отказался запускать наше приложение

      Welcome to DLL hell, now on Android!

      А вот про зеркало было бы любопытно послушать для общего развития.


      1. bitterman
        27.05.2016 10:30

        Очень просто, нашёл на cdimages.debian.org зеркало Министро в виде тупо файлов, wget'ом выкачал (опциями типа -np -m) всё, что выкачалось (получилось порядка 1 гигабайта), выложил на свой сервер так, чтобы пути получались такие же, как у qt.io и download.qt-project.org, в нужной мне xml'ке (libs.xml, относящийся к qt 5.4) поменял sed'ом старый адрес сервера на новый, попутно убедившись, что нигде-нигде нет ни одного слова про https, в котором возможны проблемы с сертификатом. В своём приложении в xml настройке Министро прописал свой адрес. Заработало :-)