笔者接触到的PDA扫码枪,有三种, 一种是蓝牙枪,连接到手机或者电脑上之后,可以直接把扫描到的数据传送到焦点所在的文本框中;另外两种是普通的掌上扫码枪,笔者手中的一款也可以和蓝牙枪一样,操作系统中有设置,可以把扫描到的数据直接送到焦点所在的文本框中;还有一款是操作系统中没并有这个设置,而是通过Android的广播机制来进行发送,而前一种实际也是利用广播机制。
在Android Studio中,这一机制还是比较容易实现的,下面这一段Kotlin代码可以实现:
而在Qt中,如果要利用Android的广播,C++和QML都无法直接实现,需要用到Java。Qt官方有两个实例:Jni Messenger,Android Service with BroadcastReceiver,在Qt的欢迎界面搜索就可以查看源代码。这两个实例比较好的解释了广播机制的运用。
笔者这里参考了一篇文章:QT for android 获取PDA扫码的广播数据并在QML中显示,这篇文章中实现了通过广播获取扫描到的数据的过程,但是这个数据只停留在Java中,C++页面如果想获取数据,必须要C++自己去Java中拿,Java不能直接把数据推给C++。博主的方法可能是设置个时间,然后不停地刷新向Java要数据。
笔者这里的实现是以Qt的实例Android Service with BroadcastReceiver为基础,作了下修改。在QT for android 获取PDA扫码的广播数据并在QML中显示这篇博文中,还有QtAndroid详解(1):QAndroidJniObject这一篇中,都有介绍怎么让C++获取Java的数据,所以笔者这里加入这一块,代码只有Java主动推送给C++的功能。
上面两篇博文中好像都没有写到对这个文件的修改,实际上这个文件很重要,在Qt6.0版本之后,QtCreater默认显示的界面,只展示了可以让开发者修改的部分,避免修改不必要的部分。QT for android 获取PDA扫码的广播数据并在QML中显示,这篇文章中的MainActivity.java是扩展了Activity类,所以有一处修改:
而Qt官方的实例Android Service with BroadcastReceiver,这里没有修改,但是也对AndroidManifest.xml文件有要求,修改的地方如下:
原本是false,需要改为true。
Qt官方的实例Android Service with BroadcastReceiver中引入了两个Java文件,ActivityUtils.java和QtAndroidService.java,这两个文件的头一行均是package org.qtproject.example.qtandroidservice;
如果要自己使用这两个文件的话,这里需要更改为自己的包名,同样,也是在AndroidManifest.xml一起修改:
需要注意的是,这两个Java文件不是放在.pro文件同级目录中,Qtt生成AndroidManifest.xml文件时会生成一个android文件夹,在这下面新建一个src文件夹,然后根据包名往下新建文件夹:包名是org.qtproject.example的话,就依次新建org->qtproject->example,然后把两个Java文件放在example文件夹中,最后在QtCreater中添加这两个文件。
工程项目源文件不附了,这里附4个文件的源码,如前述,主功能为Java通过广播获取PDA扫描的数据,然后把数据主动推给C++,笔者这里没有用QML,C++与QML通信请参考其他。
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
static MainWindow *instance() { return m_instance; }
public slots:
void slot_test(QString);
private:
Ui::MainWindow *ui;
private:
void registerNatives();
void registerBroadcastReceiver();
static MainWindow *m_instance;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
MainWindow *MainWindow::m_instance = nullptr;
static void receivedFromAndroidService(JNIEnv *env, jobject /*thiz*/, jstring value)
{
emit MainWindow::instance()->slot_test(env->GetStringUTFChars(value, nullptr));
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_instance = this;
registerNatives();
registerBroadcastReceiver();
}
void MainWindow::slot_test(QString x)
{
ui->textBrowser->append(x);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::registerNatives()
{
JNINativeMethod methods[] {
{"sendToQt", "(Ljava/lang/String;)V", reinterpret_cast<void *>(receivedFromAndroidService)}};
QAndroidJniObject javaClass("org/qtproject/example/ActivityUtils");
QAndroidJniEnvironment env;
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
env->RegisterNatives(objectClass,
methods,
sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(objectClass);
}
void MainWindow::registerBroadcastReceiver()
{
QAndroidJniEnvironment env;
jclass javaClass = env.findClass("org/qtproject/example/ActivityUtils");
QAndroidJniObject classObject(javaClass);
classObject.callMethod<void>("registerServiceBroadcastReceiver",
"(Landroid/content/Context;)V",
QtAndroid::androidContext().object());
}
ActivityUtils.java
package org.qtproject.example;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
public class ActivityUtils {
private static native void sendToQt(String message);
private static final String TAG = "ActivityUtils";
public static final String BROADCAST_NAME_ACTION = "android.intent.ACTION_DECODE_DATA";
public void registerServiceBroadcastReceiver(Context context) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_NAME_ACTION);
context.registerReceiver(serviceMessageReceiver, intentFilter);
Log.i(TAG, "Registered broadcast receiver");
}
private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "In OnReceive broadcast receiver");
if (BROADCAST_NAME_ACTION.equals(intent.getAction())) {
String name = intent.getStringExtra("barcode_string");
Log.i(TAG, "Service received name: " + name);
String message = "Hello " + name;
sendToQt(message);
Log.i(TAG, "Service sent back message: " + message);
}
}
};
}
QtAndroidService.java
package org.qtproject.example;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.os.IBinder;
import org.qtproject.qt5.android.bindings.QtService;
import android.content.IntentFilter;
public class QtAndroidService extends QtService
{
private static final String TAG = "QtAndroidService";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Creating Service");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Destroying Service");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int ret = super.onStartCommand(intent, flags, startId);
String name = new String(intent.getByteArrayExtra("barcode_string"));
Intent sendToUiIntent = new Intent();
sendToUiIntent.setAction(ActivityUtils.BROADCAST_NAME_ACTION);
Log.i(TAG, "Service sending broadcast");
return ret;
}
@Override
public IBinder onBind(Intent intent) {
return super.onBind(intent);
}
}
笔者的Qt版本更新到了6.2.4,但是官方的例程是5.15.2,在6.0版本之后,Qt取消了androidextras模块,Android相关的QAndroidJniObject、QAndroidJniEnvironment等都并入了core中,所以如果使用6.0之后版本尝试的话,在.pro文件中不需要再添加QT+=androidextras。
更新日期:2022-03-25,此外,整个过程并没有研究透,所以博文写的也不透亮,稍后会写更详细。
private String barcodeStr;
public static final String BROADCAST_NAME_ACTION = "urovo.rcv.message";、
byte[] barcode = intent.getByteArrayExtra("barcode");
int barocodelen = intent.getIntExtra("length", 0);
barcodeStr = new String(barcode, 0, barocodelen);