前言
上一篇介绍的是Qt5.15.2进行apk文件安装,本文档介绍的是,使用Qt6.2.2
虽然Qt6好多功能还在开发中,但新功能也很多如3D。笔者尝试了下,也可以正常使用的
本次文档,记录通过jni进行APK文件安装
一、实现方法
Qt使用jni安装的原理基本相同,实现方法主要分如下两种:
1.使用jni直接调用已写好的java代码
2.使用jni直接调用java接口
两者需要了解下java相关的代码,后者要求更高一点,每一步操作,都要经过jni。前者需要额外编写java代码,后者直接在C++中调用即可
笔者本次使用的是后者,我们看下如何实现
二、新建Qt项目
本文档着重实现apk安装,因此非相关部分,就不详细介绍了
1.新建一个Qt Quick项目
2.编写工具类Tool,并将实例注册到qml上下文中
3.qml界面放入一个Label和一个Button,并实现调用工具类Tool的installApk()函数
4.生成android相关配置文件
三、修改AndroidManifest ,注册FileProvider
1.修改包名Package name
此步非必要,但建议读者实际使用时,修改成自己的
2.添加权限Permissions
权限代码添加如下
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
...>
...
</application>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
</manifest>
(1)android.permission.REQUEST_INSTALL_PACKAGES
这个权限在下拉菜单中,是找不到的,需要手工添加到AndroidManifest.xml中
Android7.0引入“私有目录被限制访问”,“StrictMode API 政策”。
targetsdkversion大于25(android 7.1.1及以上),必须声明REQUEST_INSTALL_PACKAGES权限,见官方说明
(2)android.permission.READ_EXTERNAL_STORAGE
若android版本在6.0及以上,可以不用添加。因为从Android6.0开始,引入的动态权限控制(Runtime Permissions),需要运行时,实时获取才行。
3.添加FileProvider
若android版本在7.0及以上,必须要注册FileProvider
添加如下代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
...>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="edu.xidianqd.updatefileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/update_files" />
</provider>
...
</application>
</manifest>
需要注意的地方是
(1)android:authorities 组件标识参数,在FileProvider的函数getUriForFile()方法中需要传入该参数,android:authorities可以随意定义
笔者建议,尊重行业规则,以包名开头,避免和其它应用发生冲突
android:authorities="${applicationId}.fileProvider"
(2)exported:是否允许公开使用
(3)grantUriPermissions:是否允许授权文件的临时访问权限
(4)meta-data
name:值为android.support.FILE_PROVIDER_PATHS不可修改;
resource:是引用配置文件路径信息的xml文件,需要与下一步update_files.xml文件对应
四、创建update_files.xml
上面配置文件中 android:resource="@xml/file_paths" 指的是当前组件引用 res/xml/file_paths.xml 这个文件
在android/res下创建xml文件夹,并在xml文件夹下创建update_files.xml
注意:文件的位置与名称,需要与上一步android:resource参数对应
此处可添加如下内容
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<files-path path="files" name="files" />
<cache-path path="files" name="cache" />
<external-path path="files" name="external" />
<external-files-path path="files" name="externalfiles"/>
<!-- 此标签需要 support 25.0.0以上才可以使用-->
<external-cache-path path="files" name="externalcache"/>
<root-path path="path" name="root" />
</paths>
</resources>
name:名称标志字符串,不可以同名!
path:文件夹“相对路径”,完整路径取决于当前的标签类型。
注:path="",是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。
| 标签 | 路径 |
|---|---|
| …….. | * 代表 当前文件夹及其子文件夹 |
| file-path | 物理路径为Context.getFilesDir() + /files/* |
| cache-path | 物理路径为Context.getCacheDir() + /files/* |
| external-path | 物理路径为Environment.getExternalStorageDirectory() + /files/* |
| external-files-path | 物理路径为Context.getExternalFilesDir(String) + /files/* |
| external-cache-path | 物理路径为Context.getExternalCacheDir() + /files/* |
| 标签 | 路径 |
|---|---|
| …….. | * 代表 当前文件夹及其子文件夹 |
| root-path | 物理路径相当于 /path/* |
笔者添加的文件内容如下
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<external-path path="Download" name="download_dir" />
</paths>
</resources>
本次测试,笔者将apk文件放在SD卡的Download目录中
注:网上有资料说,若path="Download",则只能添加这一个目录;若path="Download/",会将目录中的子目录也添加进去。笔者没测试,有兴趣的小伙伴可以试试。
五、添加依赖dependencies
示例使用androidx.core.content.FileProvider类,需要添加相应的依赖,否则程序打包可能正常,但无法正常运行,如闪退
在android/build.gradle文件中,找到dependencies,添加如下代码
implementation "androidx.core:core:1.6.0"
这是个在线的依赖,编译时可能需要联网下载
当前最新版本是1.7.0,笔者测试发现无法正常编译,就降低到1.6.0
若读者使用的android版本较低,无法编译通过,请自行降低下这个依赖的版本试试
官方参考文档
六、使用AndroidX
按照官方文档说明 AndroidX 是对 android.support.xxx 包的整理后产物。由于之前的 support 包过于混乱,所以,Google 推出了AndroidX。
由于在后续版本中,会逐步放弃对 support 的升级和维护,所以,我们必须迁移到 AndroidX .
修改当前项目的 gradle.properties,添加如下
android.useAndroidX=true
android.enableJetifier=true
android.useAndroidX=true表示当前项目启用 AndroidXandroid.enableJetifier=true表示将依赖包也迁移到AndroidX 。如果取值为 false ,表示不迁移依赖包到AndroidX,但在使用依赖包中的内容时可能会出现问题
七、通过jni调用,安装APK
整个实现,都是在Tool::installApk(const QString fileName)完成
1.获取文件读写权限
必须要对apk包所在目录有读取的权限,否则安装时,会出现“解析包时出现问题”的提示

不幸的是,qt6获取权限这方面的功能正在开发中,无法正常使用。但笔者发现,可以先到平板系统设置里,先给本测试程序添加存储权限

2.初始化File
函数原型public File (String pathname)
pathname为文件的实际目录
实现代码如下
QJniObject jFileName = QJniObject::fromString(fileName);
QJniObject jFile("java/io/File", "(Ljava/lang/String;)V", jFileName.object<jstring>());
3.初始化Intent
函数原型public Intent (String action)
实现代码如下
QJniObject jAction = QJniObject::fromString("android.intent.action.VIEW");//Intent.ACTION_VIEW
QJniObject jIntent("android/content/Intent","(Ljava/lang/String;)V", jAction.object<jstring>());
Action的值为Intent.ACTION_VIEW,对应的字符串为android.intent.action.VIEW,所以这里直接传入字符串就可以了
4.设置Intent的Flag
函数原型public Intent setFlags (int flags)
实现代码如下
// 由于没有在Activity环境下启动Activity,设置下面的标签
jIntent.callObjectMethod("setFlags","(I)Landroid/content/Intent;", 0x10000000);//Intent.FLAG_ACTIVITY_NEW_TASK
flags的值为Intent.FLAG_ACTIVITY_NEW_TASK,对应的值为268435456 (0x10000000),所以这里直接传入实际值就可以了
5.获取Uri
QJniObject jUri;
QJniObject jContext = QNativeInterface::QAndroidApplication::context();
if(QNativeInterface::QAndroidApplication::sdkVersion() < 24) //< android N
{
jUri = QJniObject::callStaticObjectMethod("android/net/Uri","fromFile",
"(Ljava/io/File;)Landroid/net/Uri;",
jFile.object<jobject>());
}
else
{
QJniObject jAuthority = QJniObject::fromString("edu.xidianqd.updatefileprovider");
jUri = QJniObject::callStaticObjectMethod("androidx.core.content.FileProvider","getUriForFile",
"(Landroid/content/Context;Ljava/lang/String;Ljava/io/File;)Landroid/net/Uri;",
jContext.object<jobject>(),
jAuthority.object<jstring>(),
jFile.object<jobject>());
//添加这一句表示对目标应用临时授权该Uri所代表的文件
jIntent.callObjectMethod("addFlags","(I)Landroid/content/Intent;", 1);//Intent.FLAG_GRANT_READ_URI_PERMISSION
}
(1)对android7.0以下,直接根据File生成Uri
函数原型public static Uri fromFile (File file)
(2)android7.0及以上,
函数原型public static Uri getUriForFile (Context context,
String authority,
File file)
android7.0及以上,对file uri的暴露做了限制,加强了安全机制。详见:官方文档 代码里出现问题的原因是,在需要安装应用的时候将下载下来的安装包地址传给了application/vnd.android.package-archive的intent
注:authority的字串,需要与AndroidManifest.xml中的android:authorities对应才行
6.设置Intent的Data和Type
函数原型public Intent setDataAndType (Uri data,
String type)
QJniObject jTypeString = QJniObject::fromString("application/vnd.android.package-archive");
jIntent.callObjectMethod("setDataAndType",
"(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/Intent;",
jUri.object<jobject>(),
jTypeString.object<jstring>());
7.startActivity
Qt6还未封装好相关功能,直接使用jni调用如下
jContext.callObjectMethod( "startActivity",
"(Landroid/content/Intent;)V",
jIntent.object<jobject>());
运行结果

点击后,跳转到安装页面

示例源码下载
后记
本篇文档,是对上一篇的升级,研究Qt6下apk文件安装,时间比较匆忙,可能有点错误,欢迎小伙伴测试
笔者测试过aab,但未成功
实际使用时,需要检查jni错误,因时间较紧,笔者未加入,请读者自行加入
笔者目前只有华为平板(鸿蒙系统2.0)这一台设备(明天,不今天归还),其他设备未测试
其实,使用Qt开发,还有一种方便的apk文件安装方法,一行java或jni代码也不用,若是有时间了,笔者可以共享给小伙伴们
笔者发现,使用jni可以做很多事,但时间所限,笔者必须要先去忙年终总结了~……~
这篇博客介绍了如何在Qt6.2.2中使用JNI来实现Android应用的APK安装过程。主要内容包括新建Qt项目、修改AndroidManifest以注册FileProvider、创建update_files.xml、添加依赖、使用AndroidX以及通过JNI调用安装APK的步骤,详细阐述了每个步骤的关键代码和注意事项。
3249





