捣腾了两天终于捣鼓出来了,记录一下踩坑过程。
先说下原理,第一步就是从网络上获取更新包,这一步不难,http请求即可,
第二步,把下载下来的apk包保存到本地,需要获取存储权限,不然不能新建路径,新建文件,
第三步,打开保存了的apk包,因为不懂java,不会安卓开发,这里真的崩溃,全在这折腾了
这里需要调用java代码,而且Android7以上,不能直接打开,需要用到fileprovider。
从零开始,新建一个工程吧!
一,新建工程
环境:win10 + qt5.15.2
1,先实现http下载apk包功能,这个还是比较简单的,这里参考了QML 从无到有 3 (自动更新) - 走看看 (zoukankan.com)
download.h
#ifndef DOWNLOAD_H
#define DOWNLOAD_H
#include <QObject>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QFile>
#include <QFileInfo>
#include <QDir>
class Download : public QObject
{
Q_OBJECT
public:
explicit Download(QObject *parent = nullptr);
Q_INVOKABLE void httpDownload();
signals:
void progressPosition(double pre); //更新进度条信号
void downloadFinished(); //结束信号
protected slots:
void replyFinished(QNetworkReply*reply);
void onDownloadProgress(qint64 bytesSent,qint64 bytesTotal);
void onReadyRead();
private:
QNetworkAccessManager * accessManager = nullptr;
QNetworkRequest request;
QNetworkReply * reply = nullptr;
QString url = "这里填自己的下载地址";
QString fileName = "506787841apk.apk";
QFileInfo fileinfo;
QDir *dir;
QFile *file;
};
#endif // DOWNLOAD_H
download.cpp
#include "download.h"
void Download::httpDownload()
{
dir = new QDir("/");
qDebug()<< "mkpath:" << dir->mkpath("/storage/emulated/0/Android/data/com.hznk/files");
qDebug()<< "setCurrent:" << QDir::setCurrent("/storage/emulated/0/Android/data/com.hznk/files");
qDebug() << "QDir::currentPath():"<<QDir::currentPath();
dir->setPath(QDir::currentPath());
file = new QFile(fileName);
fileinfo = QFileInfo(*file);
qDebug() << "QFileInfo" << fileinfo.absoluteFilePath();
bool suc = file->open(QIODevice::WriteOnly);//只写方式打开文件
if(suc){
qDebug() << "文件打开成功";
}else{
qDebug() << "文件打开失败";
}
accessManager = new QNetworkAccessManager(this);
request.setUrl(url);
/******************设置http的header***********************/
// request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data");
// request.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
// request.setRawHeader("Content-Disposition","form-data;name='doc';filename='a.txt'");
//request.setHeader(QNetworkRequest::ContentLengthHeader,post_data.length());
reply = accessManager->get(request);//通过发送数据,返回值保存在reply指针里.
connect(accessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));//finish为manager自带的信号,replyFinished是自定义的
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT( onDownloadProgress(qint64 ,qint64 )));//download文件时进度
connect((QObject *)reply, SIGNAL(readyRead()),this, SLOT(onReadyRead()));
}
void Download::replyFinished(QNetworkReply *reply)
{
//获取响应的信息,状态码为200表示正常
QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
qDebug() << "网络应答完成:" <<status_code;
//无错误返回
if(reply->error() == QNetworkReply::NoError)
{
file->flush();
file->close();
delete file;
file=NULL;
}else
{
//处理错误
}
reply->deleteLater();//要删除reply,但是不能在repyfinished里直接delete,要调用deletelater;
reply = NULL;
accessManager->deleteLater();
accessManager = NULL;
downloadFinished();
}
void Download::onDownloadProgress(qint64 bytesSent, qint64 bytesTotal)
{
double pre = double(bytesSent)/bytesTotal;
emit progressPosition(pre);
}
void Download::onReadyRead()
{
file->write(reply->readAll());
}
调用httpdownload函数即可开始下载,我这里注册了一个单例,在qml中进行调用
//c++
QGuiApplication app(argc, argv);
QScopedPointer<Download> downloadSingletonInstance(new Download);
qmlRegisterSingletonInstance("downloadSingletonInstance", 1, 0, "Download", downloadSingletonInstance.get());
QQmlApplicationEngine engine;
//qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import downloadSingletonInstance 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Button{
text: "立即更新"
anchors.centerIn: parent
onClicked: {
Download.httpDownload()
}
}
}
直接运行发现文件打开失败,路径也没创建成功,
D libinstall_armeabi-v7a.so: mkpath: false
D libinstall_armeabi-v7a.so: setCurrent: false
D libinstall_armeabi-v7a.so: 文件打开失败
原因是没有赋予app存储权限,上次直接在手机上权限管理中允许了,然后就可以了,但是不可能让用户去手动操作,后来发现QtAndroid可以动态获取权限,需要在pro文件中添加androidextras,然后在构造函数中获取权限,这里顺便把安装apk的权限也加进去
//.pro
QT += quick androidextras
//download.h
#include <QtAndroid>
//download.c
Download::Download(QObject *parent)
: QObject{parent}
{
QStringList permission;
permission.append("android.permission.WRITE_EXTERNAL_STORAGE");
permission.append("android.permission.INSTALL_PACKAGES");
permission.append("android.permission.REQUEST_INSTALL_PACKAGES");
QtAndroid::requestPermissionsSync(permission);
}
再次运行,是不是好熟悉的界面,哈哈,原来就是这样搞出来的。
接下来文件就能成功保存了,然后就是写Java代码了
二,添加Java文件
直接按照网上的来。
(2条消息) Android代码安装apk程序_一只农民工的博客-优快云博客_android 安装apk代码
Qt/C++/Android - How to install an .APK file programmatically? - Stack Overflow
第一个是Android Studio的做法,第二个是qt的调用,我们需要根据这两个糅合我们自己的代码才行。
1,在qt工程中新建一个Java文件
我新建了个src文件夹,放到它下面,专门存放.java文件,文件放的位置跟要导入的包名有关系,
我这里不想再引入包名什么的,感觉java好麻烦,发现放到这个位置就不需要引入,就能像c用起来一样方便。
InstallAPK.java
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;
import android.support.v4.content.FileProvider;
public class InstallAPK
{
private Context context = this.context;
protected InstallAPK()
{
}
public int installApp(String appPackageName) {
if (QtNative.activity() == null)
return -1;
try {
File apk = new File(appPackageName);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri uri = FileProvider.getUriForFile(context,"org.qtproject.install.fileProvider", apk);
intent.setDataAndType(uri,"application/vnd.android.package-archive");
QtNative.activity().startActivity(intent);
return 0;
} catch (android.content.ActivityNotFoundException anfe) {
return -3;
}
}
}
代码都是东拼西凑的,也不是很懂,反正能运行就不动,哈哈哈
主要是FileProvider这个类,继承了ContentProvider这个类好像。
高版本提高了sdcard、 app文件空间的访问权限,高低版本的系统api有一定区别,Android7.0 及以上,开放(暴露)私有数据文件的唯一方式是通过 ContentProvider 来实现(我们的app提供我们的文件给系统安装程序)
2,AndroidManifest.xml 添加Provider
<provider android:name="android.support.v4.content.FileProvider" android:authorities="org.qtproject.install.fileProvider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
</provider>
android.support.v4.content.FileProvider就是调用的类库,FileProvider还有一个类库,androidx.core.content.FileProvider,我试过这个好像不能用,全是报错,应该是根据你使用的哪个支持库有关,我这里应该使用的是前者。android:authorities="org.qtproject.install.fileProvider"这个我也不知道是啥,注意这个要和上面Java中的参数一致FileProvider.getUriForFile(context,"org.qtproject.install.fileProvider", apk);原来这个地方是调用的context.getpackname,因为这里总是给我报错,我就删了,直接替换成字符串
还要再添加一下安装应用的权限,可以在qt提供的图形页面编辑,也可以在xml模式下编译
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
3.paths xml 配置
在res/xml下新建一个xml文件,文件名和上面的android:resource="@xml/filepaths"保持一致,即filepaths。里面定义的各种路径,懂的小伙伴可以自行修改
<?xml version='1.0' encoding='utf-8'?>
<paths>
<external-path
name="path1"
path="/data/dir1/" />
<external-path
name="path2"
path="/" />
<external-files-path
name="path3"
path="/data/dir2" />
<external-cache-path
name="path4"
path="/data/dir3" />
<cache-path
name="path5"
path="/data" />
<files-path
name="path6"
path="/ff" />
</paths>
全部添加完毕,先编译一下!
果然有报错。。。
这个就是前面提到的那个Android7以上需要的一个,对我们很重要的类,提示找不到这个包,我们把这个FileProvider.java文件放到src文件下,再编译一下就能成功了.这个文件我在电脑上搜不到,需要到网上去下载,我也会上传上去供大家下载。
三、打开apk文件
接下来我们就开始调用这个java文件,在下载完成信号后面运行Java的InstallApp函数
emit downloadFinished();
QAndroidJniObject intent("InstallAPK");
QAndroidJniObject jsText = QAndroidJniObject::fromString("/storage/emulated/0/Android/data/com.hznk/files/506787841apk.apk");
jint ret = intent.callMethod<jint>("installApp","(Ljava/lang/String;)I",jsText.object<jstring>());
编译运行,见证奇迹的时刻!
大功告成!