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

在“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 如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #ifndef WIDGET_H  
  2. #define WIDGET_H  
  3.   
  4. #include <QWidget>  
  5. #include <QLineEdit>  
  6. #include <QTextEdit>  
  7. #include <QLabel>  
  8.   
  9. class Widget : public QWidget  
  10. {  
  11.     Q_OBJECT  
  12.   
  13. public:  
  14.     Widget(QWidget *parent = 0);  
  15.     ~Widget();  
  16.   
  17. public slots:  
  18.     void onLaunch();  
  19.     void onGetActivityInfo();  
  20.     void onCapture();  
  21.   
  22. private:  
  23.     QLineEdit *m_actionEdit;  
  24.     QLabel*m_activityInfo;  
  25.     QLabel *m_capturedImage;  
  26. };  
  27.   
  28. #endif // WIDGET_H  

    m_actionEdit 用于输入 action 。

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

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

源文件 widget.cpp

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include "widget.h"  
  2. #include <QtAndroid>  
  3. #include <QPushButton>  
  4. #include <QHBoxLayout>  
  5. #include <QVBoxLayout>  
  6. #include <QDebug>  
  7. #include <QAndroidJniEnvironment>  
  8. #include <QAndroidActivityResultReceiver>  
  9. #include <QDateTime>  
  10. #include <QFile>  
  11. using namespace QtAndroid;  
  12.   
  13. #define CHECK_EXCEPTION() \  
  14.     if(env->ExceptionCheck())\  
  15.     {\  
  16.         qDebug() << "exception occured";\  
  17.         env->ExceptionClear();\  
  18.     }  
  19.   
  20. class ResultReceiver: public QAndroidActivityResultReceiver  
  21. {  
  22. public:  
  23.     ResultReceiver(QString imagePath, QLabel *view)  
  24.         : m_imagePath(imagePath), m_imageView(view)  
  25.     {  
  26.   
  27.     }  
  28.   
  29.     void handleActivityResult(  
  30.             int receiverRequestCode,  
  31.             int resultCode,  
  32.             const QAndroidJniObject & data)  
  33.     {  
  34.         qDebug() << "handleActivityResult, requestCode - " << receiverRequestCode  
  35.                     << " resultCode - " << resultCode  
  36.                     << " data - " << data.toString();  
  37.         //RESULT_OK == -1  
  38.         if(resultCode == -1 && receiverRequestCode == 1)  
  39.         {  
  40.             qDebug() << "captured image to - " << m_imagePath;  
  41.             qDebug() << "captured image exist - " << QFile::exists(m_imagePath);  
  42.             m_imageView->setPixmap(QPixmap(m_imagePath));  
  43.         }  
  44.     }  
  45.   
  46.     QString m_imagePath;  
  47.     QLabel *m_imageView;  
  48. };  
  49.   
  50. Widget::Widget(QWidget *parent)  
  51.     : QWidget(parent)  
  52. {  
  53.     QVBoxLayout *layout = new QVBoxLayout(this);  
  54.   
  55.     QHBoxLayout *actInfoLayout = new QHBoxLayout();  
  56.     layout->addLayout(actInfoLayout);  
  57.     QPushButton *getBtn = new QPushButton("Get Info");  
  58.     connect(getBtn, SIGNAL(clicked()), this, SLOT(onGetActivityInfo()));  
  59.     actInfoLayout->addWidget(getBtn);  
  60.     m_activityInfo = new QLabel();  
  61.     m_activityInfo->setWordWrap(true);  
  62.     actInfoLayout->addWidget(m_activityInfo, 1);  
  63.     layout->addSpacing(2);  
  64.   
  65.     QHBoxLayout *actionLayout = new QHBoxLayout();  
  66.     layout->addLayout(actionLayout);  
  67.     m_actionEdit = new QLineEdit("android.settings.SETTINGS");  
  68.     actionLayout->addWidget(m_actionEdit, 1);  
  69.     QPushButton *launchBtn = new QPushButton("Launch");  
  70.     connect(launchBtn, SIGNAL(clicked()), this, SLOT(onLaunch()));  
  71.     actionLayout->addWidget(launchBtn);  
  72.     layout->addSpacing(2);  
  73.   
  74.     QPushButton *capture = new QPushButton("CaptureImage");  
  75.     connect(capture, SIGNAL(clicked()), this, SLOT(onCapture()));  
  76.     layout->addWidget(capture);  
  77.     m_capturedImage = new QLabel();  
  78.     m_capturedImage->setScaledContents(true);  
  79.     layout->addWidget(m_capturedImage, 1);  
  80. }  
  81.   
  82. Widget::~Widget()  
  83. {  
  84.   
  85. }  
  86.   
  87. void Widget::onLaunch()  
  88. {  
  89.     QAndroidJniObject action = QAndroidJniObject::fromString(m_actionEdit->text());  
  90.     QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());  
  91.     startActivity(intent, 0);  
  92.     QAndroidJniEnvironment env;  
  93.     CHECK_EXCEPTION()  
  94. }  
  95.   
  96. void Widget::onGetActivityInfo()  
  97. {  
  98.     QAndroidJniEnvironment env;  
  99.   
  100.     QAndroidJniObject activity = androidActivity();  
  101.     QAndroidJniObject className =  
  102.             activity.callObjectMethod<jstring>("getLocalClassName");  
  103.     CHECK_EXCEPTION()  
  104.     QString name = className.toString();  
  105.     int index = name.lastIndexOf('.');  
  106.     if(index != -1)  
  107.     {  
  108.         name = name.mid(index + 1);  
  109.     }  
  110.   
  111.     jint taskId = activity.callMethod<jint>("getTaskId");  
  112.     CHECK_EXCEPTION()  
  113.     jlong instanceCount = QAndroidJniObject::callStaticMethod<jlong>(  
  114.                 "android/app/Activity",  
  115.                 "getInstanceCount"  
  116.                 );  
  117.     CHECK_EXCEPTION()  
  118.   
  119.     QString activityInfo = QString("%1,task-%2,inst-%3")  
  120.             .arg(name).arg(taskId).arg(instanceCount);  
  121.   
  122.     m_activityInfo->setText(activityInfo);  
  123.     m_activityInfo->adjustSize();  
  124. }  
  125.   
  126. void Widget::onCapture()  
  127. {  
  128.     QAndroidJniEnvironment env;  
  129.   
  130.     //constuct Intent for IMAGE_CAPTURE  
  131.     QAndroidJniObject action = QAndroidJniObject::fromString(  
  132.                 "android.media.action.IMAGE_CAPTURE");  
  133.     QAndroidJniObject intent("android/content/Intent",  
  134.                              "(Ljava/lang/String;)V",  
  135.                              action.object<jstring>());  
  136.   
  137.     //setup saved image location  
  138.     QString date = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");  
  139.     QAndroidJniObject fileName = QAndroidJniObject::fromString(date + ".jpg");  
  140.     QAndroidJniObject savedDir = QAndroidJniObject::callStaticObjectMethod(  
  141.                 "android/os/Environment",  
  142.                 "getExternalStorageDirectory",  
  143.                 "()Ljava/io/File;"  
  144.                 );  
  145.     CHECK_EXCEPTION()  
  146.     qDebug() << "savedDir - " << savedDir.toString();  
  147.     QAndroidJniObject savedImageFile(  
  148.                 "java/io/File",  
  149.                 "(Ljava/io/File;Ljava/lang/String;)V",  
  150.                 savedDir.object<jobject>(),  
  151.                 fileName.object<jstring>());  
  152.     CHECK_EXCEPTION()  
  153.     qDebug() << "savedImageFile - " << savedImageFile.toString();  
  154.     QAndroidJniObject savedImageUri =  
  155.             QAndroidJniObject::callStaticObjectMethod(  
  156.                 "android/net/Uri",  
  157.                 "fromFile",  
  158.                 "(Ljava/io/File;)Landroid/net/Uri;",  
  159.                 savedImageFile.object<jobject>());  
  160.     CHECK_EXCEPTION()  
  161.   
  162.     //tell IMAGE_CAPTURE the output location  
  163.     QAndroidJniObject mediaStoreExtraOutput  
  164.             = QAndroidJniObject::getStaticObjectField(  
  165.                 "android/provider/MediaStore",  
  166.                 "EXTRA_OUTPUT",  
  167.                 "Ljava/lang/String;");  
  168.     CHECK_EXCEPTION()  
  169.     qDebug() << "MediaStore.EXTRA_OUTPUT - " << mediaStoreExtraOutput.toString();  
  170.     intent.callObjectMethod(  
  171.                 "putExtra",  
  172.                 "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;",  
  173.                 mediaStoreExtraOutput.object<jstring>(),  
  174.                 savedImageUri.object<jobject>());  
  175.   
  176.     //launch activity for result  
  177.     ResultReceiver *resultReceiver =  
  178.             new ResultReceiver(savedImageFile.toString(), m_capturedImage);  
  179.     startActivity(intent, 1, resultReceiver);  
  180.     CHECK_EXCEPTION()  
  181. }  

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

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

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

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

onLaunch()

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

    onLaunch() 代码如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject action = QAndroidJniObject::fromString(m_actionEdit->text());  
  2. QAndroidJniObject intent("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());  
  3. startActivity(intent, 0);  
  4. QAndroidJniEnvironment env;  
  5. CHECK_EXCEPTION()  

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

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

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 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 的宏:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #define CHECK_EXCEPTION() \  
  2.     if(env->ExceptionCheck())\  
  3.     {\  
  4.         qDebug() << "exception occured";\  
  5.         env->ExceptionClear();\  
  6.     }  

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

onGetActivityInfo()

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

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject className =  
  2.         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() 这个模版方法:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. jint taskId = activity.callMethod<jint>("getTaskId");  

    用起来很简单吧。

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. jlong instanceCount = QAndroidJniObject::callStaticMethod<jlong>(  
  2.             "android/app/Activity",  
  3.             "getInstanceCount"  
  4.             );  

    这部分代码没什么实际意义,仅仅是演示。不过可以看到 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 ,代码如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject action = QAndroidJniObject::fromString(  
  2.             "android.media.action.IMAGE_CAPTURE");  
  3. QAndroidJniObject intent("android/content/Intent",  
  4.                          "(Ljava/lang/String;)V",  
  5.                          action.object<jstring>());  

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

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QString date = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");  
  2. QAndroidJniObject fileName = QAndroidJniObject::fromString(date + ".jpg");  
  3. QAndroidJniObject savedDir = QAndroidJniObject::callStaticObjectMethod(  
  4.             "android/os/Environment",  
  5.             "getExternalStorageDirectory",  
  6.             "()Ljava/io/File;"  
  7.             );  
  8. CHECK_EXCEPTION()  
  9. qDebug() << "savedDir - " << savedDir.toString();  
  10. QAndroidJniObject savedImageFile(  
  11.             "java/io/File",  
  12.             "(Ljava/io/File;Ljava/lang/String;)V",  
  13.             savedDir.object<jobject>(),  
  14.             fileName.object<jstring>());  
  15. CHECK_EXCEPTION()  
  16. qDebug() << "savedImageFile - " << savedImageFile.toString();  
  17. QAndroidJniObject savedImageUri =  
  18.         QAndroidJniObject::callStaticObjectMethod(  
  19.             "android/net/Uri",  
  20.             "fromFile",  
  21.             "(Ljava/io/File;)Landroid/net/Uri;",  
  22.             savedImageFile.object<jobject>());  
  23. 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 了。代码如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject mediaStoreExtraOutput  
  2.         = QAndroidJniObject::getStaticObjectField(  
  3.             "android/provider/MediaStore",  
  4.             "EXTRA_OUTPUT",  
  5.             "Ljava/lang/String;");  
  6. CHECK_EXCEPTION()  
  7. qDebug() << "MediaStore.EXTRA_OUTPUT - " << mediaStoreExtraOutput.toString();  
  8. intent.callObjectMethod(  
  9.             "putExtra",  
  10.             "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;",  
  11.             mediaStoreExtraOutput.object<jstring>(),  
  12.             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 方法原型为:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. public Intent putExtra (String name, Parcelable value);  

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

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ResultReceiver *resultReceiver =  
  2.         new ResultReceiver(savedImageFile.toString(), m_capturedImage);  
  3. startActivity(intent, 1, resultReceiver);  

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


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

ResultReceiver

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. class ResultReceiver: public QAndroidActivityResultReceiver  
  2. {  
  3. public:  
  4.     ResultReceiver(QString imagePath, QLabel *view)  
  5.         : m_imagePath(imagePath), m_imageView(view)  
  6.     {  
  7.   
  8.     }  
  9.   
  10.     void handleActivityResult(  
  11.             int receiverRequestCode,  
  12.             int resultCode,  
  13.             const QAndroidJniObject & data)  
  14.     {  
  15.         qDebug() << "handleActivityResult, requestCode - " << receiverRequestCode  
  16.                     << " resultCode - " << resultCode  
  17.                     << " data - " << data.toString();  
  18.         //RESULT_OK == -1  
  19.         if(resultCode == -1 && receiverRequestCode == 1)  
  20.         {  
  21.             qDebug() << "captured image to - " << m_imagePath;  
  22.             qDebug() << "captured image exist - " << QFile::exists(m_imagePath);  
  23.             m_imageView->setPixmap(QPixmap(m_imagePath));  
  24.         }  
  25.     }  
  26.   
  27.     QString m_imagePath;  
  28.     QLabel *m_imageView;  
  29. };  

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

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

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

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

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void onActivityResult(int requestCode, int resultCode, Intent data);  

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

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

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void setResult(int resultCode, Intent data);  

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


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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值