Commit 04b5b6a3 by BJQ

first commit

parents
*.iml
**/.DS_Store
# Default ignored files
/workspace.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/cordova-plugin-x5engine-webview.iml" filepath="$PROJECT_DIR$/.idea/cordova-plugin-x5engine-webview.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
This diff is collapsed. Click to expand it.
# cordova-plugin-x5engine-webview
Makes your Cordova application use the [TBS X5 WebView](http://x5.tencent.com/index)
instead of the System WebView. Requires cordova-android 4.0 or greater.
## 腾讯浏览服务X5SDK
[腾讯浏览服务X5SDK](http://x5.tencent.com/index)是通过调用微信/手机QQ/空间的X5内核,解决系统webview兼容性差、加载速度慢、功能缺陷等问题,开发接入便捷,大小只有253K,仅需几行代码,即可解决一切令开发者们头疼的问题,为用户提供最优秀的浏览体验。
腾讯浏览服务官方只提供了如何把系统Webview替换成X5的接入文档,并没有提供Cordova集成的方法。现在Hybrid App项目经常使用很多Cordoca插件提供拍照,扫二维码,App支付宝支付等功能,因此就需要把cordova和x5结合起来。
Cordova框架现在已经很完善,Cordova的Web Engine也是基于接口开发的,因此我参考系统engine的实现,写了一个x5engine插件,解决了Cordova调用X5内核的问题。
## 问题背景
熟悉Cordova生态圈的朋友可能听说过Crosswalk,[Crosswalk](https://crosswalk-project.org/documentation/cordova.html)是Intel维护的Webkit开源项目,可以通过插件安装命令` cordova plugin add cordova-plugin-crosswalk-webview` 安装,它的缺点就是太大了,集成后apk会增加20M,安装后会占用50M空间,因此一般不推荐普通App使用Crosswalk。
### Benefits
* 同时享受Cordova平台完善的生态系统和腾讯X5内核的兼容性和稳定性,大量的Cordova插件仍然可用, Apk size只增加250k。
* 微信/手机QQ/空间装机量很大,足够覆盖大多数国内用户
* 使用X5内核的播放器增强H5视频播放能力
### Drawbacks
* Increased APK size (about 250Kb)
### Install
The following directions are for cordova-cli (most people).
* Open an existing cordova project, with cordova-android 4.0.0+, and using the latest CLI. TBS X5 variables can be configured as an option when installing the plugin
* Add this plugin
```
$ cordova plugin add https://github.com/offbye/cordova-plugin-x5engine-webview.git
```
* Build
```
$ cordova build android
```
### Known issue
1. 64位手机上加载包含x5 so文件的插件报错
`TBS:initX5Core -- loadSucc: false; exception: java.lang.reflect.InvocationTargetException; cause: java.lang.UnsatisfiedLinkError: dlopen failed: "/data/data/com.tencent.mm/app_tbs/core_share/libmttwebview.so" is 32-bit instead of 64-bit
`
解决办法是在libs/armeabi目录下增加一个32位的JNI so, 随便弄个小一点的so加上就可以,如果已经用了其它的JNI so应该不会有这个错误。
2. X5内核不支持file://本地域url,但支持本地相对路径。
{
"name": "cordova-plugin-x5engine-webview",
"version": "2.0.0",
"description": "Changes the default WebView to QQ X5 WebView",
"cordova": {
"id": "cordova-plugin-x5engine-webview",
"platforms": [
"android"
]
},
"repository": {
"type": "git",
"url": "https://github.com/offbye/cordova-plugin-x5engine-webview.git"
},
"keywords": [
"cordova",
"chromium",
"x5engine",
"webview",
"engine",
"ecosystem:cordova",
"cordova-android"
],
"engines": [{
"name": "cordova-android",
"version": ">=4"
}, {
"name": "cordova-plugman",
"version": ">=4.2.0"
}],
"author": "",
"license": "Apache 2.0",
"bugs": {
"url": "https://github.com/offbye/cordova-plugin-x5engine-webview"
},
"homepage": "https://github.com/offbye/cordova-plugin-x5engine-webview"
}
package org.apache.cordova.x5engine;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.util.Log;
import android.util.TypedValue;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class AndroidProtocolHandler {
private static final String TAG = "AndroidProtocolHandler";
private Context context;
public AndroidProtocolHandler(Context context) {
this.context = context;
}
public InputStream openAsset(String path) throws IOException {
return context.getAssets().open(path, AssetManager.ACCESS_STREAMING);
}
public InputStream openResource(Uri uri) {
assert uri.getPath() != null;
// The path must be of the form ".../asset_type/asset_name.ext".
List<String> pathSegments = uri.getPathSegments();
String assetType = pathSegments.get(pathSegments.size() - 2);
String assetName = pathSegments.get(pathSegments.size() - 1);
// Drop the file extension.
assetName = assetName.split("\\.")[0];
try {
// Use the application context for resolving the resource package name so that we do
// not use the browser's own resources. Note that if 'context' here belongs to the
// test suite, it does not have a separate application context. In that case we use
// the original context object directly.
if (context.getApplicationContext() != null) {
context = context.getApplicationContext();
}
int fieldId = getFieldId(context, assetType, assetName);
int valueType = getValueType(context, fieldId);
if (valueType == TypedValue.TYPE_STRING) {
return context.getResources().openRawResource(fieldId);
} else {
Log.e(TAG, "Asset not of type string: " + uri);
return null;
}
} catch (ClassNotFoundException e) {
Log.e(TAG, "Unable to open resource URL: " + uri, e);
return null;
} catch (NoSuchFieldException e) {
Log.e(TAG, "Unable to open resource URL: " + uri, e);
return null;
} catch (IllegalAccessException e) {
Log.e(TAG, "Unable to open resource URL: " + uri, e);
return null;
}
}
public InputStream openFile(String filePath) throws IOException {
String realPath = filePath.replace(X5WebViewLocalServer.fileStart, "");
File localFile = new File(realPath);
return new FileInputStream(localFile);
}
public InputStream openContentUrl(Uri uri) throws IOException {
Integer port = uri.getPort();
String realPath;
if (port == -1) {
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + X5WebViewLocalServer.contentStart, "content:/");
} else {
realPath = uri.toString().replace(uri.getScheme() + "://" + uri.getHost() + ":" + port + X5WebViewLocalServer.contentStart, "content:/");
}
InputStream stream = null;
try {
stream = context.getContentResolver().openInputStream(Uri.parse(realPath));
} catch (SecurityException e) {
Log.e(TAG, "Unable to open content URL: " + uri, e);
}
return stream;
}
private static int getFieldId(Context context, String assetType, String assetName)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> d = context.getClassLoader()
.loadClass(context.getPackageName() + ".R$" + assetType);
java.lang.reflect.Field field = d.getField(assetName);
int id = field.getInt(null);
return id;
}
private static int getValueType(Context context, int fieldId) {
TypedValue value = new TypedValue();
context.getResources().getValue(fieldId, value, true);
return value.type;
}
}
package org.apache.cordova.x5engine;
import android.net.Uri;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class UriMatcher {
/**
* Creates the root node of the URI tree.
*
* @param code the code to match for the root URI
*/
public UriMatcher(Object code) {
mCode = code;
mWhich = -1;
mChildren = new ArrayList<UriMatcher>();
mText = null;
}
private UriMatcher() {
mCode = null;
mWhich = -1;
mChildren = new ArrayList<UriMatcher>();
mText = null;
}
/**
* Add a URI to match, and the code to return when this URI is
* matched. URI nodes may be exact match string, the token "*"
* that matches any text, or the token "#" that matches only
* numbers.
* <p>
* Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
* this method will accept a leading slash in the path.
*
* @param authority the authority to match
* @param path the path to match. * may be used as a wild card for
* any text, and # may be used as a wild card for numbers.
* @param code the code that is returned when a URI is matched
* against the given components. Must be positive.
*/
public void addURI(String scheme, String authority, String path, Object code) {
if (code == null) {
throw new IllegalArgumentException("Code can't be null");
}
String[] tokens = null;
if (path != null) {
String newPath = path;
// Strip leading slash if present.
if (path.length() > 0 && path.charAt(0) == '/') {
newPath = path.substring(1);
}
tokens = PATH_SPLIT_PATTERN.split(newPath);
}
int numTokens = tokens != null ? tokens.length : 0;
UriMatcher node = this;
for (int i = -2; i < numTokens; i++) {
String token;
if (i == -2)
token = scheme;
else if (i == -1)
token = authority;
else
token = tokens[i];
ArrayList<UriMatcher> children = node.mChildren;
int numChildren = children.size();
UriMatcher child;
int j;
for (j = 0; j < numChildren; j++) {
child = children.get(j);
if (token.equals(child.mText)) {
node = child;
break;
}
}
if (j == numChildren) {
// Child not found, create it
child = new UriMatcher();
if (token.equals("**")) {
child.mWhich = REST;
} else if (token.equals("*")) {
child.mWhich = TEXT;
} else {
child.mWhich = EXACT;
}
child.mText = token;
node.mChildren.add(child);
node = child;
}
}
node.mCode = code;
}
static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/");
/**
* Try to match against the path in a url.
*
* @param uri The url whose path we will match against.
* @return The code for the matched node (added using addURI),
* or null if there is no matched node.
*/
public Object match(Uri uri) {
final List<String> pathSegments = uri.getPathSegments();
final int li = pathSegments.size();
UriMatcher node = this;
if (li == 0 && uri.getAuthority() == null) {
return this.mCode;
}
for (int i = -2; i < li; i++) {
String u;
if (i == -2)
u = uri.getScheme();
else if (i == -1)
u = uri.getAuthority();
else
u = pathSegments.get(i);
ArrayList<UriMatcher> list = node.mChildren;
if (list == null) {
break;
}
node = null;
int lj = list.size();
for (int j = 0; j < lj; j++) {
UriMatcher n = list.get(j);
which_switch:
switch (n.mWhich) {
case EXACT:
if (n.mText.equals(u)) {
node = n;
}
break;
case TEXT:
node = n;
break;
case REST:
return n.mCode;
}
if (node != null) {
break;
}
}
if (node == null) {
return null;
}
}
return node.mCode;
}
private static final int EXACT = 0;
private static final int TEXT = 1;
private static final int REST = 2;
private Object mCode;
private int mWhich;
private String mText;
private ArrayList<UriMatcher> mChildren;
}
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.x5engine;
import android.annotation.TargetApi;
import android.os.Build;
import com.tencent.smtt.sdk.CookieManager;
import com.tencent.smtt.sdk.WebView;
import org.apache.cordova.ICordovaCookieManager;
class X5CookieManager implements ICordovaCookieManager {
protected final WebView webView;
private final CookieManager cookieManager;
//Added because lint can't see the conditional RIGHT ABOVE this
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public X5CookieManager(WebView webview) {
webView = webview;
cookieManager = CookieManager.getInstance();
//REALLY? Nobody has seen this UNTIL NOW?
// cookieManager.setAcceptFileSchemeCookies(true);
cookieManager.setAcceptCookie(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.setAcceptThirdPartyCookies(webView, true);
}
}
public void setCookiesEnabled(boolean accept) {
cookieManager.setAcceptCookie(accept);
}
public void setCookie(final String url, final String value) {
cookieManager.setCookie(url, value);
}
public String getCookie(final String url) {
return cookieManager.getCookie(url);
}
public void clearCookies() {
cookieManager.removeAllCookie();
}
public void flush() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
}
}
};
package org.apache.cordova.x5engine;
import com.tencent.smtt.export.external.interfaces.ClientCertRequest;
import org.apache.cordova.ICordovaClientCertRequest;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* Implementation of the ICordovaClientCertRequest for Android WebView.
*
* Created by zhangxitao on 16/9/14.
*/
public class X5CordovaClientCertRequest implements ICordovaClientCertRequest {
private final ClientCertRequest request;
public X5CordovaClientCertRequest(ClientCertRequest request) {
this.request = request;
}
/**
* Cancel this request
*/
public void cancel()
{
request.cancel();
}
/*
* Returns the host name of the server requesting the certificate.
*/
public String getHost()
{
return request.getHost();
}
/*
* Returns the acceptable types of asymmetric keys (can be null).
*/
public String[] getKeyTypes()
{
return request.getKeyTypes();
}
/*
* Returns the port number of the server requesting the certificate.
*/
public int getPort()
{
return request.getPort();
}
/*
* Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
*/
public Principal[] getPrincipals()
{
return request.getPrincipals();
}
/*
* Ignore the request for now. Do not remember user's choice.
*/
public void ignore()
{
request.ignore();
}
/*
* Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
*
* @param privateKey The privateKey
* @param chain The certificate chain
*/
public void proceed(PrivateKey privateKey, X509Certificate[] chain)
{
request.proceed(privateKey, chain);
}
}
package org.apache.cordova.x5engine;
/**
* Created by zhangxitao on 16/9/14.
*/
import com.tencent.smtt.export.external.interfaces.HttpAuthHandler;
import org.apache.cordova.ICordovaHttpAuthHandler;
/**
* Specifies interface for HTTP auth handler object which is used to handle auth requests and
* specifying user credentials.
*/
public class X5CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
private final HttpAuthHandler handler;
public X5CordovaHttpAuthHandler(HttpAuthHandler handler) {
this.handler = handler;
}
/**
* Instructs the XWalkView to cancel the authentication request.
*/
public void cancel() {
handler.cancel();
}
/**
* Instructs the XWalkView to proceed with the authentication with the given credentials.
*
* @param username
* @param password
*/
public void proceed(String username, String password) {
handler.proceed(username, password);
}
}
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.x5engine;
import android.webkit.JavascriptInterface;
import org.apache.cordova.CordovaBridge;
import org.apache.cordova.ExposedJsApi;
import org.json.JSONException;
/**
* Contains APIs that the JS can call. All functions in here should also have
* an equivalent entry in CordovaChromeClient.java, and be added to
* cordova-js/lib/android/plugin/android/promptbasednativeapi.js
*/
class X5ExposedJsApi implements ExposedJsApi {
private final CordovaBridge bridge;
X5ExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
}
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.cordova.x5engine;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.WebViewClient;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaWebViewEngine;
/**
* Custom WebView subclass that enables us to capture events needed for Cordova.
*/
public class X5WebView extends WebView implements CordovaWebViewEngine.EngineView {
private X5WebViewClient viewClient;
X5WebChromeClient chromeClient;
private X5WebViewEngine parentEngine;
private CordovaInterface cordova;
public static final String WEBVIEW_PREFS_NAME = "WebViewSettings";
public static final String CDV_SERVER_PATH = "serverBasePath";
private boolean status = false;
private String url = "";
private final String TAG = "X5WebView";
//reflection级别的构造函数....
public X5WebView(Context context) {
this(context, null);
}
public X5WebView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG , "creating x5webview ");
}
// Package visibility to enforce that only X5WebViewEngine should call this method.
void init(X5WebViewEngine parentEngine, CordovaInterface cordova) {
this.cordova = cordova;
this.parentEngine = parentEngine;
if (this.viewClient == null) {
setWebViewClient(new X5WebViewClient(parentEngine));
}
if (this.chromeClient == null) {
setWebChromeClient(new X5WebChromeClient(parentEngine));
}
}
@Override
public CordovaWebView getCordovaWebView() {
return parentEngine != null ? parentEngine.getCordovaWebView() : null;
}
@Override
public void setWebViewClient(WebViewClient client) {
Log.d(TAG , " =============== setWebViewClient " + client + " =================");
viewClient = (X5WebViewClient)client;
super.setWebViewClient(client);
}
@Override
public void setWebChromeClient(WebChromeClient client) {
Log.d(TAG , " =============== setWebViewChromeClient " + client + " =================");
chromeClient = (X5WebChromeClient)client;
super.setWebChromeClient(client);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Boolean ret = parentEngine.client.onDispatchKeyEvent(event);
if (ret != null) {
return ret.booleanValue();
}
return super.dispatchKeyEvent(event);
}
@Override
public void loadUrl(String s) {
Log.d(TAG , " =============== LOADING THE URL " + s + " =================");
super.loadUrl(s);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-plugin-x5engine-webview"
version="1.0.0">
<name>x5engine WebView Engine</name>
<description>Changes the default WebView to x5engine</description>
<license>Apache 2.0</license>
<keywords>cordova,chromium,x5engine,webview</keywords>
<repo>https://github.com/offbye/cordova-plugin-x5engine-webview</repo>
<issue>https://github.com/offbye/cordova-plugin-x5engine-webview</issue>
<engines>
<engine name="cordova-android" version=">=4"/>
<engine name="cordova-plugman" version=">=5.2.0"/><!-- needed for gradleReference support -->
</engines>
<!-- android -->
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<preference name="webView" value="org.apache.cordova.x5engine.X5WebViewEngine"/>
<preference name="appUserAgent" value="appX5"/>
<preference name="android-minSdkVersion" value="21" />
</config-file>
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
</config-file>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5CookieManager.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5CordovaClientCertRequest.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5CordovaHttpAuthHandler.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5ExposedJsApi.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebChromeClient.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebView.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebViewClient.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebViewEngine.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/UriMatcher.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/AndroidProtocolHandler.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/org/apache/cordova/x5engine/X5WebViewLocalServer.java" target-dir="src/org/apache/cordova/x5engine"/>
<source-file src="platforms/android/src/jniLibs/armeabi/liblbs.so" target-dir="jniLibs/armeabi"/>
<source-file src="platforms/android/libs/tbs_sdk_thirdapp_v4.3.0.1148_43697_sharewithdownloadwithfile_withoutGame_obfs_20190805_175505.jar" target-dir="libs"/>
</platform>
<info>
</info>
</plugin>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment