Qt/C++/Android - 如何通过编程方式安装 .APK 文件?

3

我正在我的应用程序内实现自己的自动更新器。我成功地将新版本的.apk文件下载到sdcard上的/Download文件夹中,但是我无法弄清楚如何打开/运行该文件,以便用户呈现安装新版本的对话框。

我能想到的唯一方法:

QString downloadedAPK = "/storage/emulated/0/Download/latest.apk"; // Not hardcoded, but wrote it here this way for simplicity
QDesktopServices::openUrl(QUrl(downloadedAPK));

调试器输出:

D/Instrumentation(26418): checkStartActivityResult  :Intent { act=android.intent.action.VIEW dat=file:///storage/emulated/0/Download/latest.apk }
D/Instrumentation(26418): checkStartActivityResult  inent is instance of inent:
W/System.err(26418): android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=file:///storage/emulated/0/Download/latest.apk }
W/System.err(26418):    at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1660)
W/System.err(26418):    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1430)
W/System.err(26418):    at android.app.Activity.startActivityForResult(Activity.java:3532)
W/System.err(26418):    at android.app.Activity.startActivityForResult(Activity.java:3493)
W/System.err(26418):    at android.app.Activity.startActivity(Activity.java:3735)
W/System.err(26418):    at android.app.Activity.startActivity(Activity.java:3703)
W/System.err(26418):    at org.qtproject.qt5.android.QtNative.openURL(QtNative.java:110)
W/System.err(26418):    at dalvik.system.NativeStart.run(Native Method)

我已经到处寻找如何从Qt中打开APK文件的相关信息,但是没有找到任何内容。唯一找到的是使用JNI的解决方案(我不想使用它,因为用C++做会更简单,而且我对整个C++ / JNI的东西一窍不通),但是这个解决方案没有很好的文档,所以我不知道如何使其工作。

那么,打开下载的apk文件最简单的方法是什么?



编辑:

我遵循了Tumbus的答案,但由于编译错误,我必须对他的JNI代码进行一些修改,如下所示:

void Updater::InstallApp(const QString &appPackageName)
{
    qDebug() << "[+] APP: " << appPackageName; // Which is the string ("/storage/emulated/0/Download/latest.apk")
    QAndroidJniObject app = QAndroidJniObject::fromString(appPackageName);
    QAndroidJniObject::callStaticMethod<jint>("AndroidIntentLauncher",
                                       "installApp",
                                       "(Ljava/lang/String;)I",
                                       app.object<jstring>());
}

当我在我的安卓设备上运行应用程序时,它从服务器上拉取最新的 .apk 文件,但之后没有任何反应。为什么会这样?(在此之前我还没有对 AndroidManifest.xml 进行任何更改。)
1个回答

4
您需要创建一个自定义意图来安装APK。有关详细信息,请参见此问题
恐怕这样的平台特定操作必须调用平台特定API。好消息是,Qt框架已经简化了JNI的封装,您可以将Java类包含到Android项目中。因此,我会从Qt中调用我的自己的静态java函数。

示例

Java类

package io.novikov.androidintentlauncher;

import org.qtproject.qt5.android.QtNative;

import java.lang.String;
import java.io.File;
import android.content.Intent;
import android.util.Log;
import android.net.Uri;
import android.content.ContentValues;
import android.content.Context;

public class AndroidIntentLauncher
{
    protected AndroidIntentLauncher()
    {
    }

    public static int installApp(String appPackageName) {
        if (QtNative.activity() == null)
            return -1;
        try {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(new File(appPackageName)), 
                                               "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            QtNative.activity().startActivity(intent);
            return 0;
        } catch (android.content.ActivityNotFoundException anfe) {
            return -3;
        }
    }

}

请注意,startActivity() 应该作为方法从 *QtNative.activity() 调用。我们必须按照传统规则为 Java 维护特殊的目录结构。下面是 Makefile 部分的示例。

JNI

调用此方法的 C++ 代码有点棘手。
const static char* MY_JAVA_CLASS = "io/novikov/androidintentlauncher/AndroidIntentLauncher";


static void InstallApp(const QString &appPackageName) {
        QAndroidJniObject jsText = QAndroidJniObject::fromString(appPackageName);
        QAndroidJniObject::callStaticMethod<jint>(MY_JAVA_CLASS,
                                           "installApp",
                                           "(Ljava/lang/String;)I",
                                           jsText.object<jstring>());
   }

字符串字面值"(Ljava/lang/String;)I"是Java方法的签名。

Java类的名称必须完整,例如"my/domain/my_app/MyClass"

Makefile

最后一个挑战是正确地将Java代码包含到您的项目中。以下是.pro文件的相关片段。

android {
    QT += androidextras
    OTHER_FILES += android_src/src/io/novikov/androidintentlauncher/AndroidIntentLauncher.java
    ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android_src
}

QMake有一个特殊的变量ANDROID_PACKAGE_SOURCE_DIR用于此任务。Java源代码必须位于ANDROID_PACKAGE_SOURCE_DIR/src/my/domain目录中。 同时,不要忘记将Java源文件添加到OTHER_FILES并包含androidextras QT选项。


我已经严格按照您的指示操作,但是我遇到了这个错误:**~\AndroidIntentLauncher.java:32: error: error: reached end of file while parsing**。 - Alaa Salah
抱歉,Java代码片段中最后一个封闭大括号丢失了。已修复。 - Tumbus
哦,我没注意到,非常感谢。现在应用程序按预期运行,从服务器拉取apk,但接下来似乎什么也没有发生。我是这样调用您的方法的:InstallApp("/storage/emulated/0/Download/latest.apk"); - 我已经编辑了我的问题来解释所有这些。 - Alaa Salah
我理解了问题所在,MY_JAVA_CLASS必须以完整形式包括域名和应用程序名称。在我的代码中是"io/novikov/androidintentlauncher/AndroidIntentLauncher"。 - Tumbus
非常感谢您的所有努力,现在完美无缺。 - Alaa Salah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接