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>
- usingnamespace 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類庫怎麼用
Android 類庫這方面,我們搞 C++ 開發的朋友,可能不熟悉。不過沒關係,可以通過 Android 線上 SDK 來學習,另外我這裡提供的 Qt JNI 程式碼,都是實測可用的,裡面演示一些功能的程式碼,如果需要,可以直接在專案中使用。
好了,我們開始慢慢介紹吧。
Android版本獲取
這個很貼心,QtAndroid名字空間直接提供了一個方法: androidSdkVersion() 。它返回一個整數值,表示 Android SDK 版本號。
網路狀態
在 Android 中,有一個 ConnectivityManager 類,可以查詢系統的網路狀態。
ConnectivityManager 類的 getActiveNetworkInfo() 方法可以獲取到當前活躍的網路連線資訊,它返回一個 NetworkInfo 類的例項。如果未聯網,這個方法返回 null 。
ConnectivityManager 在 Android 系統裡以一個服務存在,需要通過 Context 的 getSystemService() 方