QtAndroid详解(5):JNI调用Android系统功能(2)

 在“QtAndroid详解(4):JNI调用Android系统功能(1)”中我们给出了一些简单的示例,演示了如何使用 Qt JNI 类库访问网络状态、系统资源目录、当前应用信息等等,这次呢,我们提供一些新的示例,这些示例可能更具实际意义。本文的示例包括:

  • 震动
  • 让屏幕常亮
  • 动态改变应用的显示方向(横屏、竖屏)
  • 调节屏幕亮度
  • 设置铃声模式

示例介绍


                            图1

    我们按照界面上的顺序,一个一个来看这些功能如何实现。

源码分析

    构建界面的代码在 Widget 类的构造函数里,不说了。这次我们换个搞法,不列所有代码了,一个功能一个功能分开说代码,这样文章看起来短一些。

震动

    当你点击图1中的“Vibrate”按钮,onVibrate() 槽会被调用,它的代码如下:

[cpp]  view plain copy
  1. void Widget::onVibrate()  
  2. {  
  3.     QAndroidJniEnvironment env;  
  4.     QAndroidJniObject activity = androidActivity();  
  5.     QAndroidJniObject name = QAndroidJniObject::getStaticObjectField(  
  6.                 "android/content/Context",  
  7.                 "VIBRATOR_SERVICE",  
  8.                 "Ljava/lang/String;"  
  9.                 );  
  10.     CHECK_EXCEPTION();  
  11.     QAndroidJniObject vibrateService = activity.callObjectMethod(  
  12.                 "getSystemService",  
  13.                 "(Ljava/lang/String;)Ljava/lang/Object;",  
  14.                 name.object());  
  15.     CHECK_EXCEPTION();  
  16.     jlong duration = 200;  
  17.     vibrateService.callMethod<void>("vibrate""(J)V", duration);  
  18.     CHECK_EXCEPTION();  
  19. }  

    看着是不是很熟悉呢?其实使用 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 中 标签的 “android:screenOrientation” 属性。那其实, android.app.Activity 类也提供了 “void setRequestedOrientation(int requestedOrientation)” 方法,允许我们通过代码来调整一个 Activity 的显示方向。

    再简单的说一下:Android的一个应用,可能有多个Activity,每个Activity都可以有自己的屏幕显示方向,即,屏幕显示方向,是Activity的特性。

    当你点击图1中的“ScreenOrientation”按钮,就会调用到槽 onScreenOrientation() ,代码如下:

[cpp]  view plain copy
  1. void Widget::onScreenOrientation()  
  2. {  
  3.     QAndroidJniEnvironment env;  
  4.     QAndroidJniObject activity = androidActivity();  
  5.     jint orient = activity.callMethod(  
  6.                 "getRequestedOrientation"  
  7.                 );  
  8.     CHECK_EXCEPTION();  
  9.     if(orient == 1)  
  10.     {  
  11.         orient = 0;  
  12.     }  
  13.     else  
  14.     {  
  15.         orient = 1;  
  16.     }  
  17.     activity.callMethod<void>(  
  18.                 "setRequestedOrientation",  
  19.                 "(I)V", orient);  
  20.     CHECK_EXCEPTION();  
  21. }  

    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 方法来获取,不过我在代码里偷了个懒,直接用数字了。关于铃声模式对应的宏:

[cpp]  view plain copy
  1. #define RINGER_MODE_NORMAL  2  
  2. #define RINGER_MODE_SILENT  0  
  3. #define RINGER_MODE_VIBRATE 1  

    如图1所示,铃声模式是通过一组单选按钮(QRadioButton)来选择的。我在代码里使用 QButtonGroup 来管理三个单选按钮,同时将每个按钮的 id 设置为它代表的铃声模式。这样当 QButtonGroup 的“buttonClicked(int id)”触发时,我们的槽“void Widget::onRingerModeClicked(int mode)”被调用,参数直接就是响铃模式了,很方便。

    那先看看这组铃声模式单选按钮初始化的代码:

[cpp]  view plain copy
  1. void Widget::initRingerMode(QVBoxLayout *layout)  
  2. {  
  3.     QAndroidJniEnvironment env;  
  4.     QAndroidJniObject activity = androidActivity();  
  5.     QAndroidJniObject name = QAndroidJniObject::getStaticObjectField(  
  6.                 "android/content/Context",  
  7.                 "AUDIO_SERVICE",  
  8.                 "Ljava/lang/String;"  
  9.                 );  
  10.     CHECK_EXCEPTION();  
  11.     QAndroidJniObject audioService = activity.callObjectMethod(  
  12.                 "getSystemService",  
  13.                 "(Ljava/lang/String;)Ljava/lang/Object;",  
  14.                 name.object());  
  15.     CHECK_EXCEPTION();  
  16.   
  17.     int mode = audioService.callMethod(  
  18.                 "getRingerMode",  
  19.                 "()I"  
  20.                 );  
  21.     CHECK_EXCEPTION();  
  22.     layout->addWidget(new QLabel("Ringer Mode:"));  
  23.     QHBoxLayout *rowLayout = new QHBoxLayout();  
  24.     layout->addLayout(rowLayout);  
  25.     rowLayout->addSpacing(30);  
  26.     m_ringerModeGroup = new QButtonGroup(this);  
  27.     QRadioButton *normal = new QRadioButton("Normal");  
  28.     m_ringerModeGroup->addButton(normal, RINGER_MODE_NORMAL);  
  29.     rowLayout->addWidget(normal);  
  30.     QRadioButton *silent = new QRadioButton("Silent");  
  31.     m_ringerModeGroup->addButton(silent, RINGER_MODE_SILENT);  
  32.     rowLayout->addWidget(silent);  
  33.     QRadioButton *vibrate = new QRadioButton("Vibrate");  
  34.     m_ringerModeGroup->addButton(vibrate, RINGER_MODE_VIBRATE);  
  35.     rowLayout->addWidget(vibrate);  
  36.   
  37.     switch(mode)  
  38.     {  
  39.     case RINGER_MODE_NORMAL:  
  40.         normal->setChecked(true);  
  41.         break;  
  42.     case RINGER_MODE_SILENT:  
  43.         silent->setChecked(true);  
  44.         break;  
  45.     case RINGER_MODE_VIBRATE:  
  46.         vibrate->setChecked(true);  
  47.         break;  
  48.     }  
  49.   
  50.     connect(m_ringerModeGroup, SIGNAL(buttonClicked(int)),  
  51.             this, SLOT(onRingerModeClicked(int)));  
  52. }  

    在上面的代码里,我还调用 android.media.AudioManager 的 “int getRingerMode()” 方法获取了系统当前的铃声模式,然后选中对应的单选按钮。

    下面是 onRingerModeClicked 槽:

[cpp]  view plain copy
  1. void Widget::onRingerModeClicked(int mode)  
  2. {  
  3.     QAndroidJniEnvironment env;  
  4.     QAndroidJniObject activity = androidActivity();  
  5.     QAndroidJniObject name = QAndroidJniObject::getStaticObjectField(  
  6.                 "android/content/Context",  
  7.                 "AUDIO_SERVICE",  
  8.                 "Ljava/lang/String;"  
  9.                 );  
  10.     CHECK_EXCEPTION();  
  11.     QAndroidJniObject audioService = activity.callObjectMethod(  
  12.                 "getSystemService",  
  13.                 "(Ljava/lang/String;)Ljava/lang/Object;",  
  14.                 name.object());  
  15.     CHECK_EXCEPTION();  
  16.   
  17.     audioService.callMethod<void>(  
  18.                 "setRingerMode""(I)V", mode  
  19.                 );  
  20.     CHECK_EXCEPTION();  
  21. }  

    代码和onVibrate()如出一辙,不再解释了。

保持屏幕常亮

    有些应用,比如视频类的,在运行时不希望Android手机休眠、锁屏,这时就需要保持屏幕常亮。

    在 Android 上有两个办法来让屏幕常亮,一种是在 Activity 的 onCreate() 方法中,在调用 setContentView() 之前加入下面的代码:

[java]  view plain copy
  1. 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 ,代码如下:

[cpp]  view plain copy
  1. void Widget::onScreenOnChecked(bool checked)  
  2. {  
  3.     QAndroidJniEnvironment env;  
  4.     QAndroidJniObject activity = androidActivity();  
  5.   
  6.     if(m_lastChecked)  
  7.     {  
  8.         if(m_wakeLock.isValid())  
  9.         {  
  10.             m_wakeLock.callMethod<void>("release");  
  11.             CHECK_EXCEPTION();  
  12.         }  
  13.         m_lastChecked = false;  
  14.         return;  
  15.     }  
  16.   
  17.     QAndroidJniObject name = QAndroidJniObject::getStaticObjectField(  
  18.                 "android/content/Context",  
  19.                 "POWER_SERVICE",  
  20.                 "Ljava/lang/String;"  
  21.                 );  
  22.     CHECK_EXCEPTION();  
  23.     QAndroidJniObject powerService = activity.callObjectMethod(  
  24.                 "getSystemService",  
  25.                 "(Ljava/lang/String;)Ljava/lang/Object;",  
  26.                 name.object());  
  27.     CHECK_EXCEPTION();  
  28.     QAndroidJniObject tag = QAndroidJniObject::fromString("QtJniWakeLock");  
  29.     m_wakeLock = powerService.callObjectMethod(  
  30.                 "newWakeLock",  
  31.                 "(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;",  
  32.                 10, //SCREEN_BRIGHT_WAKE_LOCK  
  33.                 tag.object()  
  34.                 );  
  35.     CHECK_EXCEPTION();  
  36.     if(m_wakeLock.isValid())  
  37.     {  
  38.         m_wakeLock.callMethod<void>("acquire");  
  39.         CHECK_EXCEPTION();  
  40.     }  
  41.     m_lastChecked = true;  
  42. }  

    这里需要说明一点,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 ,代码如下:

[cpp]  view plain copy
  1. void Widget::onBrightnessChanged(int value)  
  2. {  
  3.     QAndroidJniEnvironment env;  
  4.     QAndroidJniObject activity = androidActivity();  
  5.     QAndroidJniObject contentResolver = activity.callObjectMethod(  
  6.                 "getContentResolver",  
  7.                 "()Landroid/content/ContentResolver;"  
  8.                 );  
  9.     CHECK_EXCEPTION();  
  10.   
  11.     //set brightness mode to MANUAL  
  12.     QAndroidJniObject brightnessTag = QAndroidJniObject::fromString("screen_brightness");  
  13.     QAndroidJniObject brightnessModeTag = QAndroidJniObject::fromString("screen_brightness_mode");  
  14.     bool ok = QAndroidJniObject::callStaticMethod(  
  15.                 "android/provider/Settings$System",  
  16.                 "putInt",  
  17.                 "(Landroid/content/ContentResolver;Ljava/lang/String;I)Z",  
  18.                 contentResolver.object(),  
  19.                 brightnessModeTag.object(),  
  20.                 0  
  21.                 );  
  22.     CHECK_EXCEPTION();  
  23.     qDebug() << "set brightness mode to MANUAL - " << ok;  
  24.   
  25.     //set brightness to value  
  26.     ok = QAndroidJniObject::callStaticMethod(  
  27.                 "android/provider/Settings$System",  
  28.                 "putInt",  
  29.                 "(Landroid/content/ContentResolver;Ljava/lang/String;I)Z",  
  30.                 contentResolver.object(),  
  31.                 brightnessTag.object(),  
  32.                 value  
  33.                 );  
  34.     CHECK_EXCEPTION();  
  35.     qDebug() << "set brightness to " << value << " result - " << ok;  
  36. }  

    你可能注意到 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 代码如下:

[java]  view plain copy
  1. WindowManager.LayoutParams lp = window.getAttributes();  
  2. lp.screenBrightness = brightness;  
  3. window.setAttributes(lp);  

    如果你要在 Qt 中通过 JNI 来实现上面的 Java 代码,可能会出错,因为,上面的代码必须在 Android Activity 所在的 UI 线程中调用,而 Qt 的线程,和 Activity 所在的线程,是两个不同的线程。详细的分析,请参看《 Qt on Android核心编程 》的第13章——“Qt on Android揭秘”。


------------

    额滴神呢,终于说完了耶。看起来不像是在讲 Qt C++ 代码,而是在介绍 Android 类库……不可否认的是,要使用 QAndroidJniObject 进行 JNI 开发,确实是酱紫的。

    项目源码下载:Qt JNI调用Android系统功能。


  回顾一下:

  • QtAndroid详解(4):JNI调用Android系统功能(1)
  • QtAndroid详解(3):startActivity实战Android拍照功能
  • QtAndroid详解(2):startActivity和它的小伙伴们
  • QtAndroid详解(1):QAndroidJniObject
  • Qt on Android专栏

你可能感兴趣的:(Qt,qt,qt,for,android)