0. 写在最前面
本人参考安晓辉大侠的一篇博文后,做了Qt on android的GSP相关的实验,为了后面不时之需,故而记录下来。
1. Qt on Android GPS系统流程
图1. 系统流程图
如图1所示,系统含两个层面:其一为基于QT的UI,提供启动GPS的按钮(QPushButton),以及显示GPS信号的文本域(QTextBrowser);其二为基于Activity的GPS服务,提供GPS的启动,GPS信号上报等服务。两个层面的交互及C++与Java的交互通过JNI来实现。
系统的整体线索如箭头方式所示:在QT层,按钮被点击后,槽函数startGps被触发,该函数调用Activity层的方法calledByCpp,而calledByCpp方法发送消息到mes handle,消息类型为MSG_STR_GPS_LOC,handle收到消息后,调用函数startAmap启动GPS的定位。GPS启动后,位置的实时信息在函数onLocationChanged()函数中发布,该函数把位置信息通过消息发送给msg handle,其中消息类型为MSG_RPT_GSP_INF,handle再调用native方法reportGpsInfo,就可以把消息发送给QT层。
2. 创建Android工程
图2 创建工程
工程命名为QtAndroidGps,且其路径为E:\work\c++\qt,工程创建之后,目录结构如下:
图3. 工程初始文件
在上述自动生成目录下加入文件夹android:
图4. 加入文件夹android后
文件夹android里面包含如下内容:
图5. 文件夹android的内容
可以看出,目录结构与通过eclipse生成的android工程类似,少了assets,bin,gen,res等目录,以及project.properties等文件。
本工程可采用百度与高德的库来定位,其相关的jar包分别为BaiduLBS_Android.jar与Android_Location_V1.1.2.jar,这两个包均放入到e:\work\c++\qt\QtAndroid\android\libs。
同时,在构建工程QtAndroidGps之后,qt会在路径e:\work\c++\qt\之下自动生成目录:
E:\work\c++\qt\build-QtAndroidGps-Android_for_armeabi_v7a_GCC_4_8_Qt_5_4_0-Debug,该目录包含以下文件:
图6. qt构建工程后生成的工程目录
其中,android-build目录包含qt构建出来的android工程,该工程即为完整的android工程:
图7. qt构建工程后生成的android工程目录
可以发现,之前我们人为创建的工程e:\work\c++\qt\QtAndroid\android\里面的文件除了AnroidManifest.xml被整合外,其他文件包括jar包和java文件都被拷贝入:
build-QtAndroidGps-Android_for_armeabi_v7a_GCC_4_8_Qt_5_4_0-Debug。
3. 工程文件清单3.1 E:\work\c++\qt\QtAndroidGps\android\AndroidManifest.xml
......
图8. AndroidManifest.xml
该文件有如上图标注的4个重要点:
① ->与E:\work\c++\qt\QtAndroidGps\android\src中java文件的包路径对应
② ->要把百度与高德的包用起来,必须加入Api-key
③ ->对应Activity的类名
④ ->需要开启GPS定位相关的权限
3.2.1 E:\work\c++\qt\QtAndroidGps\mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
bool event(QEvent *e);
private:
Ui::MainWindow *ui;
QPushButton *btn; // 点击启动GPS
private slots:
void startGps(); // btn对应的点击槽函数
};
#endif // MAINWINDOW_H
3.2.2
E:\work\c++\qt\QtAndroidGps\
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QAndroidJniObject>
#include "CustomEvent.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
btn = new QPushButton("start", this);
btn->setGeometry(QRect(10, 10, 100, 50));
connect(btn, SIGNAL(clicked()), this, SLOT(startGps()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::startGps()
{
//QAndroidJniObject javaAction = QAndroidJniObject::fromString(url);
QAndroidJniObject::callStaticMethod<void>( /* 调用java方法,详细情况请参考qt的帮助文档 */
"com/mtn/mes/GpsService",
"calledByCpp",
"()V");
}
bool MainWindow::event(QEvent *e)
{
if(e->type() == CustomEvent::eventType()){ /* 匹配上自定义事件类型 */
e->accept();
CustomEvent *sce = (CustomEvent*)e;
//_resultView->setText(sce->m_arg2);
ui->textBrowser->setText(sce->m_arg2); /* 显示GPS信息 */
return true;
}
return QWidget::event(e);
}
3.3.1 E:\work\c++\qt\QtAndroidGps\ CustomEvent.h
#ifndef CUSTOMEVENT_H
#define CUSTOMEVENT_H
#include <QEvent>
#include <QString>
class CustomEvent : public QEvent
{
public:
CustomEvent(int arg1 = 0, const QString &arg2 = QString());
~CustomEvent();
static Type eventType();
int m_arg1;
QString m_arg2;
private:
static Type m_evType;
};
#endif // CUSTOMEVENT_H
3.3.2
E:\work\c++\qt\QtAndroidGps\
CustomEvent.cpp
#include "CustomEvent.h"
QEvent::Type CustomEvent::m_evType = (QEvent::Type)QEvent::None;
CustomEvent::CustomEvent(int arg1, const QString &arg2)
: QEvent(eventType()), m_arg1(arg1), m_arg2(arg2)
{}
CustomEvent::~CustomEvent()
{
}
QEvent::Type CustomEvent::eventType()
{
if(m_evType == QEvent::None)
{
m_evType = (QEvent::Type)registerEventType(); /* 注册自定义事件,返回m_evType值为qt分配的自定义事件类型 */
}
return m_evType;
}
3.4
E:\work\c++\qt\QtAndroidGps\
CustomEvent.cpp
#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include <jni.h>
#include <QAndroidJniEnvironment>
#include <QAndroidJniObject>
#include "CustomEvent.h"
QObject *main_window = 0;
/* 该函数被java的本地方法调用 */
static void reportGpsInfo(JNIEnv *env, jobject thiz,int result, jstring data)
{
QString qstrData;
const char *nativeString = env->GetStringUTFChars(data, 0);
qstrData = nativeString;
env->ReleaseStringUTFChars(data, nativeString);
QCoreApplication::postEvent(main_window, new CustomEvent(result, qstrData)); /* 发送事件(携带GPS信息)给主窗口,自定义事件类型 */
qDebug() << qstrData;
}
bool registerNativeMethods()
{
JNINativeMethod methods[] {
{"reportGpsInfo", "(ILjava/lang/String;)V", (void*)reportGpsInfo} /* 注册本地方法,java方可调用,详细信息见qt帮助文档 */
};
const char *classname = "com/mtn/mes/ExtendsQtNative";
jclass clazz;
QAndroidJniEnvironment env;
QAndroidJniObject javaClass(classname);
clazz = env->GetObjectClass(javaClass.object<jobject>());
qDebug() << "find ExtendsQtNative - " << clazz;
bool result = false;
if(clazz) {
jint ret = env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(clazz);
qDebug() << "RegisterNatives return - " << ret;
result = ret >= 0;
}
if(env->ExceptionCheck()) env->ExceptionClear();
return result;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
CustomEvent::eventType(); // 注册自定义事件,生成自定义事件类型
registerNativeMethods(); // 注册本地方法
w.show();
main_window = qobject_cast<QObject*>(&w);
return a.exec();
}
3.5
E:\work\c++\qt\QtAndroidGps\android\src\com\mtn\mes\ExtendsQtNative.java
package com.mtn.mes;
import java.lang.String;
public class ExtendsQtNative
{
public native void reportGpsInfo(int result, String content);
}
3.6
E:\work\c++\qt\QtAndroidGps\android\src\com\mtn\mes\
GpsService.java
package com.mtn.mes;
import java.lang.String;
import android.content.Context;
import android.content.Intent;
import android.app.PendingIntent;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.location.Criteria;
import android.provider.Settings;
import android.os.Bundle;
import android.os.Environment;
import java.io.File;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import android.widget.Toast;
import java.util.Date;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import com.amap.api.location.AMapLocation;
import com.amap.api.location.AMapLocationListener;
import com.amap.api.location.LocationManagerProxy;
import com.amap.api.location.LocationProviderProxy;
import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.location.LocationClientOption.LocationMode;
public class GpsService extends org.qtproject.qt5.android.bindings.QtActivity
{
private static GpsService m_instance;
private final static String TAG = "GpsService";
private final static int MSG_STR_GPS_LOC = 0;
private final static int MSG_RPT_GPS_INF = 1;
// 定位相关
private LocationManagerProxy aMapManager;
public GpsService(){
m_instance = this;
}
public static void calledByCpp() {
System.out.println("[0]hello world!");
Message msg = new Message();
msg.what = MSG_STR_GPS_LOC;
m_instance.handler.sendMessage(msg); // 消息触发,启动GPS定位
//m_instance.handler.sendEmptyMessage(0); // 消息触发,启动GPS定位
}
public static void calledByCpp(int arg0) {
System.out.println("[1]hello world!");
}
private void startAmap() {
aMapManager = LocationManagerProxy.getInstance(this);
/*
* mAMapLocManager.setGpsEnable(false);
* 1.0.2版本新增方法,设置true表示混合定位中包含gps定位,false表示纯网络定位,默认是true Location
* API定位采用GPS和网络混合定位方式
* ,第一个参数是定位provider,第二个参数时间最短是2000毫秒,第三个参数距离间隔单位是米,第四个参数是定位监听者
*/
aMapManager.requestLocationUpdates(LocationProviderProxy.AMapNetwork, 2000, 10, mAMapLocationListener);
}
private AMapLocationListener mAMapLocationListener = new AMapLocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onLocationChanged(Location location) {
}
@Override
public void onLocationChanged(AMapLocation location) {
if (location != null) {
Double geoLat = location.getLatitude();
Double geoLng = location.getLongitude();
String cityCode = "";
String desc = "";
Bundle locBundle = location.getExtras();
if (locBundle != null) {
cityCode = locBundle.getString("citycode");
desc = locBundle.getString("desc");
}
String str = (
"location ok:(" + geoLng + "," + geoLat + ")"+
"\nAccuracy :" + location.getAccuracy() + "Meter" +
"\nPositioning:" + location.getProvider() +
"\nPositioning time:" + new Date(location.getTime()).toLocaleString() +
"\nCity coding :" + cityCode +
"\nLocation Description:" + desc +
"\nProvince:" + location.getProvince() +
"\nCity:" + location.getCity() +
"\nDistrict (county):" + location.getDistrict() +
"\nRegional Coding:" + location.getAdCode());
//Toast.makeText(getApplicationContext(), "高德定位\n" + str, Toast.LENGTH_SHORT).show();
// 发送位置信息到handler, hander处再转发给Qt
Message msg = new Message();
Bundle data = new Bundle();
data.putString("value", str);
msg.setData(data);
msg.what = MSG_RPT_GPS_INF;
handler.sendMessage(msg);
}
}
};
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_STR_GPS_LOC: // 消息类型为启动GPS定位
m_instance.startAmap();
break;
case MSG_RPT_GPS_INF: // 消息类型为上送GSP信息
ExtendsQtNative m_nativeNotify = new ExtendsQtNative();
Bundle data = msg.getData();
System.out.println(data.getString("value"));
m_nativeNotify.reportGpsInfo(0, data.getString("value")); // 掉用c++方法
break;
default:
System.out.println("msg type error!");
break;
}
}
};
}