在“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());
CHECK_EXCEPTION();
jlong duration = 200;
vibrateService.callMethod("vibrate", "(J)V", duration);
CHECK_EXCEPTION();
}
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的一个应用,可能有多个Activity,每个Activity都可以有自己的屏幕显示方向,即,屏幕显示方向,是Activity的特性。
当你点击图1中的“ScreenOrientation”按钮,就会调用到槽 onScreenOrientation() ,代码如下:
void Widget::onScreenOrientation()
{
QAndroidJniEnvironment env;
QAndroidJniObject activity = androidActivity();
jint orient = activity.callMethod(
"getRequestedOrientation"
);
CHECK_EXCEPTION();
if(orient == 1)
{
orient = 0;
}
else
{
orient = 1;
}
activity.callMethod(
"setRequestedOrientation",
"(I)V", orient);
CHECK_EXCEPTION();
}
手机来电铃声,一般有普通(响铃)、静音、震动三种模式,对应在代码里呢,是通过 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
那先看看这组铃声模式单选按钮初始化的代码:
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());
CHECK_EXCEPTION();
int mode = audioService.callMethod(
"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)));
}
下面是 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());
CHECK_EXCEPTION();
audioService.callMethod(
"setRingerMode", "(I)V", mode
);
CHECK_EXCEPTION();
}
有些应用,比如视频类的,在运行时不希望Android手机休眠、锁屏,这时就需要保持屏幕常亮。
在 Android 上有两个办法来让屏幕常亮,一种是在 Activity 的 onCreate() 方法中,在调用 setContentView() 之前加入下面的代码:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
android.os.PowerManager 可以控制屏幕是否常亮,它的服务名字为 Context.POWER_SERVICE 。PowerManager 的方法 “WakeLock newWakeLock(int flag, String tag)” 可以获取一个 WakeLock 实例, 其中 flag 可以是下列几种:
我在示例中使用 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("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());
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()
);
CHECK_EXCEPTION();
if(m_wakeLock.isValid())
{
m_wakeLock.callMethod("acquire");
CHECK_EXCEPTION();
}
m_lastChecked = true;
}
我还给应用添加了 WAKE_LOCK 权限。
Android手机的设置界面中可以调整屏幕亮度,这是通过修改 Settings 来实现的。一个应用要想调整 Settings ,需要请求 WRITE_SETTINGS 权限。
android.provider.Settings.System 这个类提供了下面两个静态方法:
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(
"android/provider/Settings$System",
"putInt",
"(Landroid/content/ContentResolver;Ljava/lang/String;I)Z",
contentResolver.object(),
brightnessModeTag.object(),
0
);
CHECK_EXCEPTION();
qDebug() << "set brightness mode to MANUAL - " << ok;
//set brightness to value
ok = QAndroidJniObject::callStaticMethod(
"android/provider/Settings$System",
"putInt",
"(Landroid/content/ContentResolver;Ljava/lang/String;I)Z",
contentResolver.object(),
brightnessTag.object(),
value
);
CHECK_EXCEPTION();
qDebug() << "set brightness to " << value << " result - " << ok;
}
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 C++ 代码,而是在介绍 Android 类库……不可否认的是,要使用 QAndroidJniObject 进行 JNI 开发,确实是酱紫的。
项目源码下载:Qt JNI调用Android系统功能。
回顾一下: