安卓第三方浏览器内核Crosswalk升级到77内核过程记录

591 阅读5分钟

一、背景介绍

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环境

npm i cordova@8.1.0 -g

创建cordova项目

cordova create APP名称 APP包名

进入cordova项目,修改html文件为你需要运行的html文件(非必须)

cd APP名称

下载项目需要的其他cordova插件(非必须)

cordova plugin add 插件名@版本号

下载项目需要的Crosswalk插件

cordova plugin add cordova-plugin-crosswalk-webview-v3

下载他项目中我们需要的Crosswalk 77内核(aar文件),后续需要使用这个文件作为新内核使用

Crosswalk Native项目地址

添加安卓平台

cordova platform add android --save

使用Android Studio打开安卓项目: 项目名/platforms/android

安卓环境这块我也不太懂,但是需要保证platforms/android目录下有以下文件才能让项目调整Gradle然后正常运行:

image.png

配置镜像: 项目名/platforms/android/build.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

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 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

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 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测试 image.png

成果截图:

image.png

三、后续问题以及解决

1.运行成功后,发现元素获得焦点后会有橙色的框框,通过远程调试发现用户代理样式表中有以下样式规则:

image.png

解决办法: 在html里加一个:focus { outline: none; }样式即可

  1. file协议下,使用import语法错误,提示TypeError: Failed to fetch dynamically imported module: xxx

解决办法: 修改 项目名/platforms/android/app/src/main/java/org/crosswalk/engine/XWalkCordovaResourceClient.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;
    }