uniapp系列-超详细教你在uni-app+vue3里通过web-view组件传递信息打开H5页面写入localstorage并解决兼容性

Posted 糖~豆豆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uniapp系列-超详细教你在uni-app+vue3里通过web-view组件传递信息打开H5页面写入localstorage并解决兼容性相关的知识,希望对你有一定的参考价值。

web-view是什么

为什么使用这种方式搞页面?有什么好处呢?

  • 之前开发好的H5页面,不想重新开发,想要直接放进项目用
  • 页面热更新,不需要重新打包
  • 复杂需求使用H5比较方便,比如复杂的echarts等不想要使用uniapp搞
  • 其他好处等你 O(∩_∩)O 发掘哦~

如何使用web-view,并互相传递数据?

在uni-app vue页面使用web-view发送消息给H5(注意观察下面postMessage函数)

<template>
  <web-view
    :src="url"
    ref="webview"
    @onPostMessage="handleWebviewMessage"
    @message="handleWebviewMessage"
  ></web-view>
</template>

<script setup lang="ts">
import  ref  from "vue";
import  onLoad, onBackPress  from "@dcloudio/uni-app";
import  systemInfo  from "../../utils/system";
const  uniPlatform, platform  = systemInfo();
const url = ref("https://www.baidu.com/");

// 先接受到h5页面发来的data,再给H5发送数据的逻辑
const pages = getCurrentPages();
const vw = ref(null);
const postMessage = () =>  
  vw.value = pages[pages.length - 1].$getAppWebview().children()[0];
  const userData =  TOKEN: "AAAAAA" ;
  vw.value.evalJS(`receiveData($JSON.stringify(userData))`);
;
let postNumber = 0;
const handleWebviewMessage = (data) => 
  console.log("接收到消息:" + JSON.stringify(data));
  if (postNumber === 0) 
    postMessage();
    postNumber++;
  
;
// 下面的代码选择性使用
// 作为组件传参使用
// onLoad((options) => 
//   console.log("web-view::", options);
//   url.value = decodeURIComponent(options.url);
// );
// 解决返回按钮功能不兼容问题
// onBackPress(() => 
//   if (uniPlatform === "app" && platform === "android") return false;
//   uni.redirectTo(
//     url: "/pages/home/index",
//   );
//   return true;
// );
</script>

utils/system.ts

const systemInfo = function () 
  let systemInfomations = uni.getSystemInfoSync(); // 设备系统信息
  let scaleFactor = 750 / systemInfomations.windowWidth; // 机型适配比例系数
  let windowHeight = systemInfomations.windowHeight * scaleFactor; // 当前机型-屏幕高度
  let windowWidth = systemInfomations.windowWidth * scaleFactor; // 当前机型-屏幕宽度
  let statusBarHeight = systemInfomations.statusBarHeight * scaleFactor; // 状态栏高度
  let platform = systemInfomations.platform; // 运行平台
  const uniPlatform = systemInfomations.uniPlatform; // 运行环境
  return 
    scaleFactor,
    windowHeight,
    windowWidth,
    statusBarHeight,
    platform,
    uniPlatform
  ;
;


export  systemInfo ;

nvue获取webview窗口的方式是与普通vue获取webview的方式不一样,下面自己选择性写哦

H5端怎么发送数据给uniapp呢?

  • 方案1:通过evalJS(这个有个注意事项,比如要如何保证一定能传递过去,避免出现没有成功的情况,可以参考下面问题6的答案)
  • 方案2:通过 url 传参

H5页面代码:index.html

先去下载这个uni.webview.js文件放入你H5项目里面:
最新版地址:https://gitee.com/dcloud/uni-app/raw/dev/dist/uni.webview.1.5.4.js

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
</head>

<body>
    <h2 onclick="jumpTo()">我是\\(^o^)/~模拟的登录页,现在没有登陆成功哦</h2>
    <h2>如果我收到信息,下面会出现粉色的字哦~</h2>
    <h2 id="receiveData" ></h2>
</body>
<script type="text/javascript" src="./uni.webview.1.5.4.js"></script>
<script type="text/javascript">
    function receiveData(msg) 
        document.getElementById(\'receiveData\').innerText=\'下面是我收到的信息内容:\'+JSON.stringify(msg)
        // localStorage.setItem() // 在这里可以拿到传递过来的数据,写入localStorage,然后其他逻辑
    
    document.addEventListener("UniAppJSBridgeReady", function () 
        uni.postMessage(
            data: 
                action: "autoLogin",
            ,
        );
        uni.getEnv(function (res)  console.log("当前环境:" + JSON.stringify(res)); );)
</script>

</html>

常见问题点

  1. web-view 加载 uni-app H5,内部跳转冲突如何解决?
    使用 uni.webView.navigateTo
  2. web-view 的页面怎么和应用内的页面交互?
    调用 uni 相关的 API,就可以实现页面切换及发送消息。参考:在 web-view 加载的 HTML 中调用 uni 的 API
  3. web-view 加载的 HTML 中,是否可以调用原生?
    加载的 HTML 中是有 5+ 环境的,在 plusready 后调用即可。参考:一个简单实用的 plusready 方法添加链接描述
  4. uniapp嵌套webview页面,返回按钮跳转问题,以及解决uniapp 使用安卓手机在webview点击返回按钮后可以正常返回,但是ios手机需要点击按钮2次,返回两次的问题(假如你跳转到H5的webview后因某些原因需要定制化左上角的返回按钮,可以参考下面的做法)
// webview页面代码
import  onBackPress  from \'@dcloudio/uni-app\';
import  systemInfo  from \'@/utils/system\'; // 下面放这个代码
const  uniPlatform, platform  = systemInfo();
onBackPress(() => 
  if (uniPlatform === \'app\' && platform === \'android\') return false; // 如果你没有遇到返回的兼容问题,就可以去掉这一行代码
  uni.redirectTo(
    url: \'/pages/home/index\'
  );
  return true;
);
  1. uniapp嵌套webview页面,接收数据onPostMessage写了不管用怎么办?或者@message写了不管用怎么办?原因:nvue获取webview窗口的方式是与普通vue获取webview的方式不一样,你可以这两个选择性写

  2. uniapp通过webview的evalJS传递数据给H5,有的时候传递不成功的情况如何解决?
    可以先让H5加载完毕后,告诉uniapp,然后我们再调用函数传递给H5,可以参考我上面的代码,也可以直接下载本文最上面的我仓库的代码进行测试,如果路过的小伙伴有其他更好的办法,欢迎留言哦~~

  3. 待补充其他问题,欢迎小伙伴提出其他问题,我会不断更新本文档哦

注意事项

  • 小程序仅支持加载网络网页,不支持本地html
  • 补充说明:app-vue下web-view组件不支持自定义样式,而v-show的本质是改变组件的样式。即组件支持v-if而不是支持v-show。
  • 小程序端 web-view 组件一定有原生导航栏,下面一定是全屏的 web-view 组件,navigationStyle: custom 对 web-view 组件无效。
  • App 端使用 uni.web-view.js 的最低版为 uni.webview.1.5.4.js
  • App 平台同时支持网络网页和本地网页,但本地网页及相关资源(js、css等文件)必须放在 uni-app 项目根目录->hybrid->html 文件夹下或者 static 目录下,如下为一个加载本地网页的uni-app项目文件目录示例:
  • nvue web-view 必须指定样式宽高
  • App 网页向应用 postMessage 为实时消息
  • app-nvue web-view 默认没有大小,可以通过样式设置大小,如果想充满整个窗口,设置 flex: 1 即可,标题栏不会自动显示 web-view 页面中的 title。如果想充满整个窗口且想要显示标题推荐使用 vue 页面的 web-view(默认充满屏幕不可控制大小), 想自定义 web-view 大小使用 nvue web-view

今天就写到这里啦~

  • 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~
  • 大家要天天开心哦

欢迎大家指出文章需要改正之处~
学无止境,合作共赢

欢迎路过的小哥哥小姐姐们提出更好的意见哇~~

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)

全量更新和增量更新

在Android开发中,版本更新是一个非常常见的需求。目前更新主要分为两种方式,全量更新增量更新

如下图,分别是酷安应用宝两个商店的更新页面:


可以明显的看到
酷安的更新方式是全量更新,即每次下载全量的新版本文件。
应用宝的更新方式是增量更新,下载新文件就旧文件的差异部分,然后跟就文件做合并即可。

增量更新的好处很明显

仅需要下载少量的文件即可完成更新,理论上如果是基于之前文件基础做改动的话,那么文件越大,优势越明显。

例如上图中的 PICOOC,如果全量更新的话需要下载92M左右的文件,增量更新的话只需要下载26M的文件。


bsdiff 实现增量更新

网上查了一些资料,目前比较主流的方案是使用 bsdiff 实现文件的差分与合并。

官网:http://www.daemonology.net/bsdiff/

大体流程如下:

新文件和旧文件对比生成补丁文件

旧文件和补丁文件合并成新文件

出补丁包的过程肯定是在服务端进行的,Android端比较简单,只负责拿到补丁包然后跟本地文件做合并,校验下合并文件的MD5是否跟新包MD5一致即可。


Android端bsdiff实现

为了方便各位大佬快速实现功能,我整了个库发布到 mavenCentral

懒得自己弄的可以直接依赖一下远程库就能实现,参考https://github.com/yuzhiqiang1993/XeonDiff

需要自定义的继续往下看。

bsdiff 官网介绍的时候提到bsdiff用到了 bzip2

所以我们需要下载bsdiffbzip2 源码。

bsdiff: http://www.daemonology.net/bsdiff/bsdiff-4.3.tar.gz

bzip2: https://www.sourceware.org/bzip2/downloads.html

网络不好的可以从Github上自取、已经处理好了,直接用就行。源码也放上去了,想自己处理的按照下文做就行。

Github地址 :https://github.com/yuzhiqiang1993/XeonDiff

废话不多说,开始干

首先新建一个module,bsdiff是用C语言实现的,我们需要用到JNI。右键新建module,选择 Android Native Library, 名称随意填

会生成如下文件:

然后把下载解压后的 bzip文件夹以及bsdiff.c和bspatch.c 拷贝到cpp 目录下
如下:

为了方便区分,改下名字,把baip2-1.0.8的包名也改成合法的,以免出问题。
如下

然后先在 CMakeLists 把bsdiff和bspatch源文件添加进去

这个时候返回去查看 bsdiff.c 和 bspatch.c 文件会提示报错,

不用管,直接删掉报错代码,然后在下方报红的地方根据提示导入头文件

两个文件操作一样,这样就没报错了。

然后就是去声明 native 方法了。

先去瞅一眼bsdiffbspatchmain 方法

以bsdiff的main方法为例:

首先是接收两个参数,第一个是数值,值必须是4,第二个是个数组,数组中第一个参数没啥用,就是报错的时候提示用的,第二个参数是旧文件地址,第三个参数是新文件地址,第四个参数是补丁文件地址,看注释也能看明白。


方法执行完成后返回0

bspatch的main方法类似,自己看一下就行。

知道传什么参数后就好办了,下一步就是声明native方法即可。

如下

package com.xeon.bsdiff.utils

/**
 * @description: BsDiff工具类
 * @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
 * @date   : 2021/11/16
 * @time   : 22:12
 */

object XeonBsDiffUtil 

    init 
        System.loadLibrary("xeon_bsdiff")
    

    /**
     * 生成补丁包
     * @param newFilePath String 新文件的地址
     * @param oldFilePath String  旧文件的地址
     * @param patchFilePath String  生成的补丁文件地址
     * @return Int
     */
    external fun diff(newFilePath: String, oldFilePath: String, patchFilePath: String): Int
    /**
     * 合并差分包
     * @param oldFilePath String 旧文件地址
     * @param patchFilePath String 补丁文件地址
     * @param combineFilePath String 合并后的新文件地址
     * @return Int
     */
    external fun patch(oldFilePath: String, patchFilePath: String, combineFilePath: String): Int


然后根据提示在xeon_bsdiff.cpp文件中生成对应方法:

extern "C"
JNIEXPORT jint JNICALL
Java_com_xeon_bsdiff_XeonBsDiffUtil_diff(JNIEnv *env, jobject thiz, jstring new_file_path, jstring old_file_path, jstring patch_file_path) 



extern "C"
JNIEXPORT jint JNICALL
Java_com_xeon_bsdiff_XeonBsDiffUtil_patch(JNIEnv *env, jobject thiz, jstring old_file_path, jstring patch_file_path, jstring combine_file_path) 



下面就是实现这两个方法就好了。

首先是修改 bsdiffbspatchmain 方法名称,方便待会儿导入

示例:

然后更改xeon_bsdiff.cpp文件代码

如下:

#include <jni.h>

/*声明要调用的方法*/
extern "C" 
extern int bsdiff_main(int argc, char *argv[]);
extern int bspatch_main(int argc, char *argv[]);




/**
 * 生成补丁文件
 */
extern "C"
JNIEXPORT jint JNICALL
Java_com_xeon_bsdiff_utils_XeonBsDiffUtil_diff(JNIEnv *env, jobject thiz, jstring new_file_path, jstring old_file_path, jstring patch_file_path) 

    const char *newFile = env->GetStringUTFChars(new_file_path, nullptr);
    const char *oldFile = env->GetStringUTFChars(old_file_path, nullptr);
    const char *patchFile = env->GetStringUTFChars(patch_file_path, nullptr);

    char *argv[] = "xeon_bs_diff", const_cast<char *>(oldFile), const_cast<char *>(newFile),
                    const_cast<char *>(patchFile);
    /* 调用bsDiff的main方法,*/
    int res = bsdiff_main(4, argv);
    env->ReleaseStringUTFChars(old_file_path, oldFile);
    env->ReleaseStringUTFChars(new_file_path, newFile);
    env->ReleaseStringUTFChars(patch_file_path, patchFile);
    return res;

/**
 * 合并补丁文件
 */
extern "C"
JNIEXPORT jint JNICALL
Java_com_xeon_bsdiff_utils_XeonBsDiffUtil_patch(JNIEnv *env, jobject thiz, jstring old_file_path, jstring patch_file_path, jstring combine_file_path) 
    const char *oldFile = env->GetStringUTFChars(old_file_path, nullptr);
    const char *patchFile = env->GetStringUTFChars(patch_file_path, nullptr);
    const char *combineFile = env->GetStringUTFChars(combine_file_path, nullptr);
    char *argv[] = "xeon_bs_patch", const_cast<char *>(oldFile), const_cast<char *>(combineFile),
                    const_cast<char *>(patchFile);
    /*调bspatch的main方法*/
    int res = bspatch_main(4, argv);
    env->ReleaseStringUTFChars(old_file_path, oldFile);
    env->ReleaseStringUTFChars(combine_file_path, combineFile);
    env->ReleaseStringUTFChars(patch_file_path, patchFile);
    return res;

至此,代码已经编写完成了。

此时如果你直接运行App的话,应该提示如下错误:

提示找不到bzip相关的引用。
这个时候需要去CMakeList中导入一下bzip2里的.c源文件

如下

关键代码;

#导入bzip2
include_directories($CMAKE_SOURCE_DIR/bzip2)
#列出bzip2目录下所有.c 源文件 赋值给bzip2
file(GLOB bzip2 bzip2/*.c)

然后在add_library中添加.c源文件

add_library(
        # Sets the name of the library.
        xeon_bsdiff

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        $bzip2
        bsdiff.c
        bspatch.c
        xeon_bsdiff.cpp
)

然后再运行,应该会提示如下报错:

multiple definition of `main’
表示有多个main方法的定义,并给出了文件名。这个简单,点击指定文件名把main方法改一下即可。

例如:
把bzip2recover 中的main改成 bzip2recover_main

其他提示的文件同理

都改完之后应该就没什么报错了,可以正常运行了。

至此,我们的diff module 的所有代码就ok了。


下面就是正常的业务编写了,不浪费篇幅,直接贴代码了:

package com.xeon.xeonbsdiff

import androidx.lifecycle.*
import com.blankj.utilcode.util.*
import com.xeon.bsdiff.utils.XeonBsDiffUtil
import kotlinx.coroutines.*
import java.io.File
import kotlin.system.measureTimeMillis


/**
 * @description: MainViewModel
 * @author : yuzhiqiang (zhiqiang.yu.xeon@gmail.com)
 * @date   : 2021/11/16
 * @time   : 22:20
 */

class MainViewModel : ViewModel() 

    /*异常处理*/
    private val exceptionHandler = CoroutineExceptionHandler  coroutineContext, throwable ->
        LogUtils.e("异常了:$throwable.localizedMessage")
        throwable.printStackTrace()
    

    /*文件后缀名*/
    private val suffix = "apk"
    /*旧文件*/
    private val oldFile = File(PathUtils.getExternalAppFilesPath(), "old.$suffix")
    /*新文件*/
    private val newFile = File(PathUtils.getExternalAppFilesPath(), "new.$suffix")
    /*补丁文件*/
    private val patchFile = File(PathUtils.getExternalAppFilesPath(), "patch.$suffix")
    /*合并后的文件*/
    private val combineFile = File(PathUtils.getExternalAppFilesPath(), "combine.$suffix")

    /*生成补丁文件*/
    fun fileDiff() 
        viewModelScope.launch(exceptionHandler) 

            val measureTimeMillis = measureTimeMillis 
                withContext(Dispatchers.IO) 
                    if (!oldFile.exists() || !newFile.exists()) 
                        ToastUtils.showShort("对比包缺失")
                        return@withContext
                    
                    /*生成差分包*/
                    XeonBsDiffUtil.diff(newFile.absolutePath, oldFile.absolutePath, patchFile.absolutePath)
                
            

            LogUtils.i("生成补丁文件耗时:$measureTimeMillis")
            LogUtils.i("oldFileSize:$FileUtils.getSize(oldFile)")
            LogUtils.i("newFileSize:$FileUtils.getSize(newFile)")
            LogUtils.i("patchFileSize:$FileUtils.getSize(patchFile)")

        

    

    /*合并补丁文件*/
    fun filePatch() 
        viewModelScope.launch(exceptionHandler) 
            val measureTimeMillis = measureTimeMillis 
                withContext(Dispatchers.IO) 
                    LogUtils.e(PathUtils.getExternalAppFilesPath())
                    if (!oldFile.exists() || !patchFile.exists()) 
                        ToastUtils.showShort("补丁文件或旧文件缺失")
                        return@withContext
                    
                    XeonBsDiffUtil.patch(oldFile.absolutePath, patchFile.absolutePath, combineFile.absolutePath)
                
            
            LogUtils.i("合并补丁文件耗时:$measureTimeMillis")
            LogUtils.i("newFile MD5:$FileUtils.getFileMD5ToString(newFile)")
            LogUtils.i("combineFile MD5:$FileUtils.getFileMD5ToString(combineFile)")

        

    

然后找个文件,在之前文件基础上改改就可以试验一下了。

我这边是随便找了两个相差几个版本的apk,是自己比较喜欢用的一款开源阅读软件,贼好用。

这里直接上运行后结果了,如下:

生成补丁文件的Log:

合并补丁文件Log:

文件截图:

可以看到,新文件和合并后的文件MD5值一致,至此,我们就实现了文件的增量更新操作。

因为bsdiff是基于二进制的处理,所以不仅仅是apk可以实现增量更新,理论上何文件都可以使用,例如 图片,文本文件等等

注意,打出来的so文件在这个目录下。

好了,这样我们就在Android上实现了文件的增量更新,实际开发中,Android要做的也比较简单,只需要下载补丁文件做合并操作即可。大多数逻辑还是在后端的。

以下是实际开发过程中可能需要注意的问题:

  • 生成补丁包是非常耗时的操作,不过绝大多数情况下是在服务端进行的,相对来讲合并文件就快很多。
  • 理论上每出一个新版本的文件,都要跟之前所有版本的旧文件做diff操作,需要维护很多条补丁包记录。
  • 如果新文件和旧文件相差太大的话,搞不好还会出现补丁文件比新文件都大的情况,这种就得不偿失了.
  • 如果本身文件不大的话,感觉没什么必要去走增量更新,走增量更新反而会增加后端的维护成本以及增加出错的风险,这个需要根据实际的业务场景去考虑。

如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

以上是关于uniapp系列-超详细教你在uni-app+vue3里通过web-view组件传递信息打开H5页面写入localstorage并解决兼容性的主要内容,如果未能解决你的问题,请参考以下文章

【超详细】手把手教你在Linux下安装Python

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)

手把手教你在Android中使用bsdiff实现文件增量更新 (超详细)