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 目录
源码分析
代码没什么逻辑可讲……都在下面了:
#include "widget.h"#include <QVBoxLayout>#include <QListWidgetItem>#include <QtAndroid>#include <QAndroidJniEnvironment>#include <QAndroidJniObject>#include <QDebug>using namespace QtAndroid;#define CHECK_EXCEPTION() \ if(env->ExceptionCheck())\ {\ qDebug() << "exception occured";\ env->ExceptionClear();\ }Widget::Widget(QWidget *parent) : QWidget(parent){ QVBoxLayout *layout = new QVBoxLayout(this); m_refresh = new QPushButton("Refresh"); connect(m_refresh, SIGNAL(clicked()), this, SLOT(onRefresh())); layout->addWidget(m_refresh); m_list = new QListWidget(); layout->addWidget(m_list, 1);}Widget::~Widget(){}void Widget::onRefresh(){ m_list->clear(); QAndroidJniEnvironment env; //get Android SDK version m_list->addItem(QString("SDK版本:%1").arg(androidSdkVersion())); QAndroidJniObject activity = androidActivity(); //get network state QAndroidJniObject connectivity = QAndroidJniObject::getStaticObjectField( "android/content/Context", "CONNECTIVITY_SERVICE", "Ljava/lang/String;"); if(connectivity.isValid()){ qDebug() << "connectivity id - " << connectivity.toString(); CHECK_EXCEPTION() QAndroidJniObject connectivityService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", connectivity.object<jstring>()); CHECK_EXCEPTION() qDebug() << "got connectivity service - " << connectivityService.isValid(); if(connectivityService.isValid()) { QAndroidJniObject networkInfo = connectivityService.callObjectMethod( "getActiveNetworkInfo", "()Landroid/net/NetworkInfo;"); CHECK_EXCEPTION() qDebug() << "got NetworkInfo - " << networkInfo.isValid(); if(networkInfo.isValid()) { m_list->addItem(QString("网络状态:已连接(%1)").arg(networkInfo.toString())); } else { m_list->addItem("网络状态:未连接"); } } } //get variable directories of Android System QAndroidJniObject externalStorageDir = QAndroidJniObject::callStaticObjectMethod( "android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;" ); CHECK_EXCEPTION() m_list->addItem(QString("外部存储目录:%1").arg(externalStorageDir.toString())); QAndroidJniObject dataDir = QAndroidJniObject::callStaticObjectMethod( "android/os/Environment", "getDataDirectory", "()Ljava/io/File;" ); CHECK_EXCEPTION() m_list->addItem(QString("数据目录:%1").arg(dataDir.toString())); QAndroidJniObject dcim = QAndroidJniObject::getStaticObjectField( "android/os/Environment", "DIRECTORY_DCIM", "Ljava/lang/String;" ); CHECK_EXCEPTION() QAndroidJniObject dcimDir = QAndroidJniObject::callStaticObjectMethod( "android/os/Environment", "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;", dcim.object<jstring>() ); CHECK_EXCEPTION() m_list->addItem(QString("照片目录:%1").arg(dcimDir.toString())); QAndroidJniObject music = QAndroidJniObject::getStaticObjectField( "android/os/Environment", "DIRECTORY_MUSIC", "Ljava/lang/String;" ); CHECK_EXCEPTION() QAndroidJniObject musicDir = QAndroidJniObject::callStaticObjectMethod( "android/os/Environment", "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;", music.object<jstring>() ); CHECK_EXCEPTION() m_list->addItem(QString("音乐目录:%1").arg(musicDir.toString())); QAndroidJniObject movie = QAndroidJniObject::getStaticObjectField( "android/os/Environment", "DIRECTORY_MOVIES", "Ljava/lang/String;" ); CHECK_EXCEPTION() QAndroidJniObject movieDir = QAndroidJniObject::callStaticObjectMethod( "android/os/Environment", "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;", movie.object<jstring>() ); CHECK_EXCEPTION() m_list->addItem(QString("视频目录:%1").arg(movieDir.toString())); QAndroidJniObject ringtones = QAndroidJniObject::getStaticObjectField( "android/os/Environment", "DIRECTORY_RINGTONES", "Ljava/lang/String;" ); CHECK_EXCEPTION() QAndroidJniObject ringtonesDir = QAndroidJniObject::callStaticObjectMethod( "android/os/Environment", "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;", ringtones.object<jstring>() ); CHECK_EXCEPTION() m_list->addItem(QString("铃声目录:%1").arg(ringtonesDir.toString())); //app's infomation QAndroidJniObject filesDir = activity.callObjectMethod( "getFilesDir", "()Ljava/io/File;"); CHECK_EXCEPTION() m_list->addItem(QString("应用文件目录:%1").arg(filesDir.toString())); QAndroidJniObject packageName = activity.callObjectMethod<jstring>("getPackageName"); CHECK_EXCEPTION() m_list->addItem(QString("应用包名:%1").arg(packageName.toString())); QAndroidJniObject appCacheDir = activity.callObjectMethod( "getCacheDir", "()Ljava/io/File;"); CHECK_EXCEPTION() m_list->addItem(QString("应用缓存目录:%1").arg(appCacheDir.toString())); QAndroidJniObject appInfo = activity.callObjectMethod( "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); CHECK_EXCEPTION() QAndroidJniObject appClassName = appInfo.getObjectField<jstring>("className"); CHECK_EXCEPTION() m_list->addItem(QString("应用类名:%1").arg(appClassName.toString())); QAndroidJniObject appLocation = appInfo.getObjectField( "sourceDir", "Ljava/lang/String;"); CHECK_EXCEPTION() m_list->addItem(QString("APK位置:%1").arg(appLocation.toString()));}
最恐怖的就是 onRefresh() 这个槽了,将近一百五十行代码,这不是好的编程实践,实际开发中尽量别这么干。
其实在 Qt 中通过 JNI 调用 Android 功能,关键的就是两点:
- Qt提供的API怎么用
- 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 代码如下:
Context.getSystemService(Context.CONNECTIVITY_SERVICE);
这些都是 Android Java 背景知识,现在来看 Qt JNI 代码。一行一行过。
QAndroidJniObject connectivity = QAndroidJniObject::getStaticObjectField( "android/content/Context", "CONNECTIVITY_SERVICE", "Ljava/lang/String;");
这行代码使用获取 Context 类的静态成员 CONNECTIVITY_SERVICE ,保存在 connectivity 对象里,我们在获取 ConnectivityManager 时需要它。
QAndroidJniObject connectivityService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", connectivity.object<jstring>());
这行代码调用 Context 的 getSystemService 方法来获取 ConnectivityManager 实例。我们需要一个 Context 实例,刚好 QtAndroid::androidActivity() 方法能返回一个给我们。
拿到了 ConnectivityManager 实例,就该调用它的 getActiveNetworkInfo() 方法来获取当前的活动连接了。下面是代码:
QAndroidJniObject networkInfo = connectivityService.callObjectMethod( "getActiveNetworkInfo", "()Landroid/net/NetworkInfo;");
QAndroidJniObject 有个方法叫 isValid() ,当它返回 true 时代表它拿的 JNI 对象正常可用, false 就代表没拿到可用的 JNI 对象,一般也就是 Java 里的 null 。所以,我认为networkInfo.isValid() 为 true 说明网络连接正常。
其它的都是辅助性代码,看最前面的源码好了。
Android系统的各种目录
示例里的各种系统级的目录,都是通过 android.os.Enviroment 这个类获取的。
我们先说图片、视频、铃声这些吧,他们通过getExternalStoragePublicDirectory(String) 方法获取。Android给每个公共存储目录提供了一个字符串类型的名字,定义为 Enviroment 类的静态成员变量。所以,我们使用 Qt JNI 获取这些目录的步骤是:
- 获取目录类型名
- 调用getExternalStoragePublicDirectory
按照这个逻辑来看获取图片目录的代码,关键的就下面两行:
QAndroidJniObject dcim = QAndroidJniObject::getStaticObjectField( "android/os/Environment", "DIRECTORY_DCIM", "Ljava/lang/String;" ); QAndroidJniObject dcimDir = QAndroidJniObject::callStaticObjectMethod( "android/os/Environment", "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;", dcim.object<jstring>() );
我们使用 QAndroidJniObject::getStaticObjectField() 方法来获取 Java 类 Enviroment 的静态成员变量,然后使用 callStaticObjectMethod 调用getExternalStoragePublicDirectory 方法。
当前应用信息
当前应用的一些信息,可以通过 Android 里的 Activity 类获取。
我们需要一个 Activity 对象,在 Qt on Android 应用里,对应的类是 QtActivity ,之前在“QtAndroid详解(3):startActivity实战Android拍照功能”中我们已经介绍过了。不多说了。
获取当前应用 files 目录的代码如下:
QAndroidJniObject filesDir = activity.callObjectMethod( "getFilesDir", "()Ljava/io/File;");
它的返回结果就是 /data/data/an.qt.SystemInfo/files ,实际上使用 Qt 的 QDir::currentPath() 方法能得到同样的结果。
--------
好啦,这次就到这里吧。下一次我们会展示更有意思的一些实用功能,如让手机震动、让屏幕常亮、动态切换横屏竖屏等。再再往后可能还会介绍调节音量、调整屏幕亮度、使用推送、通知栏、选取联系人等等,不过要看我的时间哈。
回顾一下:
- QtAndroid详解(3):startActivity实战Android拍照功能
- QtAndroid详解(2):startActivity和它的小伙伴们
- QtAndroid详解(1):QAndroidJniObject
- Qt on Android专栏
更多相关文章
- Android网络编程之通过Get方法实现
- 研究开源OpenWnn Android输入法源代码
- Android assets 目录作用
- android控件的监听绑定方法
- 全面的Android文件目录解析和获取方法(包含对6.0系统的说明)
- Android资源目录 /res/xml /res/raw 和 /assets介绍
- Android官方的文档中提到了模拟器中设置代理服务器的方法,即在命
- 如何向Android的framework里添加新类 &&& android修改开放类方法
- Android swap分区作用及swapper软件设置方法