QtAndroid詳解(5):JNI調用Android系統功能(2)

來源:互聯網
上載者:User

標籤: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)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.