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 #include #include #include #include #include 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()); 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() ); 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() ); 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() ); 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() ); 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(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(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());
這行代碼調用 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() );
我們使用 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() 方法能得到同樣的結果。
--------
好啦,這次就到這裡吧。下一次我們會展示更有意思的一些實用功能,如讓手機震動、讓螢幕常亮、動態切換橫屏豎屏等。再再往後可能還會介紹調節音量、調整螢幕亮度、使用推送、通知欄、選取連絡人等等,不過要看我的時間哈。