QtAndroid详解(3):startActivity实战Android拍照功能

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情

    在“QtAndroid详解(1):QAndroidJniObject”中,我们介绍了 QAndroidJniObject 这个 Qt JNI 的核心类,在“”中我们介绍了 startActivity 以及与它配套的一些 Android 背景知识,这次我们来看一个实例,演示如何使用 startActivity 来调用 Android 系统功能,同时也演示 QAndroidJniObject 的常见用法。

实例介绍

    先看下实例效果,然后再论。



    我们只是演示 API 用法,实例设计的很简单。

    第一排有个 “Get Info” 按钮,点击会获取 Qt Andorid App 所用的 Activity 的类名,如你所见,为 QtActivity 。除了 Activity 的类名,我还获取了当前 Activity 所对应的 taskId , inst 则对应于 Android Activity 的静态方法 —— getInstanceCount() 。

    第二排一个编辑框,一个 “Launch” 按钮。编辑框可以输入一个 action ,点击 Launch 按钮则调用这个 action 对应的活动。调用时不关心结果,没有提供 QAndroidActivityResultReceiver 。

    第三排是“Capture Image”按钮,点击它会调用系统的拍照功能来捕获一张图片,并将这张照片显示在界面上。这个功能,我使用 QAndroidActivityResultReceiver 来处理结果。因为拍照可能被取消。

源码分析

    介绍了示例的功能,现在来看代码吧。

    我的示例是基于 Qt Widgets 的,使用新建项目向导时,基类选择了 QWidget ,没有要 ui 文件。创建了一个 AndroidManifest.xml 文件,包名修改为 “an.qt.QtAndroid” ,添加了 android.permission.WRITE_EXTERNAL_STORAGE 和 android.permission.CAMERA 权限。详细的过程请参考“Qt on Android:图文详解Hello World全过程”和“Windows下Qt 5.2 for Android开发入门”这两篇文章吧,或者我的书《Qt on Android核心编程》。这里我们专注于 Qt JNI 部分的代码分析。

头文件widget.h

    头文件 widget.h 如下:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QLineEdit>
#include <QTextEdit>
#include <QLabel>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    void onLaunch();
    void onGetActivityInfo();
    void onCapture();

private:
    QLineEdit *m_actionEdit;
    QLabel*m_activityInfo;
    QLabel *m_capturedImage;
};

#endif // WIDGET_H

    m_actionEdit 用于输入 action 。

    m_activityInfo 是显示 Activity 类名等信息的。

    m_capturedImage 用来显示捕捉到的照片。

源文件 widget.cpp

    源文件 widget.cpp ,内容如下:

#include "widget.h"
#include <QtAndroid>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QDebug>
#include <QAndroidJniEnvironment>
#include <QAndroidActivityResultReceiver>
#include <QDateTime>
#include <QFile>
using namespace QtAndroid;

#define CHECK_EXCEPTION() \
    if(env->ExceptionCheck())\
    {\
        qDebug() << "exception occured";\
        env->ExceptionClear();\
    }

class ResultReceiver: public QAndroidActivityResultReceiver
{
public:
    ResultReceiver(QString imagePath, QLabel *view)
        : m_imagePath(imagePath), m_imageView(view)
    {

    }

    void handleActivityResult(
            int receiverRequestCode,
            int resultCode,
            const QAndroidJniObject & data)
    {
        qDebug() << "handleActivityResult, requestCode - " << receiverRequestCode
                    << " resultCode - " << resultCode
                    << " data - " << data.toString();
        //RESULT_OK == -1
        if(resultCode == -1 && receiverRequestCode == 1)
        {
            qDebug() << "captured image to - " << m_imagePath;
            qDebug() << "captured image exist - " << QFile::exists(m_imagePath);
            m_imageView->setPixmap(QPixmap(m_imagePath));
        }
    }

    QString m_imagePath;
    QLabel *m_imageView;
};

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QVBoxLayout *layout = new QVBoxLayout(this);

    QHBoxLayout *actInfoLayout = new QHBoxLayout();
    layout->addLayout(actInfoLayout);
    QPushButton *getBtn = new QPushButton("Get Info");
    connect(getBtn, SIGNAL(clicked()), this, SLOT(onGetActivityInfo()));
    actInfoLayout->addWidget(getBtn);
    m_activityInfo = new QLabel();
    m_activityInfo->setWordWrap(true);
    actInfoLayout->addWidget(m_activityInfo, 1);
    layout->addSpacing(2);

    QHBoxLayout *actionLayout = new QHBoxLayout();
    layout->addLayout(actionLayout);
    m_actionEdit = new QLineEdit("android.settings.SETTINGS");
    actionLayout->addWidget(m_actionEdit, 1);
    QPushButton *launchBtn = new QPushButton("Launch");
    connect(launchBtn, SIGNAL(clicked()), this, SLOT(onLaunch()));
    actionLayout->addWidget(launchBtn);
    layout->addSpacing(2);

    QPushButton *capture = new QPushButton("CaptureImage");
    connect(capture, SIGNAL(clicked()), this, SLOT(onCapture()));
    layout->addWidget(capture);
    m_capturedImage = new QLabel();
    m_capturedImage->setScaledContents(true);
    layout->addWidget(m_capturedImage, 1);
}

Widget::~Widget()
{

}

void Widget::onLaunch()
{
    QAndroidJniObject action = QAndroidJniObject::fromString(m_actionEdit->text());
    QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());
    startActivity(intent, 0);
    QAndroidJniEnvironment env;
    CHECK_EXCEPTION()
}

void Widget::onGetActivityInfo()
{
    QAndroidJniEnvironment env;

    QAndroidJniObject activity = androidActivity();
    QAndroidJniObject className =
            activity.callObjectMethod<jstring>("getLocalClassName");
    CHECK_EXCEPTION()
    QString name = className.toString();
    int index = name.lastIndexOf('.');
    if(index != -1)
    {
        name = name.mid(index + 1);
    }

    jint taskId = activity.callMethod<jint>("getTaskId");
    CHECK_EXCEPTION()
    jlong instanceCount = QAndroidJniObject::callStaticMethod<jlong>(
                "android/app/Activity",
                "getInstanceCount"
                );
    CHECK_EXCEPTION()

    QString activityInfo = QString("%1,task-%2,inst-%3")
            .arg(name).arg(taskId).arg(instanceCount);

    m_activityInfo->setText(activityInfo);
    m_activityInfo->adjustSize();
}

void Widget::onCapture()
{
    QAndroidJniEnvironment env;

    //constuct Intent for IMAGE_CAPTURE
    QAndroidJniObject action = QAndroidJniObject::fromString(
                "android.media.action.IMAGE_CAPTURE");
    QAndroidJniObject intent("android/content/Intent",
                             "(Ljava/lang/String;)V",
                             action.object<jstring>());

    //setup saved image location
    QString date = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
    QAndroidJniObject fileName = QAndroidJniObject::fromString(date + ".jpg");
    QAndroidJniObject savedDir = QAndroidJniObject::callStaticObjectMethod(
                "android/os/Environment",
                "getExternalStorageDirectory",
                "()Ljava/io/File;"
                );
    CHECK_EXCEPTION()
    qDebug() << "savedDir - " << savedDir.toString();
    QAndroidJniObject savedImageFile(
                "java/io/File",
                "(Ljava/io/File;Ljava/lang/String;)V",
                savedDir.object<jobject>(),
                fileName.object<jstring>());
    CHECK_EXCEPTION()
    qDebug() << "savedImageFile - " << savedImageFile.toString();
    QAndroidJniObject savedImageUri =
            QAndroidJniObject::callStaticObjectMethod(
                "android/net/Uri",
                "fromFile",
                "(Ljava/io/File;)Landroid/net/Uri;",
                savedImageFile.object<jobject>());
    CHECK_EXCEPTION()

    //tell IMAGE_CAPTURE the output location
    QAndroidJniObject mediaStoreExtraOutput
            = QAndroidJniObject::getStaticObjectField(
                "android/provider/MediaStore",
                "EXTRA_OUTPUT",
                "Ljava/lang/String;");
    CHECK_EXCEPTION()
    qDebug() << "MediaStore.EXTRA_OUTPUT - " << mediaStoreExtraOutput.toString();
    intent.callObjectMethod(
                "putExtra",
                "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;",
                mediaStoreExtraOutput.object<jstring>(),
                savedImageUri.object<jobject>());

    //launch activity for result
    ResultReceiver *resultReceiver =
            new ResultReceiver(savedImageFile.toString(), m_capturedImage);
    startActivity(intent, 1, resultReceiver);
    CHECK_EXCEPTION()
}

    构造函数不说了吧,都是使用 Qt Widgets 编程的老相识了。我们就说其它的几部分:

  • onLaunch(),启动活动
  • onGetActivityInfo(),获取活动信息
  • onCapture(),调用 Android 拍照功能
  • ResultReceiver类,处理返回结果

    其实代码本身很简单,只是牵涉到 JNI 和 Java 类库,C++的朋友可能不熟悉,所以我们展开讲一下。

    提一下,在 widget.cpp 开始,我使用 "using namespace QtAndroid" 语句引入了 QtAndroid 名字空间,否则后面在调用 startActivity() 等方法时就要用 "QtAndroid::startActivity()"这种形式。

onLaunch()

    onLaunch() 是一个槽,点击 "Launch" 按钮会被调用,信号与槽的连接是在 Widget 的构造函数完成的。

    onLaunch() 代码如下:

    QAndroidJniObject action = QAndroidJniObject::fromString(m_actionEdit->text());
    QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());
    startActivity(intent, 0);
    QAndroidJniEnvironment env;
    CHECK_EXCEPTION()

    只有 5 行,不过这是使用 startActivity 不计后果的调用一个 Android Activity 的通用做法。

    第1行从界面上的编辑框里获取 action 对应的字符串。默认是 “android.settings.SETTINGS” ,对应 Android 系统的设置界面。这里我使用 QAndroidJniObject::fromString() 这个方便的静态方法直接构造一个 JNI 对象。

    第2行代码构造了一个 Intent 对象,这里使用的QAndroidJniObject的构造函数,原型如下:

QAndroidJniObject(const char * className, const char * signature, ...);

    如你所见,第一个参数为类名,第二个参数为方法签名,第三个及以后的参数对应 Java 方法的参数。我们分别来解释第2行代码里的三个参数。

    第1个参数是 Java 中 Intent 类的全路径类名在 JNI 中的字符串表示。Intent 的全路径类名是 android.content.Intent ,对应的字符串表示为“android/content/Intent”,“.”替换为“/”。

    第2个参数指定使用 Intent 构造函数中的 Intent(String action) ,这个版本的构造函数接受一个字符串形式的 action ,它的方法签名是“(Ljava/lang/String;)V”,注意构造函数没有返回值,方法签名中以 VOID 来表示。

    第3个参数则是要传递给 Intent 构造函数的参数, Java 类型为 String ,我们要将第 1 行代码得到得 QAndroidJniObject 对象转换为实际的 JNI 对象(QAndroidJniObject是 Qt 对实际 JNI 对象的封装或代理),这是通过调用object()方法实现的。object()是模板方法,模版参数是 JNI 类型,这里是 jstring ,所以代码为 action.object<jstring>() 。

    第3行代码就比较简单了,我们不关心结果,所以只传递了第1个参数——intent,第2个参数传0,第3个参数有默认值(NULL),我们没传递。

    OK,到这里为止,如何构造对应于 Android Intent 的 QAndroidJniObject 对象,如果传递给 startActivity() 来启动一个活动就介绍清楚了。

    第 4 、5 两行代码,实际上是检查 JNI 调用是否发生了异常,如果发生异常,就清理掉异常,否则程序就会卡死在那里。

    检查异常需要 QAndroidJniEnvironment 类,构造这个类的实例时,会自动关联到当前线程的 JNI 环境。然后我们就可以调用 JNIEnv 的方法 ExceptionCheck() 和 ExceptionClear() 来检查和清理异常了。因为示例中多处用到(每次 JNI 调用都需要检查异常),我定义了一个名为 CHECK_EXCEPTION 的宏:

#define CHECK_EXCEPTION() \
    if(env->ExceptionCheck())\
    {\
        qDebug() << "exception occured";\
        env->ExceptionClear();\
    }

    好啦,这个简单的函数终于结束了啊,解释它们比代码本身费劲多了去了,我天,用函数来描述代码,真个是词不达意呀。所以有一种说法,好的代码是自解释的。所以还有一种说法是,代码即文档。

onGetActivityInfo()

    QtAndroid名字空间有一个 androidActivity() 方法,可以获取到 Qt on Android App 使用的 Android Activity 对象。有了 Activity 对应的 JNI 对象,我们就可以通过 QAndroidJniObject 提供的方法来访问 Activity 的一些方法和属性了, onGetActivityInfo() 这个槽就是这么做的。

    首先我们调用 androidActivity() 获取了一个 QAndroidJniObject 对象,代表 Android 框架里的 Activity 。 Android Activity 类有一个方法 getLocalClassName() ,可以获取 Activity 对应的 Java 类的类名。使用 JNI 得到类名的代码如下:

    QAndroidJniObject className =
            activity.callObjectMethod<jstring>("getLocalClassName");

    我使用 QAndroidJniObject 的 QAndroidJniObject callObjectMethod(const char * methodName) const 方法,这个方法是模板方法,模板参数对应 Java 方法的返回值类型。Android Activity 的 getLocalClassName() 返回的是 String ,JNI 类型为 jstring ,所以 C++ 代码就写成上面的样子。

    说一下 QAndroidJniObject 的 toString() 方法,它可以生成一个 JNI 对象的字符串描述,对于类型为 String(jstring) 的 JNI 对象,返回的就是字符串本身的值,这也是一种方便的、将jstring转换为 QString 的方式;对于非 String(jstring)类型的 JNI 对象,toString() 会调用对应 Java 类的 toString() 方法,返回的具体是什么字符串,就看 Java 类怎么实现 toString() 了,比如 Java 的 File 类,toString() 就会返回文件名(也可能带路径)。

    要调用一个 Java (JNI)对象的实例方法,需要先有实例,Qt 5.3 以后,我们不用重写 QtActivity 就可以获取到 Qt App 对应的 Android Activity 实例喽,就可以直接调用这个实例的方法来获取一些信息。比如我获取 Activity 对应的 taskId ,就使用 QAndroidJniObject 的 callMethod() 这个模版方法:

jint taskId = activity.callMethod<jint>("getTaskId");

    用起来很简单吧。

    为了演示 QAndroidJniObject 的静态方法 T  callStaticMethod(const char * className, const char * methodName) ,我专门看了下 android.app.Activity 的 API ,返现 getInstanceCount() 方法符合要求,于是就在 onGetActivityInfo() 槽里加入了相关代码:

    jlong instanceCount = QAndroidJniObject::callStaticMethod<jlong>(
                "android/app/Activity",
                "getInstanceCount"
                );

    这部分代码没什么实际意义,仅仅是演示。不过可以看到 Qt App 对应的 Java 类名为 QtActivity ,这点我在《 Qt on Android核心编程》一书中也做了介绍,并且详细分析了 Qt 怎样和 Android Activity 衔接,感兴趣的朋友可以参考。

onCapture()

    终于要说到最复杂的槽——onCapture()——了。

    先说一下 Android 上使用相机捕捉图片的事儿。要捕获图片,需要调用 android.media.action.IMAGE_CAPTURE ,在调用 IMAGE_CAPTURE 时,还可以给它传递一个 EXTRA_OUTPUT 来指定图片的存储位置。IMAGE_CAPTURE 会打开相机来拍照,并且根据操作结果设置 Activity 的 result 。当你返回键,或者拍照完成后点击拍照界面上的取消按钮时,result code 被设置为 RESULT_FAILED ,当你点击完成按钮时,拍到的图片会被保存到指定的位置并且 result code 被设置为 RESULT_OK 。

    我在 onCapture() 里,就是根据 IMAGE_CAPTURE 的这种行为来写 JNI 调用代码。

    第一件事儿是构造一个 Intent ,代码如下:

    QAndroidJniObject action = QAndroidJniObject::fromString(
                "android.media.action.IMAGE_CAPTURE");
    QAndroidJniObject intent("android/content/Intent",
                             "(Ljava/lang/String;)V",
                             action.object<jstring>());

    在讲解 onLaunch() 时已经看过类似的代码,跳过吧。

    我希望把图片保存到外置存储目录下,所以接下来就是构造保存路径:

    QString date = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
    QAndroidJniObject fileName = QAndroidJniObject::fromString(date + ".jpg");
    QAndroidJniObject savedDir = QAndroidJniObject::callStaticObjectMethod(
                "android/os/Environment",
                "getExternalStorageDirectory",
                "()Ljava/io/File;"
                );
    CHECK_EXCEPTION()
    qDebug() << "savedDir - " << savedDir.toString();
    QAndroidJniObject savedImageFile(
                "java/io/File",
                "(Ljava/io/File;Ljava/lang/String;)V",
                savedDir.object<jobject>(),
                fileName.object<jstring>());
    CHECK_EXCEPTION()
    qDebug() << "savedImageFile - " << savedImageFile.toString();
    QAndroidJniObject savedImageUri =
            QAndroidJniObject::callStaticObjectMethod(
                "android/net/Uri",
                "fromFile",
                "(Ljava/io/File;)Landroid/net/Uri;",
                savedImageFile.object<jobject>());
    CHECK_EXCEPTION()

    代码中先根据当前日期设置图片文件名,然后通过 QAndroidJniObject::callStaticObjectMethod() 调用 Android android.os.Environment 类的 getExternalStorageDirectory() 方法来获取外部存储的目录。 getExternalStorageDirectory() 没有参数,返回值为 Java String 对象,所以其方法签名为 “()Ljava/io/File;” 。

    在 Android 上,/data 分区被视为内部存储,应用一般会被安装到 /data/data 目录下,而外部存储实际上又分为两部分,内置 SD 分区和扩展 SD 分区。内置 SD 分区是从 FLASH 中划分出的一块存储区域,一般挂载到 /mnt/sdcard 目录下。扩展 SD 分区,则是你手机上插入的 microSD 之类的存储卡所对应的分区。因为现在很多 Android 系统都会从 FLASH 上划分一块作为内置 SD 使用,所以 Environment 类的 getExternalStorageDirectory() 方法获取到的,一般是内置 SD 分区,不是 microSD 这类外部存储卡对应的分区。

    在 Java 里,目录、文件都通过 java.io.File 类来表示。我们获取到的外部存储目录,也是一个 File 对象。 File 类有一个构造函数 File(File dir, String fileName) ,可以创建一个 File 对象,代表 dir 对象所指定的目录下的 fileName 文件。 File 对象的 toString() 方法会返回它所代表的文件的路径。

    代码里的 savedImageFile 对象,使用 “(Ljava/io/File;Ljava/lang/String;)V”作为 File 类构造函数的签名,然后用 savedDir.object<jobject>() 获得外部存储目录对应的 JNI 对象来传递给构造函数。

    在我们通过 Intent 调用 IMAGE_CAPTURE 时,要指定的存储位置,是通过 android.net.Uri 类表示的,因此我又调用 Uri 的静态方法 fromFile() 创建了一个 Uri 对象,fromFile()只有一个类型为 File 的入参,返回值则是 Uri ,因此方法签名为“(Ljava/io/File;)Landroid/net/Uri;”。

    也许你注意到在使用 QAndroidJniObject 的 callStaticObjectMethod() 方法,给 Java 类传参时,多次用到 xxx.object<jobject>() 这种调用。还记得之前我们将 QAndroidJniObject 转为 jstring 的代码是 xxx.object<jstring> ,那转换代表 JNI File 对象的 QAndroidJniObject 对象时为何不使用 xxx.object<jfile>() 呢?这是因为,在 JNI 的类型系统里,Java String 作为基础类型,而其它的对象类型,统一都使用 jobject 来表示。因此以后我们不管拿到什么样的 QAndroidJniObject 对象,只要是代表一个非 String 类型的 Java 对象,想转换为实际的 JNI 对象,都用 xxx.object<jobject>() 这种方式。我们得到的 Uri 对象,在传递给 Intent 时就是这么用的。

    创建好表示存储路径的 Uri 对象后,就是如何传递给 Intent 了。代码如下:

    QAndroidJniObject mediaStoreExtraOutput
            = QAndroidJniObject::getStaticObjectField(
                "android/provider/MediaStore",
                "EXTRA_OUTPUT",
                "Ljava/lang/String;");
    CHECK_EXCEPTION()
    qDebug() << "MediaStore.EXTRA_OUTPUT - " << mediaStoreExtraOutput.toString();
    intent.callObjectMethod(
                "putExtra",
                "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;",
                mediaStoreExtraOutput.object<jstring>(),
                savedImageUri.object<jobject>());

    Android Intent 类提供了一系列的 putExtra 方法,让我们能够把类型为 int 、 float 、 String 、int[] 、double 、char 、 byte 等等的数据存储在 Intent 实例中,带给被调用方。被调用方可以用 getExtra 方法获取到发起调用一方传递的数据。这就是使用 Intent 传递数据的一般用法。 Intent 还有一个 putExtras方法,可以传递一个复杂数据对象——Bundle,如果你要传递的数据很多,就可以把数据组合为一个 Bundle 。

    要在两个组件之间传递数据,就要有个约定,因为传递的数据可能不知一个,就需要给每个数据起个名字,这就是 key ,putExtra() 方法的第一个参数类型为字符串,用于指定数据的 key ,接收 Intent 的一方可以根据这个 key 把数据取出来。

    那么,问题来了,接收方怎么知道你传递了什么数据、key是什么呢?当然,答案很简单,你告诉人家就行了嘛。或者人家傲娇地声明自己只接受某某某类型、key为xxx的数据,那你在调用时按人家要求做也就是了。

    对于 IMAGE_CAPTURE ,它接受的数据是 Uri ,对应的 key 则为 android.provider.MediaStore.EXTRA_OUTPUT ,是 MediaStore 类的静态成员变量。因为我在代码里就先调用 QAndroidJniObject::getStaticObjectField() 方法获取到 EXTRA_OUTPUT 。这里又得岔开来讲一个喽。

    QAndroidJniObject 不但提供了调用 Java 方法的接口,还提供了访问和修改 Java 属性的接口。 getStaticObjectField 这一系列的重载方法,就是访问 Java 类的静态成员变量的;而 getObjectField 那一系列的重载方法,则是访问 Java 类的实例成员变量(这种成员变量,每个实例都有一份)的。

    在访问 Java 类成员变量时,需要变量名字和变量签名。比如获得 EXTRA_OUTPUT 的代码,传递的 fieldName 为 “EXTRA_OUTPUT” ,传递的签名为 “Ljava/lang/String;” ,表示 EXTRA_OUTPUT 是一个类型为 String 的静态成员。

    得到了 EXTRA_OUTPUT ,调用 Intent 的 putExtra 就可能把 savedImageUri 存入 Intent (关联的key为 EXTRA_OUTPUT)。我调用的 putExtra 方法原型为:

public Intent putExtra (String name, Parcelable value);

    Android 提供了一个轻量级、高效率的进程间通信机制: Parcel 。使用 Parcel 通信时,交互的数据需要序列化,因此 Android 设计了 Parcelable 接口,方便传递数据。你想传递的数据,只要实现 Parcelable 接口和特定的模板方法即可。我们用到的 Uri ,就是 Parcelable 的子类。也因此我选择了上面的 putExtra() 方法。

    要想 HIGH ,前戏必须做足。是吧,告诉了 Intent 要传递的数据,就可以调用 startActivity 了:

    ResultReceiver *resultReceiver =
            new ResultReceiver(savedImageFile.toString(), m_capturedImage);
    startActivity(intent, 1, resultReceiver);

    注意,调用拍照,我们是很关心结果的哦,所以呢,我创建了一个 QAndroidActivityResultReceiver 对象,传递给 startActivity ,同时也指定 requestCode 为 1。


    好啦,吧啦吧啦这么多,好歹讲差不多了。其实代码没几行……关键是,你要在 Qt 里用 JNI ,要正确使用 QAndroidJniObject 和 startActivity ,就需要了解 Java 类库,诸如类名、功能,有什么属性什么方法,都得研究下,才能写出可用的 JNI 代码来。额滴神呢,好有趣!

ResultReceiver

    前面我讲到调用拍照的 IMAGE_CAPTURE 时,给 startActivity() 方法传递了一个 resultReceiver ,类型是 ResultReceiver 。 ResultReceiver 从 QAndroidActivityResultReceiver 继承而来,实现了 handleActivityResult 方法。代码如下:

class ResultReceiver: public QAndroidActivityResultReceiver
{
public:
    ResultReceiver(QString imagePath, QLabel *view)
        : m_imagePath(imagePath), m_imageView(view)
    {

    }

    void handleActivityResult(
            int receiverRequestCode,
            int resultCode,
            const QAndroidJniObject & data)
    {
        qDebug() << "handleActivityResult, requestCode - " << receiverRequestCode
                    << " resultCode - " << resultCode
                    << " data - " << data.toString();
        //RESULT_OK == -1
        if(resultCode == -1 && receiverRequestCode == 1)
        {
            qDebug() << "captured image to - " << m_imagePath;
            qDebug() << "captured image exist - " << QFile::exists(m_imagePath);
            m_imageView->setPixmap(QPixmap(m_imagePath));
        }
    }

    QString m_imagePath;
    QLabel *m_imageView;
};

    ResultReceiver 的构造函数有两个参数,imagePath 代表图片路径,view 是一个 QLabel ,用来显示抓拍的图片。

    handleActivityResult 方法在 requestCode 为 1 并且 resultCode 为 RESULT_OK 时更新 view ,显示图片。

    我的示例对结果的处理就这么简单。

    如果你想传递更复杂的数据,可以通过 Intent ,也就是说了,被调用的 Activity ,也可以传递一个 Intent 给调用那一方。你看,Android Activity 的 onActivityResult() 方法原型如下:

void onActivityResult(int requestCode, int resultCode, Intent data);

    这与我们的 handleActivityResult 方法是对应的,第三个参数,类型就是 Intent 。

    当你这么做的时候,通常需要写 Java 代码来实现能使用 Intent 传递数据的组件,你可以通过下面的方法把 Intent 传递给调用那方:

void setResult(int resultCode, Intent data);

    Android 这种通信和传递数据的方式,还是挺方便的。


-----------------

K.O. !终于结束了,一个使用Qt JNI的简单之极的例子,费了我三升口水才讲完……

    

------------

    回顾一下:



Android 11适配指南之系统相机拍照、打开相册,安卓app开发教程 Android 6 权限适配 Android 7 文件适配 Android 10/11 存储适配 ok,接下来以一个更换头像的小例子来讲解一下。 示例 ============================================================= 点击头像,然后弹窗,给出不同的选项,执行不同的操作。 mBinding.llImg.setOnClickListener { TakeImage 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目.. 阅读详情

相关推荐

QT配置android环境,简单易懂

安装软件 软件百度云链接:https://pan.baidu.com/s/1gqz0Ca4r_TcVjbXdmvZimA 提取码:r4p1 1.Qt Creator for andorid 安装qtcreator最好是安装qt for android版本,否则可能使用其它版本的qtcreator配置android环境时出现检测不到android套件的情况。 我安装的是qt creator5.7版本,安装方法没啥讲究,基本默认,把安装路径改一改,安装的组件也可以默认,不放心的可以把android的都选上。

Arcofcosmos的博客 1万+

适配AndroidQ调用系统相机拍照后保存到相册并显示在界面上

最近一直抽空在做仿微信朋友圈的功能,在点击加号选择选择拍摄进入相机拍照并将图片显示出来,因为版本迭代(Android11马上也就出来了),所以需要进行版本适配,所以在这里进行记录一下,方便以后查看和小伙伴们参考 先来一波效果图: 微信的效果: 布局就是一个简单的按钮以及一个图片,当我们点击按钮进入系统相册中,点击拍照,然后在onActivityResult()方法回调中对图片进行处理,让其显示在界面上 主要代码: public class MainActivity extends AppCompatActivity implements View.OnClickListener {

QtAndroid详解 3 startActivity实战Android拍照功能

QtAndroid详解 3 startActivity实战Android拍照功能

jfdfhh的博客 527

Qt 实现android camera摄像头的preview和拍照

折腾了几天,终于有结果了,Qtandroid开发的支持还可以,就是Qt进行android开发没有anroid studio主流,用Qt进行android开发,基本都是在写C++ ,还蛮棒的。 网上找了好一会才找到参考文献5,问题得到解决。 文件目录如下: 完整代码如下: camera_widgets_display.pro QT += core gui multimedia multimediawidgets greaterThan(QT_MAJOR_VERSION, 4):

不解不惑 4492

QtAndroid详解(2)startActivity和它的小伙伴们

研究如何使用 QtAndroidstartActivity 方法,介绍使用 startActivity 方法的背景知识……

安晓辉生涯——聚焦程序员的职业规划与成长 1万+

4、QAndroidJniObject实战Android拍照、录像功能

对于 IMAGE_CAPTURE ,它接受的数据是 Uri ,对应的 key 则为 android.provider.MediaStore.EXTRA_OUTPUT ,是 MediaStore 类的静态成员变量。要在两个组件之间传递数据,就要有个约定,因为传递的数据可能不知一个,就需要给每个数据起个名字,这就是 key ,putExtra() 方法的第一个参数类型为字符串,用于指定数据的 key ,接收 Intent 的一方可以根据这个 key 把数据取出来。的,因此我又调用 Uri 的。

qq_39075859的博客 1260

QtAndroid详解 2 startActivity和它的小伙伴们

QtAndroid详解 2 startActivity和它的小伙伴们

jfdfhh的博客 926

Qt for android 新旧版本的 拍照问题

【代码】Qt for android 新旧版本的 拍照问题。

443

启动另一app——Qt for Android

intent中文名字叫做意图,是一个将要执行的动作的抽象的描述,简单来说就是穿的消息,由Intent协助完成各个组件之间的通讯。由于我不太熟悉安卓的框架,所以也没办法做更多的讲解,故直接展示可行的代码。c++代码中直接调用。

xiaopei_yan的博客 842

Qt on Android: Qt 5.3.0 公布,针对 Android 改进的说明

    5月20日本,Qt 官方博客宣布 Qt 5.3.0 公布!    这个版本号聚焦在性能、稳定性和可用性的提升上,与 5.1 / 5.2 相比有非常大提升。    5.3.0 的主要变化:稳定能、可用性大大提升Qt for Windows Runtime BetaOfficial support for QNX 6.6 and QNX 6.5 SP1引入的新特性QQuickWidget,...

weixin_34419321的博客 134

QtAndroid开发中的跨平台实践与优化

跨平台开发框架是现代软件开发的重要技术,其中Qt作为成熟的C++框架,通过统一的代码库支持多平台原生应用构建。其核心原理在于抽象层设计,将平台差异封装在框架内部,使开发者能专注于业务逻辑实现。在移动开发领域,Qt on Android结合了Qt的跨平台优势与Android系统特性,特别适合需要同时覆盖桌面和移动端的项目。技术价值体现在开发效率提升、代码复用率提高以及维护成本降低等方面。实际应用场景包括工业控制、嵌入式设备、图形密集型应用等。本文重点探讨环境配置、平台特性适配、性能优化等实战经验,特别是针对A

weixin_33976072的博客 547

qt for android 调用java代码 实现自动更新APP

用的是qt 5.11.1   1:新建一个qt项目 2:找到项目---构建和运行----Build Android APK---- Create Templates  ,点击,生成 android文件。 3:打开Visual Studio Code新建文件,保存为java文件,文件名为mmActivity(名字就这个,等你会了自己再命名),将以下代码复制进去保存。保存的时候要用...

u012230798的博客 3020

android 调用系统相机或者系统相册功能时,onActivityResult方法不执行问题的解决过程

我的情况:我自定义了一个Activity继承自FragementActivity,在里边使用一下代码 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(intent,4050); 启动系统相机功能。但是,当返回当前Activity时,系统并没有调用onActiv

想飞的企鹅 3642

Android Activity之间跳转出现短暂黑屏的处理方法

转载自:https://my.oschina.net/u/1244156/blog/261616       在默认情况下,Android应用程序启动时,会有一个黑屏的时期,原因是,首个activity会加载一些数据,比如初 始化列表数据、向服务器发送请求获取数据等等。同样,使用startActivity(intent)方法从一个Activity进入到新的 Activit

u012149181的博客 4546

Qt应用开发(安卓)——Hello Qt On Android

这一篇从实际出发,讲述如何创建、编译和部署Qt On Android项目。

波塞冬的博客 1万+

Qt中调用相机实现拍照

因此,在Qt中调用相机实现拍照并实时显示通常需要创建一个单独的线程用于相机采集,然后通过定时器控制画面刷新频率,以实现流畅的实时预览效果。在线程内部,可以使用信号槽机制将采集到的图像传递给主线程,然后在主线程里更新显示区域。通常情况下,相机采集图像的操作是一个阻塞式操作,即调用相机采集函数后必须等待采集到图像后才能进行下一步操作。如果在主线程里执行相机采集操作,会导致界面卡顿,无法实现实时预览。因此,常见的做法是将相机采集操作放在一个单独的线程中执行,以避免阻塞主线程。

温故而知新,可以为师矣。 839

Qt For Android | QT安卓开发环境搭建

Qt For Android QT安卓开发环境搭建 前言 Qt For Android在最初搭建环境时走了许多弯路,这里我将我的配置的过程分享出来,希望能帮大家快速搭建起开发环境,这里我以Qt 5.14.2为例进行介绍。 一、准备 1.Qt 5.14.2 2.android-ndk-r21 3.android-sdk 4.jdk-8u201 注意如果你的Qt版本不是5.14.2,那么一定要选择与Qt版本匹配的ndk,否则可能无法成功配置,我会将这些文件下载链接放在我的网站上供下载。

小翔的博客 1万+
上一篇: QtAndroid详解(2):startActivity和它的小伙伴们
下一篇: QtAndroid详解(4):JNI调用Android系统功能(1)
foruok
foruok 领域专家: C/C++技术领域 领域专家: C/C++技术领域
博客等级 码龄21年 2万+粉丝 581原创
评论 41
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

foruok

你可以选择打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值