QtAndroid详解(4):JNI调用Android系统功能(1)

前面几篇我们讲解了 QtAndroid 名字空间的基本用法,这次我们使用前面讲过的方法和类库,展示一些简单的小示例。我在《Qt on Android核心编程》一书中主要通过“继承 QtActivity ,实现自己的 Activity 并添加 static 方法”这种形式来调用 Android 系统的一些功能。这一系列的文章,我们主要使用 Qt 5.3 里引入的 QtAndroid 名字空间内的方法和 QAndroidJniObject 类来展示 Qt 中如何进行 JNI 调用,只在必要时才重写 QtActivity 。

        Qt on Android 应用,根据你的需求,经常会调用到 Android 系统提供的一些功能,比如判断网络连接、获取外部存储路径,或者缓存文件目录等等。这些经常被朋友问到,我会在这一系列文章中慢慢把 Qt on Android 开发中经常用到的功能点都演示一下。希望对大家有所帮助。

示例介绍

    示例很简单,使用 Qt Widgets 来展示。下图是效果:



    如上图所示,界面非常简陋,点下 Refresh 按钮,就获取一些 Android 系统信息和当前应用的一些信息,放在 QListWidget 中。包括下面的内容:

  • 手机的 Android 版本
  • 网络状态和网络信息
  • 手机的数据目录
  • 手机外部存储目录
  • 手机的照片、音乐、视频、铃声等目录
  • 应用的路径
  • 安装后,系统保留的 APK 的位置
  • 应用的 files 目录

源码分析

    代码没什么逻辑可讲……都在下面了:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include "widget.h"  
  2. #include <QVBoxLayout>  
  3. #include <QListWidgetItem>  
  4. #include <QtAndroid>  
  5. #include <QAndroidJniEnvironment>  
  6. #include <QAndroidJniObject>  
  7. #include <QDebug>  
  8.   
  9. using namespace QtAndroid;  
  10.   
  11. #define CHECK_EXCEPTION() \  
  12.     if(env->ExceptionCheck())\  
  13.     {\  
  14.         qDebug() << "exception occured";\  
  15.         env->ExceptionClear();\  
  16.     }  
  17.   
  18. Widget::Widget(QWidget *parent)  
  19.     : QWidget(parent)  
  20. {  
  21.     QVBoxLayout *layout = new QVBoxLayout(this);  
  22.     m_refresh = new QPushButton("Refresh");  
  23.     connect(m_refresh, SIGNAL(clicked()), this, SLOT(onRefresh()));  
  24.     layout->addWidget(m_refresh);  
  25.     m_list = new QListWidget();  
  26.     layout->addWidget(m_list, 1);  
  27. }  
  28.   
  29. Widget::~Widget()  
  30. {  
  31.   
  32. }  
  33.   
  34. void Widget::onRefresh()  
  35. {  
  36.     m_list->clear();  
  37.     QAndroidJniEnvironment env;  
  38.   
  39.     //get Android SDK version  
  40.     m_list->addItem(QString("SDK版本:%1").arg(androidSdkVersion()));  
  41.   
  42.     QAndroidJniObject activity = androidActivity();  
  43.     //get network state  
  44.     QAndroidJniObject connectivity = QAndroidJniObject::getStaticObjectField(  
  45.                 "android/content/Context",  
  46.                 "CONNECTIVITY_SERVICE",  
  47.                 "Ljava/lang/String;");  
  48.     if(connectivity.isValid()){  
  49.         qDebug() << "connectivity id - " << connectivity.toString();  
  50.         CHECK_EXCEPTION()  
  51.         QAndroidJniObject connectivityService = activity.callObjectMethod(  
  52.                     "getSystemService",  
  53.                     "(Ljava/lang/String;)Ljava/lang/Object;",  
  54.                     connectivity.object<jstring>());  
  55.         CHECK_EXCEPTION()  
  56.         qDebug() << "got connectivity service - " << connectivityService.isValid();  
  57.         if(connectivityService.isValid())  
  58.         {  
  59.             QAndroidJniObject networkInfo = connectivityService.callObjectMethod(  
  60.                         "getActiveNetworkInfo",  
  61.                         "()Landroid/net/NetworkInfo;");  
  62.             CHECK_EXCEPTION()  
  63.                     qDebug() << "got NetworkInfo - " << networkInfo.isValid();  
  64.             if(networkInfo.isValid())  
  65.             {  
  66.                 m_list->addItem(QString("网络状态:已连接(%1)").arg(networkInfo.toString()));  
  67.             }  
  68.             else  
  69.             {  
  70.                 m_list->addItem("网络状态:未连接");  
  71.             }  
  72.         }  
  73.     }  
  74.   
  75.     //get variable directories of Android System  
  76.     QAndroidJniObject externalStorageDir = QAndroidJniObject::callStaticObjectMethod(  
  77.                 "android/os/Environment",  
  78.                 "getExternalStorageDirectory",  
  79.                 "()Ljava/io/File;"  
  80.                 );  
  81.     CHECK_EXCEPTION()  
  82.     m_list->addItem(QString("外部存储目录:%1").arg(externalStorageDir.toString()));  
  83.   
  84.     QAndroidJniObject dataDir = QAndroidJniObject::callStaticObjectMethod(  
  85.                 "android/os/Environment",  
  86.                 "getDataDirectory",  
  87.                 "()Ljava/io/File;"  
  88.                 );  
  89.     CHECK_EXCEPTION()  
  90.     m_list->addItem(QString("数据目录:%1").arg(dataDir.toString()));  
  91.   
  92.     QAndroidJniObject dcim = QAndroidJniObject::getStaticObjectField(  
  93.                 "android/os/Environment",  
  94.                 "DIRECTORY_DCIM",  
  95.                 "Ljava/lang/String;"  
  96.                 );  
  97.     CHECK_EXCEPTION()  
  98.     QAndroidJniObject dcimDir = QAndroidJniObject::callStaticObjectMethod(  
  99.                 "android/os/Environment",  
  100.                 "getExternalStoragePublicDirectory",  
  101.                 "(Ljava/lang/String;)Ljava/io/File;",  
  102.                 dcim.object<jstring>()  
  103.                 );  
  104.     CHECK_EXCEPTION()  
  105.     m_list->addItem(QString("照片目录:%1").arg(dcimDir.toString()));  
  106.   
  107.     QAndroidJniObject music = QAndroidJniObject::getStaticObjectField(  
  108.                 "android/os/Environment",  
  109.                 "DIRECTORY_MUSIC",  
  110.                 "Ljava/lang/String;"  
  111.                 );  
  112.     CHECK_EXCEPTION()  
  113.     QAndroidJniObject musicDir = QAndroidJniObject::callStaticObjectMethod(  
  114.                 "android/os/Environment",  
  115.                 "getExternalStoragePublicDirectory",  
  116.                 "(Ljava/lang/String;)Ljava/io/File;",  
  117.                 music.object<jstring>()  
  118.                 );  
  119.     CHECK_EXCEPTION()  
  120.     m_list->addItem(QString("音乐目录:%1").arg(musicDir.toString()));  
  121.   
  122.     QAndroidJniObject movie = QAndroidJniObject::getStaticObjectField(  
  123.                 "android/os/Environment",  
  124.                 "DIRECTORY_MOVIES",  
  125.                 "Ljava/lang/String;"  
  126.                 );  
  127.     CHECK_EXCEPTION()  
  128.     QAndroidJniObject movieDir = QAndroidJniObject::callStaticObjectMethod(  
  129.                 "android/os/Environment",  
  130.                 "getExternalStoragePublicDirectory",  
  131.                 "(Ljava/lang/String;)Ljava/io/File;",  
  132.                 movie.object<jstring>()  
  133.                 );  
  134.     CHECK_EXCEPTION()  
  135.     m_list->addItem(QString("视频目录:%1").arg(movieDir.toString()));  
  136.   
  137.     QAndroidJniObject ringtones = QAndroidJniObject::getStaticObjectField(  
  138.                 "android/os/Environment",  
  139.                 "DIRECTORY_RINGTONES",  
  140.                 "Ljava/lang/String;"  
  141.                 );  
  142.     CHECK_EXCEPTION()  
  143.     QAndroidJniObject ringtonesDir = QAndroidJniObject::callStaticObjectMethod(  
  144.                 "android/os/Environment",  
  145.                 "getExternalStoragePublicDirectory",  
  146.                 "(Ljava/lang/String;)Ljava/io/File;",  
  147.                 ringtones.object<jstring>()  
  148.                 );  
  149.     CHECK_EXCEPTION()  
  150.     m_list->addItem(QString("铃声目录:%1").arg(ringtonesDir.toString()));  
  151.   
  152.     //app's infomation  
  153.     QAndroidJniObject filesDir = activity.callObjectMethod(  
  154.                 "getFilesDir",  
  155.                 "()Ljava/io/File;");  
  156.     CHECK_EXCEPTION()  
  157.     m_list->addItem(QString("应用文件目录:%1").arg(filesDir.toString()));  
  158.   
  159.     QAndroidJniObject packageName = activity.callObjectMethod<jstring>("getPackageName");  
  160.     CHECK_EXCEPTION()  
  161.     m_list->addItem(QString("应用包名:%1").arg(packageName.toString()));  
  162.   
  163.     QAndroidJniObject appCacheDir = activity.callObjectMethod(  
  164.                 "getCacheDir",  
  165.                 "()Ljava/io/File;");  
  166.     CHECK_EXCEPTION()  
  167.     m_list->addItem(QString("应用缓存目录:%1").arg(appCacheDir.toString()));  
  168.   
  169.     QAndroidJniObject appInfo = activity.callObjectMethod(  
  170.                 "getApplicationInfo",  
  171.                 "()Landroid/content/pm/ApplicationInfo;");  
  172.     CHECK_EXCEPTION()  
  173.   
  174.     QAndroidJniObject appClassName = appInfo.getObjectField<jstring>("className");  
  175.     CHECK_EXCEPTION()  
  176.     m_list->addItem(QString("应用类名:%1").arg(appClassName.toString()));  
  177.   
  178.     QAndroidJniObject appLocation = appInfo.getObjectField(  
  179.                 "sourceDir""Ljava/lang/String;");  
  180.     CHECK_EXCEPTION()  
  181.     m_list->addItem(QString("APK位置:%1").arg(appLocation.toString()));  
  182. }  

    最恐怖的就是 onRefresh() 这个槽了,将近一百五十行代码,这不是好的编程实践,实际开发中尽量别这么干。

    其实在 Qt 中通过 JNI 调用 Android 功能,关键的就是两点:

  1. Qt提供的API怎么用
  2. Android类库怎么用

    Qt 提供的 API ,在“QtAndroid详解(1):QAndroidJniObject”、"QtAndroid详解(2):startActivity和它的小伙伴们"、"QtAndroid详解(3):startActivity实战Android拍照功能"这三篇文章中已有详细讲解,不再赘述了。

    Android 类库这方面,我们搞 C++ 开发的朋友,可能不熟悉。不过没关系,可以通过 Android 在线 SDK 来学习,另外我这里提供的 Qt JNI 代码,都是实测可用的,里面演示一些功能的代码,如果需要,可以直接在项目中使用。

    好了,我们开始慢慢介绍吧。

Android版本获取

    这个很贴心,QtAndroid名字空间直接提供了一个方法: androidSdkVersion() 。它返回一个整数值,表示 Android SDK 版本号。

网络状态

    在 Android 中,有一个 ConnectivityManager 类,可以查询系统的网络状态。

    ConnectivityManager 类的 getActiveNetworkInfo() 方法可以获取到当前活跃的网络连接信息,它返回一个 NetworkInfo 类的实例。如果未联网,这个方法返回 null 。

    ConnectivityManager 在 Android 系统里以一个服务存在,需要通过 Context 的 getSystemService() 方法来获取到这个服务。 getSystemService() 接受一个代表服务名字的字符串作为参数。对于网络连接管理服务,名字是 CONNECTIVITY_SERVICE ,它是 Context 类的静态成员变量。

    获取网络连接管理服务的 Java 代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. Context.getSystemService(Context.CONNECTIVITY_SERVICE);  

    这些都是 Android Java 背景知识,现在来看 Qt JNI 代码。一行一行过。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject connectivity = QAndroidJniObject::getStaticObjectField(  
  2.             "android/content/Context",  
  3.             "CONNECTIVITY_SERVICE",  
  4.             "Ljava/lang/String;");  

    这行代码使用获取 Context 类的静态成员 CONNECTIVITY_SERVICE ,保存在 connectivity 对象里,我们在获取 ConnectivityManager 时需要它。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject connectivityService = activity.callObjectMethod(  
  2.             "getSystemService",  
  3.             "(Ljava/lang/String;)Ljava/lang/Object;",  
  4.             connectivity.object<jstring>());  

    这行代码调用 Context 的 getSystemService 方法来获取 ConnectivityManager 实例。我们需要一个 Context 实例,刚好 QtAndroid::androidActivity() 方法能返回一个给我们。

    拿到了 ConnectivityManager 实例,就该调用它的 getActiveNetworkInfo() 方法来获取当前的活动连接了。下面是代码:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject networkInfo = connectivityService.callObjectMethod(  
  2.             "getActiveNetworkInfo",  
  3.             "()Landroid/net/NetworkInfo;");  

    QAndroidJniObject 有个方法叫 isValid() ,当它返回 true 时代表它拿的 JNI 对象正常可用, false 就代表没拿到可用的 JNI 对象,一般也就是 Java 里的 null 。所以,我认为networkInfo.isValid() 为 true 说明网络连接正常。

    其它的都是辅助性代码,看最前面的源码好了。

Android系统的各种目录

    示例里的各种系统级的目录,都是通过 android.os.Enviroment 这个类获取的。

    我们先说图片、视频、铃声这些吧,他们通过 getExternalStoragePublicDirectory(String) 方法获取。Android给每个公共存储目录提供了一个字符串类型的名字,定义为 Enviroment 类的静态成员变量。所以,我们使用 Qt JNI 获取这些目录的步骤是:

  1. 获取目录类型名
  2. 调用getExternalStoragePublicDirectory

    按照这个逻辑来看获取图片目录的代码,关键的就下面两行:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject dcim = QAndroidJniObject::getStaticObjectField(  
  2.             "android/os/Environment",  
  3.             "DIRECTORY_DCIM",  
  4.             "Ljava/lang/String;"  
  5.             );  
  6.   
  7. QAndroidJniObject dcimDir = QAndroidJniObject::callStaticObjectMethod(  
  8.             "android/os/Environment",  
  9.             "getExternalStoragePublicDirectory",  
  10.             "(Ljava/lang/String;)Ljava/io/File;",  
  11.             dcim.object<jstring>()  
  12.             );  

    我们使用 QAndroidJniObject::getStaticObjectField() 方法来获取 Java 类 Enviroment 的静态成员变量,然后使用 callStaticObjectMethod 调用 getExternalStoragePublicDirectory 方法。

当前应用信息

    当前应用的一些信息,可以通过 Android 里的 Activity 类获取。

    我们需要一个 Activity 对象,在 Qt on Android 应用里,对应的类是 QtActivity ,之前在“QtAndroid详解(3):startActivity实战Android拍照功能”中我们已经介绍过了。不多说了。

    获取当前应用 files 目录的代码如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. QAndroidJniObject filesDir = activity.callObjectMethod(  
  2.             "getFilesDir",  
  3.             "()Ljava/io/File;");  

    它的返回结果就是 /data/data/an.qt.SystemInfo/files ,实际上使用 Qt 的 QDir::currentPath() 方法能得到同样的结果。


--------

    好啦,这次就到这里吧。下一次我们会展示更有意思的一些实用功能,如让手机震动、让屏幕常亮、动态切换横屏竖屏等。再再往后可能还会介绍调节音量、调整屏幕亮度、使用推送、通知栏、选取联系人等等,不过要看我的时间哈。

    回顾一下:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值