【问题标题】:Qt/C++/Android - How to install an .APK file programmatically?Qt/C++/Android - 如何以编程方式安装 .APK 文件?
【发布时间】:2016-02-02 22:59:09
【问题描述】:

我正在我的应用程序中实现我自己的自动更新程序。我能够成功地将较新版本的 .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>());
}

当我在我的 android 设备上运行我的应用程序时,它会从我的服务器中提取最新的 .apk 文件,然后什么也没有发生。为什么? (到目前为止,我还没有对 AndroidManifest.xml 进行任何更改)。

【问题讨论】:

    标签: android c++ qt java-native-interface apk


    【解决方案1】:

    您必须自定义 Intent 才能安装 APK。详情请见this question

    恐怕这种特定于平台的想法必须需要调用特定于平台的 API。好消息是 Qt 框架简化了对 JNI 的封装,您可以将 Java 类包含到 Android 项目中。因此,我会制作自己的静态 java 函数,从 Qt 调用。

    示例

    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"

    生成文件

    最后一个挑战是将 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: 错误:错误:解析时到达文件末尾
    • 很抱歉,Java 代码片段中缺少最后一个大括号。固定。
    • 哦,我没注意到,非常感谢。现在应用程序按预期运行,从服务器拉取 apk,但接下来似乎没有发生任何事情。我这样称呼你的方法:InstallApp("/storage/emulated/0/Download/latest.apk"); - 我已经编辑了我的问题来解释这一切。
    • 我看到了这个问题。 MY_JAVA_CLASS 必须是完整的形式,包括域名和应用名称。在我的代码中,它是“io/novikov/androidintentlauncher/AndroidIntentLauncher”
    • 非常感谢您的所有努力,现在完美无缺。