Qt for Android10 自动更新app,安装apk包

捣腾了两天终于捣鼓出来了,记录一下踩坑过程。

先说下原理,第一步就是从网络上获取更新包,这一步不难,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程序_一只农民工的博客-CSDN博客_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>());

编译运行,见证奇迹的时刻!

大功告成! 

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值