一、背景介绍
libccy/noname项目是由水乎在2013年进行开发的一个Javascript游戏项目,其安卓端是使用了Cordova技术来进行文件读写等本地操作,由于其当时的手机Webview内核不尽人意,所以开发者水乎引入了第三方浏览器内核Crosswalk的17版本(chromium 46)。
目前在手机内置的Webview版本越来越高的情况下,对比当时引入的46内核,显得当时的Crosswalk内核极为卡顿,加载时间长,并且不能使用目前最基础的ES6语法。为了能使用更新的函数,我们引入了core.js,虽然解决了不能使用最新可polyfill函数的问题,但是也带来了一个不太能忍受的问题: 每次进入游戏都得等5秒以上!
二、内核的升级
1.所需环境
NodeJs:v10.24.1 (建议使用nvm)
Android Studio 2021.3.1 (需要有一定的安卓开发经验,我没有)
项目中Gradle 6.5
项目中Android Gradle Plugin Version 4.0
2.创建项目的步骤
下载全局cordova环境
css
npm i cordova@8.1.0 -g
创建cordova项目
lua
cordova create APP名称 APP包名
进入cordova项目,修改html文件为你需要运行的html文件(非必须)
bash
cd APP名称
下载项目需要的其他cordova插件(非必须)
csharp
cordova plugin add 插件名@版本号
下载项目需要的Crosswalk插件
csharp
cordova plugin add cordova-plugin-crosswalk-webview-v3
下载他项目中我们需要的Crosswalk 77内核(aar文件),后续需要使用这个文件作为新内核使用
添加安卓平台
sql
cordova platform add android --save
使用Android Studio打开安卓项目: 项目名/platforms/android
安卓环境这块我也不太懂,但是需要保证platforms/android目录下有以下文件才能让项目调整Gradle然后正常运行:
配置镜像: 项目名/platforms/android/build.gradle
gradle
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
maven{ url 'https://maven.aliyun.com/repository/public' }
maven{ url 'https://maven.aliyun.com/repository/google' }
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral { url 'https://maven.aliyun.com/repository/public' }
maven{ url 'https://maven.aliyun.com/repository/public' }
maven{ url 'https://maven.aliyun.com/repository/google' }
maven{ url 'https://maven.aliyun.com/repository/jcenter' }
google()
}
//This replaces project.properties w.r.t. build settings
project.ext {
defaultBuildToolsVersion="29.0.2" //String
defaultMinSdkVersion=24 //Integer - Minimum requirement is Android 5.1
defaultTargetSdkVersion=29 //Integer - We ALWAYS target the latest by default
defaultCompileSdkVersion=29 //Integer - We ALWAYS compile with the latest by default
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
将上面下载好的aar文件放入项目名/platforms/android/app/libs文件夹,然后修改项目名/platforms/android/cordova-plugin-crosswalk-webview-v3下的xxx-xwalk.gradle文件,然后执行gradle sync
gradle
cdvPluginPostBuildExtras.add({
...
android: {
// 新增这个属性
aaptOptions {
noCompress 'dat', 'pak'
}
}
...
dependencies: {
// 第一个引入的包换成我们下载的aar文件,而不是请求Crosswalk官网下载53的内核
implementation 'com.pakdata.xwalk.refactor:xwalk_core_lirary:77@aar'
...
}
})
修改项目名/platforms/android/app/src/main/res/xml/config.xml文件,保证以下代码存在 注:关于--unlimited-storage的作用可以查看此议题
xml
<?xml version="1.0" encoding="utf-8"?>
<widget id="你的包名" version="App显示的版本号" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<!-- 修改系统内核为Crosswalk内核 -->
<preference name="webView" value="org.crosswalk.engine.XWalkWebViewEngine" />
<preference default="17+" name="xwalkVersion" />
<!-- 默认值是--disable-pull-to-refresh-effect,加上--unlimited-storage是为了让indexedDB正常使用 -->
<preference default="--disable-pull-to-refresh-effect --unlimited-storage" name="xwalkCommandLine" />
<preference default="embedded" name="xwalkMode" />
<preference default="false" name="xwalkMultipleApk" />
<preference value="false" name="cdvBuildMultipleApks" />
<!-- 指定apk最低支持版本 -->
<preference name="android-minSdkVersion" value="22"/>
<!-- 其他代码无需修改 -->
...
</widget>
从官方Crosswalk内核迁移到此内核,其数据会"丢失"。我和朋友研究出的迁移数据的代码如下(经过自己测试没有明显问题):
只需要把data下的app_xwalkcore/Default文件夹(旧版)重命名为app_xwalkcore/DefaultProfile文件夹(新版)即可
创建 项目名/platforms/android/app/src/main/java/你的包名/updateDataApplication.java
java
package 你的包名;
import android.app.Application;
import android.util.Log;
import java.io.File;
public class updateDataApplication extends Application {
private static final String TAG = "updateDataApplication";
@Override
public void onCreate() {
super.onCreate();
File dataPath = getFilesDir().getParentFile();
Log.e(TAG, dataPath.getAbsolutePath());
File xwalkCore = new File(dataPath, "app_xwalkcore/");
Log.e(TAG, String.valueOf(xwalkCore.exists()));
Log.e(TAG, String.valueOf(xwalkCore.isDirectory()));
File oldDataDirectory = new File(xwalkCore, "Default");
Log.e(TAG, String.valueOf(oldDataDirectory.exists()));
Log.e(TAG, String.valueOf(oldDataDirectory.isDirectory()));
File newDataDirectory = new File(xwalkCore, "DefaultProfile");
Log.e(TAG, String.valueOf(newDataDirectory.exists()));
Log.e(TAG, String.valueOf(newDataDirectory.isDirectory()));
if (oldDataDirectory.exists()) {
if (newDataDirectory.exists()) {
deleteFile(newDataDirectory);
}
// 重命名文件夹以同步数据
Log.e(TAG, String.valueOf(
oldDataDirectory.renameTo(new File(xwalkCore, "DefaultProfile"))
));
}
}
// 这个方法来自互联网
private void deleteFile(File file) {
if (file.exists()) {//判断文件是否存在
if (file.isFile()) {//判断是否是文件
file.delete();//删除文件
} else if (file.isDirectory()) {//否则如果它是一个目录
File[] files = file.listFiles();//声明目录下所有的文件 files[];
for (int i = 0;i < files.length;i ++) {//遍历目录下所有的文件
this.deleteFile(files[i]);//把每个文件用这个方法进行迭代
}
file.delete();//删除文件夹
}
} else {
Log.e(TAG,"所删除的文件不存在");
}
}
}
然后修改项目名/platforms/android/app/src/main/AndroidManifest.xml,应用你的updateDataApplication类,以及添加必要的安卓权限
xml
<?xml version='1.0' encoding='utf-8'?>
<manifest xmlns:tools="http://schemas.android.com/tools"
android:hardwareAccelerated="true" android:versionCode="实际的版本号代码" android:versionName="显示的版本号" package="你的包名" xmlns:android="http://schemas.android.com/apk/res/android">
<supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
<!-- 系统权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 应用你的updateDataApplication类 -->
<application android:name=".updateDataApplication" android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:usesCleartextTraffic="true" tools:targetApi="m">
<!-- activity按你的实际需求来 -->
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:label="@string/activity_name"
android:launchMode="singleTop"
android:name="MainActivity"
android:screenOrientation="sensorLandscape"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter android:label="@string/launcher_name">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
最后,使用Android Studio自带的打包功能进行打包,或者进行debug测试
成果截图:
三、后续问题以及解决
1.运行成功后,发现元素获得焦点后会有橙色的框框,通过远程调试发现用户代理样式表中有以下样式规则:
解决办法: 在html里加一个:focus { outline: none; }样式即可
- file协议下,使用import语法错误,提示TypeError: Failed to fetch dynamically imported module: xxx
解决办法: 修改 项目名/platforms/android/app/src/main/java/org/crosswalk/engine/XWalkCordovaResourceClient.java
java
import android.util.Log;
// 在这个方法下面添加新的方法
@Override
public WebResourceResponse shouldInterceptLoadRequest(XWalkView view, String url) {
...
}
// 新增方法
@Override
public XWalkWebResourceResponse shouldInterceptLoadRequest(XWalkView view, XWalkWebResourceRequest request) {
String url = request.getUrl().toString();
String method = request.getMethod();
Map<String, String> headers = request.getRequestHeaders();
Log.e("Request", method + " " + url + " " + headers);
if (url.startsWith("file://") && !url.contains("/app_webview/")&& !url.contains("/app_xwalkcore/")) {
// 是否是模块请求
if (headers != null
&& headers.containsKey("Origin")
&& Objects.equals(headers.get("Origin"), "file://")
&& Objects.equals(headers.get("Sec-Fetch-Mode"), "cors")) {
try {
URL Url = new URL(url);
URLConnection connection = Url.openConnection();
InputStream data = Url.openStream();
// 手动返回数据
return createXWalkWebResourceResponse(connection.getContentType(), "utf-8", data);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 返回null是让浏览器自己处理
return null;
}