Qt官方的Webview仅仅在Qtquick中支持安卓和ios,安卓的官方实现是调用安卓系统自带的浏览器API,但是Qt官方没有写js交互,于是研究了一通宵得出几个解决方案:
1.使用Qt官方的QML webview和HTML5的Websockt,在js中使用websockt,在qt qucik中使用websocket服务器,结果可行,可惜安卓很多版本不支持Websockt,于是乎3个小时过去了。
2.既然websockt行不通,就想了下socket.io库,能运行~可惜qt quick的Websockt服务器不支持socket.io的协议,于是乎 3小时又浪费了
3.使用HTML sockt或post,post要执行的函数,用QML的eval函数来执行字符串代码,于是乎用xhl来发送post数据,然后用C++建立TcpSocket来接收,但是我放弃了,解析HTML封包不划算,为了发送4个字符他会发一大堆HTML协议字符串…因为 socket由C++接收.websockt在 qt quick中,js post->sockt->qt quick 要跨3层交互才收的到数据,实在蛋疼…更重要的是还要避免意外情况,于是乎2小时又浪费了
4.最后花了5个小时,在国外看到一些开源代码,经过修改 实现了原生交互,依然是调用安卓的webview,内嵌到qt中
具体实现:c++ –> java –> js
因为安卓的浏览器和java的交互是最稳定的
先生成Qt 安卓项目,先编译一次,在目录下新建android文件夹(QT中可以创建) 和 assets文件夹
把androud-build中的src目录下全部文件复制到android目录,打开eclipse,在QtActivity包下添加一个类:
下面代码是参考过来的:
package org.qtproject.qt5.android.bindings;
public interface NativeCalls {
public void createNewWebView(int tag);
public void removeWebView(int tag);
public void moveWebView(int tag, int x, int y);
public void resizeWebView(int tag, int w, int h);
public void attachWebViewToMainLayout(int tag);
public void loadUrlAtWebView(int tag, String url);
public void loadHtmlAtWebView(int tag, String html);
}
然后再添加一个类,用来继承webview,定制一些功能,比如开启js和弹窗等,并且添加JavaScript交互接口,接口函数在C++中定义,在JAVA中声明、
package org.qtproject.qt5.android.bindings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.webkit.*;
import android.content.Context;
import android.util.Log;
public class Cjavascript {
public Cjavascript ( ) {
}
public native int testcall2();
public native void testcall3();
public native void testcall4();
public void testcall()
{
Log.i("testcalltestcalltestcalltestcall", "call:");
}
}
package org.qtproject.qt5.android.bindings;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
init();
// TODO 自动生成的构造函数存根
}
@SuppressLint("SetJavaScriptEnabled")
public void init() {
this.getSettings().setJavaScriptEnabled(true);
this.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
//否则不会执行js的Alert
this.setWebChromeClient(new WebChromeClient()
{ public boolean onJsAlert(WebView view, String url, String message,
JsResult result)
{
//Auto-generated method stub
return super.onJsAlert(view, url, message, result);
}
});
this.setWebViewClient(new WebViewClient());
this.setBackgroundColor(Color.BLACK);
this.addJavascriptInterface(new Cjavascript(), "obj");
}
}
最后就是修改Qt自己生成的QtActivity类,在作用域中添加以下代码即可:
下面的代码大部分参考别人的,少量修改。官方源码中都是使用RunOnUiThread,下面的是用handle进行多线程界面操作。
//-------------WEBVIEW----------------
protected ArrayList<MyWebView> webViewsList = new ArrayList<MyWebView>();
//-----------------------------------------------------------------------------
protected MyWebView findWebViewByTag(int tag) {
MyWebView webViewRes = null;
for (int i = 0; i < webViewsList.size(); i++) {
MyWebView wv = (MyWebView) webViewsList.get(i);
if (((Integer)wv.getTag()).intValue() == tag) {
webViewRes = wv;
break;
}
}
return webViewRes;
}
//-----------------------------------------------------------------------------
@Override
public void createNewWebView(int tag) {
Message msg = new Message();
msg.what = tag;
createNewWebViewHandler.sendMessage(msg);
}
protected Handler createNewWebViewHandler = new Handler() {
@SuppressLint("SetJavaScriptEnabled")
@Override
public void handleMessage(Message msg) {
MyWebView webView = new MyWebView(QtActivity.this);
webView.setTag(msg.what);
// webView.getSettings().setJavaScriptEnabled(true);
// webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
// //否则不会执行js的Alert
// webView.setWebChromeClient(new WebChromeClient()
// { public boolean onJsAlert(WebView view, String url, String message,
// JsResult result)
// {
// //Auto-generated method stub
// return super.onJsAlert(view, url, message, result);
// }
//
// });
// webView.setWebViewClient(new WebViewClient());
// webView.setBackgroundColor(Color.BLACK);
webViewsList.add(webView);
}
};
//-----------------------------------------------------------------------------
@Override
public void removeWebView(int tag) {
Message msg = new Message();
msg.what = tag;
removeWebViewHandler.sendMessage(msg);
}
protected Handler removeWebViewHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
MyWebView webView = findWebViewByTag(msg.what);
if (webView != null) {
webViewsList.remove(webView);
FrameLayout mainLayout = (FrameLayout) findViewById(R.id.content);
mainLayout.removeView(webView);
}
}
};
//-----------------------------------------------------------------------------
@Override
public void moveWebView(int tag, int x, int y) {
Message msg = new Message();
msg.what = tag;
msg.arg1 = x;
msg.arg2 = y;
moveWebViewHandler.sendMessage(msg);
}
protected Handler moveWebViewHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
View viewToMove = null;
viewToMove = findWebViewByTag(msg.what);
if (viewToMove != null) {
FrameLayout.LayoutParams params = (android.widget.FrameLayout.LayoutParams) viewToMove
.getLayoutParams();
params.leftMargin = msg.arg1;
params.topMargin = msg.arg2;
viewToMove.setLayoutParams(params);
}
}
};
//-----------------------------------------------------------------------------
@Override
public void resizeWebView(int tag, int w, int h) {
Message msg = new Message();
msg.what = tag;
msg.arg1 = w;
msg.arg2 = h;
resizeWebViewHandler.sendMessage(msg);
}
protected Handler resizeWebViewHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
View viewToResize = null;
viewToResize = findWebViewByTag(msg.what);
if (viewToResize != null) {
FrameLayout.LayoutParams params = (android.widget.FrameLayout.LayoutParams) viewToResize
.getLayoutParams();
params.width = msg.arg1;
params.height = msg.arg2;
viewToResize.setLayoutParams(params);
}
}
};
//-----------------------------------------------------------------------------
@Override
public void attachWebViewToMainLayout(int tag) {
Message msg = new Message();
msg.what = tag;
attachWebViewToMainLayoutHandler.sendMessage(msg);
}
protected Handler attachWebViewToMainLayoutHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
View viewToAttach = null;
viewToAttach = findWebViewByTag(msg.what);
if (viewToAttach != null) {
FrameLayout mainLayout = (FrameLayout) findViewById(R.id.content);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(100, 100);
params.leftMargin = 0;
params.topMargin = 0;
mainLayout.addView(viewToAttach, params);
}
}
};
//-----------------------------------------------------------------------------
@Override
public void loadUrlAtWebView(int tag, String url) {
Log.i("url", "curid:"+Thread.currentThread().getId());
Message msg = new Message();
msg.what = tag;
msg.obj = url;
loadUrlAtWebViewHandler.sendMessage(msg);
}
protected Handler loadUrlAtWebViewHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.i("url", "mainid:"+Thread.currentThread().getId());
MyWebView webView = findWebViewByTag(msg.what);
if (webView != null) {
webView.loadUrl(msg.obj.toString());
Log.i("url", msg.obj.toString());
}
//Log.i("TestNative*********", "call:");
//TestNative testNative = new TestNative();
//testNative.Test();
}
};
//-----------------------------------------------------------------------------
@Override
public void loadHtmlAtWebView(int tag, String html) {
Message msg = new Message();
msg.what = tag;
msg.obj = html;
loadHtmlAtWebViewHandler.sendMessage(msg);
}
protected Handler loadHtmlAtWebViewHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
MyWebView webView = findWebViewByTag(msg.what);
if (webView != null) {
webView.loadData(msg.obj.toString(), "text/html; charset=UTF-8", "UTF-8");
}
}
};
C++层,注册JavaScript函数:
#ifndef JAVA_JAVASCTRIPTFUNC_H
#define JAVA_JAVASCTRIPTFUNC_H
#include <jni.h>
#include <QtAndroidExtras/QtAndroidExtras>
#include <QtCore/QString>
class JAVA_javasctriptFunc
{
public:
JAVA_javasctriptFunc();
bool regfuc();
};
#endif // JAVA_JAVASCTRIPTFUNC_H
#include "java_javasctriptfunc.h"
int cout=0;
static jint testcall2(JNIEnv *env, jobject thiz)
{
cout++;
qInfo() << "MY C+++: testcall2"<<QThread::currentThreadId() ;
return cout;
// QMessageBox::information(NULL,"HEHE","33");
}
static void testcall3(JNIEnv *env, jobject thiz)
{
qInfo() << "MY C+++: testcall3"<<QThread::currentThreadId() ;
// QMessageBox::information(NULL,"HEHE","33");
}
static void testcall4(JNIEnv *env, jobject thiz)
{
qInfo() << "MY C+++: testcall4"<<QThread::currentThreadId() ;
// QMessageBox::information(NULL,"HEHE","33");
}
JAVA_javasctriptFunc::JAVA_javasctriptFunc()
{
}
bool JAVA_javasctriptFunc::regfuc()
{
JNINativeMethod methods[] {
{"testcall2", "()I", (void*)testcall2},
{"testcall3", "()V", (void*)testcall3},
{"testcall4", "()V", (void*)testcall4}
};
//package org.qtproject.qt5.android.bindings;
const char *classname = "org/qtproject/qt5/android/bindings/Cjavascript";
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;
}
WEBVIEW的C++层实现:
#pragma once
#include <jni.h>
#include <QtAndroidExtras/QtAndroidExtras>
#include <QtCore/QString>
class CAndroidNativeCallsSender
{
public:
CAndroidNativeCallsSender();
~CAndroidNativeCallsSender();
void createNewWebView( int id ) const;
void removeWebView( int id ) const;
void attachWebViewToMainLayout( int id ) const;
void moveWebView( int id, int x, int y ) const;
void resizeWebView( int id, int w, int h ) const;
void loadUrlAtWebView( int id, QString const& url ) const;
void loadHtmlAtWebView( int id, QString const& html ) const;
protected:
jmethodID m_createNewWebViewMID;
jmethodID m_removeWebViewMID;
jmethodID m_attachWebViewToMainLayoutMID;
jmethodID m_moveWebViewMID;
jmethodID m_resizeWebViewMID;
jmethodID m_loadUrlAtWebViewMID;
jmethodID m_loadHtmlAtWebViewMID;
jobject m_objectRef;
};
#include "AndroidNativeCallsSender.h"
#include <QtAndroidExtras/QAndroidJniObject>
//-----------------------------------------------------------------------------
CAndroidNativeCallsSender::CAndroidNativeCallsSender()
{
QAndroidJniEnvironment jniEnv;
QAndroidJniObject qObjAct = QAndroidJniObject::callStaticObjectMethod( "org/qtproject/qt5/android/QtNative",
"activity",
"()Landroid/app/Activity;" );
jobject objAct = qObjAct.object<jobject>();
m_objectRef = jniEnv->NewGlobalRef( objAct );
jclass cls = jniEnv->GetObjectClass( objAct );
if( cls )
{
m_createNewWebViewMID = jniEnv->GetMethodID( cls, "createNewWebView", "(I)V" );
m_removeWebViewMID = jniEnv->GetMethodID( cls, "removeWebView", "(I)V" );
m_attachWebViewToMainLayoutMID = jniEnv->GetMethodID( cls, "attachWebViewToMainLayout", "(I)V" );
m_moveWebViewMID = jniEnv->GetMethodID( cls, "moveWebView", "(III)V" );
m_resizeWebViewMID = jniEnv->GetMethodID( cls, "resizeWebView", "(III)V" );
m_loadUrlAtWebViewMID = jniEnv->GetMethodID( cls, "loadUrlAtWebView", "(ILjava/lang/String;)V" );
m_loadHtmlAtWebViewMID = jniEnv->GetMethodID( cls, "loadHtmlAtWebView", "(ILjava/lang/String;)V" );
}
}
//-----------------------------------------------------------------------------
CAndroidNativeCallsSender::~CAndroidNativeCallsSender()
{
if( m_objectRef != NULL )
{
QAndroidJniEnvironment jniEnv;
jniEnv->DeleteGlobalRef( m_objectRef );
}
}
//-----------------------------------------------------------------------------
void CAndroidNativeCallsSender::createNewWebView( int id ) const
{
if( m_createNewWebViewMID )
{
QAndroidJniEnvironment jniEnv;
jniEnv->CallVoidMethod( m_objectRef, m_createNewWebViewMID, id );
}
}
//-----------------------------------------------------------------------------
void CAndroidNativeCallsSender::removeWebView( int id ) const
{
if( m_removeWebViewMID )
{
QAndroidJniEnvironment jniEnv;
jniEnv->CallVoidMethod( m_objectRef, m_removeWebViewMID, id );
}
}
//-----------------------------------------------------------------------------
void CAndroidNativeCallsSender::attachWebViewToMainLayout( int id ) const
{
if( m_attachWebViewToMainLayoutMID )
{
QAndroidJniEnvironment jniEnv;
jniEnv->CallVoidMethod( m_objectRef, m_attachWebViewToMainLayoutMID, id );
}
}
//-----------------------------------------------------------------------------
void CAndroidNativeCallsSender::moveWebView( int id, int x, int y ) const
{
if( m_moveWebViewMID )
{
QAndroidJniEnvironment jniEnv;
jniEnv->CallVoidMethod( m_objectRef, m_moveWebViewMID, id, x, y );
}
}
//-----------------------------------------------------------------------------
void CAndroidNativeCallsSender::resizeWebView( int id, int w, int h ) const
{
if( m_resizeWebViewMID )
{
QAndroidJniEnvironment jniEnv;
jniEnv->CallVoidMethod( m_objectRef, m_resizeWebViewMID, id, w, h );
}
}
//-----------------------------------------------------------------------------
void CAndroidNativeCallsSender::loadUrlAtWebView( int id, QString const& url ) const
{
if( m_loadUrlAtWebViewMID )
{
QAndroidJniEnvironment jniEnv;
jstring jurl = jniEnv->NewStringUTF( url.toUtf8().constData() );
jniEnv->CallVoidMethod( m_objectRef,
m_loadUrlAtWebViewMID,
id,
jurl );
jniEnv->DeleteLocalRef( jurl );
}
}
//-----------------------------------------------------------------------------
void CAndroidNativeCallsSender::loadHtmlAtWebView( int id, QString const& html ) const
{
if( m_loadUrlAtWebViewMID )
{
QAndroidJniEnvironment jniEnv;
jstring jhtml = jniEnv->NewStringUTF( html.toUtf8().constData() );
jniEnv->CallVoidMethod( m_objectRef,
m_loadHtmlAtWebViewMID,
id,
jhtml );
jniEnv->DeleteLocalRef( jhtml );
}
}
#pragma once
#include <QtWidgets/QWidget>
#include <QtCore/QPoint>
#include "AndroidNativeCallsSender.h"
class QtCustomAndroidWebView : public QWidget
{
Q_OBJECT
public:
explicit QtCustomAndroidWebView( QWidget *parent = 0 );
~QtCustomAndroidWebView();
void loadURL( QString const& url ) const;
void loadHtmlData( QString const& data ) const;
void move( int x, int y );
void move( QPoint const& p );
void resize( int w, int h );
int androidID() const { return m_androidID; }
protected:
static int sm_tag;
int m_androidID;
CAndroidNativeCallsSender m_nativeSender;
int generateNewTag();
signals:
public slots:
};
#include "QtCustomAndroidWebView.h"
//#include <QtWidgets/QLayout>
//-----------------------------------------------------------------------------
int QtCustomAndroidWebView::sm_tag = 0;
//-----------------------------------------------------------------------------
QtCustomAndroidWebView::QtCustomAndroidWebView( QWidget *parent )
: QWidget( parent )
, m_androidID( generateNewTag() )
{
// if( parent && parent->layout() )
// parent->layout()->addWidget( this );
m_nativeSender.createNewWebView( androidID() );
m_nativeSender.attachWebViewToMainLayout( androidID() );
}
//-----------------------------------------------------------------------------
QtCustomAndroidWebView::~QtCustomAndroidWebView()
{
m_nativeSender.removeWebView( androidID() );
}
//-----------------------------------------------------------------------------
void QtCustomAndroidWebView::loadURL( QString const& url ) const
{
m_nativeSender.loadUrlAtWebView( androidID(), url );
}
//-----------------------------------------------------------------------------
void QtCustomAndroidWebView::loadHtmlData( QString const& data ) const
{
m_nativeSender.loadHtmlAtWebView( androidID(), data );
}
//-----------------------------------------------------------------------------
void QtCustomAndroidWebView::move( int x, int y )
{
QWidget::move( x, y );
m_nativeSender.moveWebView( androidID(), x, y );
}
//-----------------------------------------------------------------------------
void QtCustomAndroidWebView::move( QPoint const& p )
{
QtCustomAndroidWebView::move( p.x(), p.y() );
}
//-----------------------------------------------------------------------------
void QtCustomAndroidWebView::resize( int w, int h )
{
QWidget::resize( w, h );
m_nativeSender.resizeWebView( androidID(), w, h );
}
//-----------------------------------------------------------------------------
int QtCustomAndroidWebView::generateNewTag()
{
sm_tag++;
return sm_tag;
}
测试代码
#include "mytest.h"
#include "QtCustomAndroidWebView.h"
#include<QScreen>
#include <QApplication>
#include<QMessageBox>
#include<jni.h>
MyTest::MyTest(QWidget *parent) : QWidget(parent)
{
vbox = new QVBoxLayout(this);
btn = new QPushButton("call");
btn->setText("CALL");
edit = new QTextEdit();
vbox->addWidget(btn);
vbox->addWidget(edit);
// webview = new QtCustomAndroidWebView();
// webview->resize(this->width(),this->height());
// webview->move(0,0);
// webview->loadURL("http://www.baidu.com");
// vbox->addWidget(webview);
QScreen *screen = QApplication::primaryScreen();
webview = new QtCustomAndroidWebView( );
webview->resize(this->width(), 200);
webview->loadURL("file:///android_asset/index.html");
//注册js函数
javajavasctriptFunc = new JAVA_javasctriptFunc();
if(javajavasctriptFunc->regfuc()==false)
{
qDebug()<<"error";
}
vbox->addWidget(webview);
connect(btn,SIGNAL(clicked(bool)),this,SLOT(onclick()));
}
void MyTest::onclick()
{
int cout=0;
while (1) {
QThread::msleep(200);
QString s;
s.sprintf("javascript:test2(%d)",cout);
cout++;
webview->loadURL(s);
// 执行js脚本 返回值可参考Qt android src中WebView的实现 webview->loadURL("javascript:test()");
}
}
$(function(){
$("button").click(function(){
var x=obj.testcall2();
$("#list1").append("<li><a href=\"#\">Inbox<span class=\"ui-li-count\">"+x+"</span></a></li>");
$('#list1').listview("refresh");
});
});
如何调用JavaScript能获取返回值,可参考Qt的源码,其实没啥必要,只需要注册一个函数给js调用,让他把返回值弄进来就是
m_webSettingsSetDisplayZoomControls = webSettings.getClass().getMethod("setDisplayZoomControls", boolean.class);
if (Build.VERSION.SDK_INT > 18) {
m_webViewEvaluateJavascript = m_webView.getClass().getMethod("evaluateJavascript",
String.class,
ValueCallback.class);
}
public void runJavaScript(final String script, final long callbackId)
{
if (script == null)
return;
if (Build.VERSION.SDK_INT < 19 || m_webViewEvaluateJavascript == null)
return;
m_activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
m_webViewEvaluateJavascript.invoke(m_webView, script, callbackId == -1 ? null :
new ValueCallback<String>() {
@Override
public void onReceiveValue(String result) {
c_onRunJavaScriptResult(m_id, callbackId, result);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
}