標籤:qt on android qt for android android
在“QtAndroid詳解(4):JNI調用Android系統功能(1)”中我們給出了一些簡單的樣本,示範了如何使用 Qt JNI 類庫訪問網路狀態、系統資來源目錄、當前應用資訊等等,這次呢,我們提供一些新的樣本,這些樣本可能更具實際意義。本文的樣本包括:
- 震動
- 讓螢幕常亮
- 動態改變應用的顯示方向(橫屏、豎屏)
- 調節螢幕亮度
- 設定鈴聲模式
樣本介紹
圖1
我們按照介面上的順序,一個一個來看這些功能如何?。
源碼分析
構建介面的代碼在 Widget 類的建構函式裡,不說了。這次我們換個搞法,不列所有代碼了,一個功能一個功能分開說代碼,這樣文章看起來短一些。
震動
當你點擊圖1中的“Vibrate”按鈕,onVibrate() 槽會被調用,它的代碼如下:
void Widget::onVibrate(){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "VIBRATOR_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject vibrateService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); jlong duration = 200; vibrateService.callMethod<void>("vibrate", "(J)V", duration); CHECK_EXCEPTION();}
看著是不是很熟悉呢?其實使用 QAndroidJniObject 來調用 Java 類庫,寫出來的代碼看起來都差不多……你翻翻“QtAndroid詳解(3):startActivity實戰Android拍照功能”和“QtAndroid詳解(4):JNI調用Android系統功能(1)”裡面的代碼就會更確認這一點。其實這裡面體現的是QAndroidJniObject的一般用法。
Android 裡的很多系統服務都有一個名字,以靜態成員變數的形式定義在 Context 類中。之前也見識過了。震動器的名字是 Context.VIBRATOR_SERVICE ,對應的類為 android.os.Vibrator。Vibrator 的方法 “void vibrate(long ms)” ,可以產生震動,參數單位是毫秒。
我們 C++ 代碼,先使用 QAndroidJniObject::getStaticObjectField() 方法從 android.content.Context 類擷取服務的名字;然後調用 Activity 的 getSystemService方法擷取 Vibrator 執行個體;最後調用 Vibrator 的 vibrate(long) 方法來產生震動。
改變應用在螢幕上的顯示方向
Qt Creator給我們產生的針對 Android 的應用,預設沒有設定 Activity 的 screenOrientation ,如果你手機旋轉,應用也可能變成橫屏或豎屏顯示。當我們需要應用固定以某個方向顯示時,需要修改 AndroidManifest.xml 中 <activity> 標籤的 “android:screenOrientation” 屬性。那其實, android.app.Activity 類也提供了 “void setRequestedOrientation(int requestedOrientation)” 方法,允許我們通過代碼來調整一個 Activity 的顯示方向。
再簡單的說一下:Android的一個應用,可能有多個Activity,每個Activity都可以有自己的螢幕顯示方向,即,螢幕顯示方向,是Activity的特性。
當你點擊圖1中的“ScreenOrientation”按鈕,就會調用到槽 onScreenOrientation() ,代碼如下:
void Widget::onScreenOrientation(){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); jint orient = activity.callMethod<jint>( "getRequestedOrientation" ); CHECK_EXCEPTION(); if(orient == 1) { orient = 0; } else { orient = 1; } activity.callMethod<void>( "setRequestedOrientation", "(I)V", orient); CHECK_EXCEPTION();}
QtAndroid名字空間裡的 androidActivity() 方法可以返回Qt應用使用的 Activity 對象,然後直接調用setRequestOrientation() 方法來改變當前Activity的螢幕方向。
鈴聲模式
手機來電鈴聲,一般有普通(響鈴)、靜音、震動三種模式,對應在代碼裡呢,是通過 android.media.AudioManager 類的“void setRingerMode(int ringerMode)”來設定鈴聲模式。 AudioManager 的名字是 AUDIO_SERVICE 。
setRingerMode的整形參數,有三個值: RINGER_MODE_NORMAL 、 RINGER_MODE_SILENT 、 RINGER_MODE_VIBRATE 。這些以靜態成員變數的形式定義在 android.media.AudioManager 類中,我們在 C++ 代碼中可以通過 QAndroidJniObject::getStaticField 方法來擷取,不過我在代碼裡偷了個懶,直接用數字了。關於鈴聲模式對應的宏:
#define RINGER_MODE_NORMAL 2#define RINGER_MODE_SILENT 0#define RINGER_MODE_VIBRATE 1
1所示,鈴聲模式是通過一組選項按鈕(QRadioButton)來選擇的。我在代碼裡使用 QButtonGroup 來管理三個選項按鈕,同時將每個按鈕的 id 設定為它代表的鈴聲模式。這樣當 QButtonGroup 的“buttonClicked(int id)”觸發時,我們的槽“void Widget::onRingerModeClicked(int mode)”被調用,參數直接就是響鈴模式了,很方便。
那先看看這組鈴聲模式選項按鈕初始化的代碼:
void Widget::initRingerMode(QVBoxLayout *layout){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "AUDIO_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject audioService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); int mode = audioService.callMethod<jint>( "getRingerMode", "()I" ); CHECK_EXCEPTION(); layout->addWidget(new QLabel("Ringer Mode:")); QHBoxLayout *rowLayout = new QHBoxLayout(); layout->addLayout(rowLayout); rowLayout->addSpacing(30); m_ringerModeGroup = new QButtonGroup(this); QRadioButton *normal = new QRadioButton("Normal"); m_ringerModeGroup->addButton(normal, RINGER_MODE_NORMAL); rowLayout->addWidget(normal); QRadioButton *silent = new QRadioButton("Silent"); m_ringerModeGroup->addButton(silent, RINGER_MODE_SILENT); rowLayout->addWidget(silent); QRadioButton *vibrate = new QRadioButton("Vibrate"); m_ringerModeGroup->addButton(vibrate, RINGER_MODE_VIBRATE); rowLayout->addWidget(vibrate); switch(mode) { case RINGER_MODE_NORMAL: normal->setChecked(true); break; case RINGER_MODE_SILENT: silent->setChecked(true); break; case RINGER_MODE_VIBRATE: vibrate->setChecked(true); break; } connect(m_ringerModeGroup, SIGNAL(buttonClicked(int)), this, SLOT(onRingerModeClicked(int)));}
在上面的代碼裡,我還調用 android.media.AudioManager 的 “int getRingerMode()” 方法擷取了系統當前的鈴聲模式,然後選中對應的選項按鈕。
下面是 onRingerModeClicked 槽:
void Widget::onRingerModeClicked(int mode){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "AUDIO_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject audioService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); audioService.callMethod<void>( "setRingerMode", "(I)V", mode ); CHECK_EXCEPTION();}
代碼和onVibrate()如出一轍,不再解釋了。
保持螢幕常亮
有些應用,比如視頻類的,在運行時不希望Android手機休眠、鎖屏,這時就需要保持螢幕常亮。
在 Android 上有兩個辦法來讓螢幕常亮,一種是在 Activity 的 onCreate() 方法中,在調用 setContentView() 之前加入下面的代碼:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
這種方式不需要特別的許可權。不過在 Qt 中,我們不能通過 QAndroidJniObject 來使用這種方式。因為我們的介面出來時,setContentView() 已經被調用過了……另外還有線程問題。所以我用了第二種方式:PowerManager 。
android.os.PowerManager 可以控制螢幕是否常亮,它的服務名字為 Context.POWER_SERVICE 。PowerManager 的方法 “WakeLock newWakeLock(int flag, String tag)” 可以擷取一個 WakeLock 執行個體, 其中 flag 可以是下列幾種:
- PARTIAL_WAKE_LOCK(常量值1)
- SCREEN_DIM_WAKE_LOCK(常量值6)
- SCREEN_BRIGHT_WAKE_LOCK(常量值10)
- FULL_WAKE_LOCK(常量值16)
我在樣本中使用 SCREEN_BRIGHT_WAKE_LOCK ,對應的常量值為 10 。我直接用常量,沒有使用 QAndroidJniObject::getStaticField() 來擷取這個常量。
當得到 WakeLock 執行個體後,就可以調用它的 “void acquire()” 方法來完成鎖定,調用它的“void release()”方法釋放鎖定。我們應該在合適的時候釋放鎖定,不然會很耗電。
圖1中我用了一個複選框(QCheckBox)來控制是否讓螢幕常亮,對應的槽為 onScreenOnChecked ,代碼如下:
void Widget::onScreenOnChecked(bool checked){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); if(m_lastChecked) { if(m_wakeLock.isValid()) { m_wakeLock.callMethod<void>("release"); CHECK_EXCEPTION(); } m_lastChecked = false; return; } QAndroidJniObject name = QAndroidJniObject::getStaticObjectField( "android/content/Context", "POWER_SERVICE", "Ljava/lang/String;" ); CHECK_EXCEPTION(); QAndroidJniObject powerService = activity.callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", name.object<jstring>()); CHECK_EXCEPTION(); QAndroidJniObject tag = QAndroidJniObject::fromString("QtJniWakeLock"); m_wakeLock = powerService.callObjectMethod( "newWakeLock", "(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;", 10, //SCREEN_BRIGHT_WAKE_LOCK tag.object<jstring>() ); CHECK_EXCEPTION(); if(m_wakeLock.isValid()) { m_wakeLock.callMethod<void>("acquire"); CHECK_EXCEPTION(); } m_lastChecked = true;}
這裡需要說明一點,WakeLock 是 android.os.PowerManager的內部類,我們通過 JNI 訪問內部類時,簽名是 “Outer$Inner” 這種格式,比如“PowerManager$WakeLock”。
我還給應用添加了 WAKE_LOCK 許可權。
調整螢幕亮度
Android手機的設定介面中可以調整螢幕亮度,這是通過修改 Settings 來實現的。一個應用要想調整 Settings ,需要請求 WRITE_SETTINGS 許可權。
android.provider.Settings.System 這個類提供了下面兩個靜態方法:
- static boolean putInt(ContentResolver, String key, int value);
- static int getInt(ContentResolver, String key);
putInt 方法用於寫入一個設定(key-value對),getInt則用來擷取key代表的值。
System 是 Settings 類的內部類,JNI 簽名時要注意。
1所示,我用一個 QSlider 來調整螢幕亮度,螢幕亮度的範圍是 0~255 。當你拖動 QSlider 時,會觸發槽 onBrightnessChanged ,代碼如下:
void Widget::onBrightnessChanged(int value){ QAndroidJniEnvironment env; QAndroidJniObject activity = androidActivity(); QAndroidJniObject contentResolver = activity.callObjectMethod( "getContentResolver", "()Landroid/content/ContentResolver;" ); CHECK_EXCEPTION(); //set brightness mode to MANUAL QAndroidJniObject brightnessTag = QAndroidJniObject::fromString("screen_brightness"); QAndroidJniObject brightnessModeTag = QAndroidJniObject::fromString("screen_brightness_mode"); bool ok = QAndroidJniObject::callStaticMethod<jboolean>( "android/provider/Settings$System", "putInt", "(Landroid/content/ContentResolver;Ljava/lang/String;I)Z", contentResolver.object<jobject>(), brightnessModeTag.object<jstring>(), 0 ); CHECK_EXCEPTION(); qDebug() << "set brightness mode to MANUAL - " << ok; //set brightness to value ok = QAndroidJniObject::callStaticMethod<jboolean>( "android/provider/Settings$System", "putInt", "(Landroid/content/ContentResolver;Ljava/lang/String;I)Z", contentResolver.object<jobject>(), brightnessTag.object<jstring>(), value ); CHECK_EXCEPTION(); qDebug() << "set brightness to " << value << " result - " << ok;}
你可能注意到 putInt 和 getInt 的第一個參數,類型是 ContentResolver ,全限定類名為 android.content.ContentResolver ,Activity 的方法“ContentResolver getContentResolver()”可以擷取到 ContentResolver 執行個體,我在 onBrightnessChanged 槽的開始部分就是這樣擷取 ContentResolver 執行個體的。
Settings.System 這個類可以修改系統的各種設定,它定義了很多靜態常量來標識配置項。SCREEN_BRIGHTNESS和SCREEN_BRIGHTNESS_MODE是我在代碼中用到的兩個,前者標識螢幕亮度,後者標識螢幕亮度模式。它們都是字串常量,我偷懶了,直接使用了 Android 文檔中的常亮字串。
有些使用者可能將螢幕亮度調節模式設定為自動了,在自動模式下,修改 System 中的 SCREEN_BRIGHTNESS 是無效的,所以我一開始就粗暴的把螢幕亮度模式設定為手動了。System類的靜態常量SCREEN_BRIGHTNESS_MODE_AUTOMATIC(值為1)代表自動調節模式,SCREEN_BRIGHTNESS_MODE_MANUAL(值為0)代表手動模式。我又犯懶了,還是直接用了數值。
我這裡用的方式,會影響Android的全域設定……不一定是你想要的哦。如果你只想修改當前應用的螢幕亮度,那還有另外一種方式:設定 Activity 對應的 Window 的屬性。對應的 Java 代碼如下:
WindowManager.LayoutParams lp = window.getAttributes(); lp.screenBrightness = brightness; window.setAttributes(lp);
如果你要在 Qt 中通過 JNI 來實現上面的 Java 代碼,可能會出錯,因為,上面的代碼必須在 Android Activity 所在的 UI 線程中調用,而 Qt 的線程,和 Activity 所在的線程,是兩個不同的線程。詳細的分析,請參看《Qt on Android核心編程》的第13章——“Qt on Android揭秘”。
------------
額滴神呢,終於說完了耶。看起來不像是在講 Qt C++ 代碼,而是在介紹 Android 類庫……不可否認的是,要使用 QAndroidJniObject 進行 JNI 開發,確實是醬紫的。
回顧一下:
- QtAndroid詳解(4):JNI調用Android系統功能(1)
- QtAndroid詳解(3):startActivity實戰Android拍照功能
- QtAndroid詳解(2):startActivity和它的小夥伴們
- QtAndroid詳解(1):QAndroidJniObject
- Qt on Android專欄
QtAndroid詳解(5):JNI調用Android系統功能(2)