Qt/C++/Android - 如何以编程方式安装 .APK 文件?
Qt/C++/Android - How to install an .APK file programmatically?
我正在我的应用程序中实现我自己的自动更新程序。我能够成功地将较新版本的 .apk 文件下载到 sdcard 上的 /Download 文件夹中,但我不知道如何 open/run 该文件,所以用户会看到新的安装对话框。
我唯一能想到的:
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 进行任何更改)。
您必须创建自定义意图才能安装 APK。有关详细信息,请参阅 this question。
恐怕这种特定于平台的想法必须需要调用特定于平台的 API。好消息是 Qt 框架简化了对 JNI 的总结,您可以将 Java class 包含到 Android 项目中。因此,我会制作自己的从 Qt 调用的静态 java 函数。
例子
Java class
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 class 的名称必须是完整的 "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 选项。
我正在我的应用程序中实现我自己的自动更新程序。我能够成功地将较新版本的 .apk 文件下载到 sdcard 上的 /Download 文件夹中,但我不知道如何 open/run 该文件,所以用户会看到新的安装对话框。
我唯一能想到的:
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 进行任何更改)。
您必须创建自定义意图才能安装 APK。有关详细信息,请参阅 this question。
恐怕这种特定于平台的想法必须需要调用特定于平台的 API。好消息是 Qt 框架简化了对 JNI 的总结,您可以将 Java class 包含到 Android 项目中。因此,我会制作自己的从 Qt 调用的静态 java 函数。
例子
Java class
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 class 的名称必须是完整的 "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 选项。