Flutter嵌入式WebView插件x5_webview的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter嵌入式WebView插件x5_webview的使用

x5_webview - pub package

pub package

  • 一个基于腾讯x5引擎的webview的flutter插件,暂时只支持android使用。
  • 提示:之前内嵌webview出现的一系列问题得到解决,请更新到最新版试用,谢谢支持。

x5内核介绍

x5内核,腾讯为改善移动端web体验的一种内核架构。加载更快,更省流量,视频播放优化,文件助手等等。

快速集成

添加依赖

pubspec.yaml文件中添加依赖:

dependencies:
  x5_webview: ^x.x.x # 最新版本见上方

初始化x5

安卓6.0+需在init之前请求动态权限,可以使用permission_handler

// 请求权限
Map<Permission, PermissionStatus> statuses = await [
  Permission.phone,
  Permission.storage,
].request();
// 判断权限
if (!(statuses[Permission.phone].isGranted &&
    statuses[Permission.storage].isGranted)) {
  print("权限被拒绝");
  return;
}

var isOk = await X5Sdk.init();
print(isOk ? "X5内核成功加载" : "X5内核加载失败");

如果你只是想要简单的展示web页面,可使用以下代码直接打开一个webActivity,性能更佳(推荐使用,视频播放也可以这个api):

X5Sdk.openWebActivity("https://www.baidu.com", title: "web页面");

使用内嵌webview

return Scaffold(
  appBar: AppBar(
    title: Text("X5WebView示例"),
  ),
  body: defaultTargetPlatform == TargetPlatform.android
      ? X5WebView(
          url: "http://debugtbs.qq.com",
          javaScriptEnabled: true,
          header: {"TestHeader": "测试"},
          userAgentString: "my_ua",
          // Url拦截,传null不会拦截会自动跳转
          onUrlLoading: (willLoadUrl) {
            _controller.loadUrl(willLoadUrl);
          },
          onWebViewCreated: (control) {
            _controller = control;
          },
          onPageFinished: () async {
            var url = await _controller.currentUrl();
            print(url);
            var body = await _controller.evaluateJavascript('document.body.innerHTML');
            print(body);
          },
        )
      :
      // 可替换为其他已实现ios webview,此处使用webview_flutter
      WebView(
          initialUrl: "https://www.baidu.com",
          javascriptMode: JavascriptMode.unrestricted,
          onWebViewCreated: (control) {
            _otherController = control;
            var body = _otherController.evaluateJavascript('document.body.innerHTML');
            print(body);
          },
        ),
);

内嵌webview js与flutter互调

flutter调用js

var body = await _controller.evaluateJavascript("document.body.innerHTML");

js调用flutter

flutter代码

X5WebView(
  ...
  javascriptChannels: JavascriptChannels(
    ["X5Web", "Toast"], (name, data) {
      switch (name) {
        ...
      }
    })
)

js代码

X5Web.postMessage("XXX")
Toast.postMessage("YYY")

打开本地html文件(使用assets文件,内嵌webview同理)

var fileS = await rootBundle.loadString("assets/index.html");
var url = Uri.dataFromString(fileS,
                          mimeType: 'text/html',
                          encoding: Encoding.getByName('utf-8'))
                      .toString();
X5Sdk.openWebActivity(url, title: "本地html示例");

注意事项

  • minSdkVersion 19以上

  • 该插件暂时只支持Android手机,IOS会使用无效。ios可使用webview_flutter或其他已实现IOS WKWebView插件

  • 一般手机安装了QQ,微信,QQ浏览器等软件,手机里自动会有X5内核,如果没有X5内核会在wifi下自动下载,X5内核没有加载成功会自动使用系统内核。详细配置可用手机打开以下链接查看X5内核的详情

    http://debugtbs.qq.com
    
  • 请使用真机测试,模拟器可能不能正常显示

  • 如果测试正常加载,打包后不能加载,可以尝试使用android studio打开android目录直接打包apk。或者使用以下命令行打包

    flutter build apk --target-platform android-arm --no-shrink
    

并增加混淆配置(详见example)

-dontwarn dalvik.**
-dontwarn com.tencent.smtt.**

-keep class com.tencent.smtt.** {
    *;
}

-keep class com.tencent.tbs.** {
    *;
}
  • android9.0版本webview联不了网在manifest添加

    <application
        ...
        android:usesCleartextTraffic="true">
    </application>
    
  • android7.0读写文件需要在manifest的application内添加(xml文件已在插件内,无需自己创建)

    <!-- 不使用androidx 请用android:name="android.support.v4.content.FileProvider" -->
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/x5webview_file_paths" />
    </provider>
    
  • X5Sdk.openWebActivity actionbar颜色自定义

    //1.
    implementation "androidx.appcompat:appcompat:1.1.0"
    
    //2.
    <style name="AppTheme" parent="ThemeOverlay.AppCompat.Dark">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">#2196F3</item>
        <item name="colorPrimaryDark">#1976D2</item>
        <item name="colorAccent">#FF4081</item>
        <item name="windowNoTitle">false</item>
        <item name="windowActionBar">true</item>
    </style>
    
    //3.
    <application
        ...
        android:theme="@style/AppTheme">
    
  • 有比较急的问题可以加我QQ:793710663

示例程序下载(密码:123456)

apk下载

完整示例demo

import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:x5_webview/x5_sdk.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  var crashInfo;
  bool isLoadOk = false;

  @override
  void initState() {
    super.initState();
    loadX5();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: Center(
        child: ListView(
          children: <Widget>[
            ElevatedButton(
              onPressed: () async {
                X5Sdk.openWebActivity("http://debugtbs.qq.com", title: "X5内核信息");
              },
              child: Text("查看X5内核信息"),
            ),
            ElevatedButton(
              onPressed: () async {
                var fileHtmlContents = await rootBundle.loadString("assets/index.html");
                var url = Uri.dataFromString(fileHtmlContents,
                    mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString();
                Navigator.of(context).push(CupertinoPageRoute(builder: (BuildContext context) {
                  return DemoWebViewPage(url);
                }));
              },
              child: Text("flutter内嵌x5webview"),
            ),
            ElevatedButton(
              onPressed: () async {
                var fileHtmlContents = await rootBundle.loadString("assets/index.html");
                var url = Uri.dataFromString(fileHtmlContents,
                    mimeType: 'text/html', encoding: Encoding.getByName('utf-8')).toString();

                await X5Sdk.openWebActivity(url, title: "本地html示例");
              },
              child: Text("本地html"),
            ),
            ElevatedButton(
              onPressed: () async {
                loadX5();
              },
              child: Text("重新加载内核"),
            ),
            Text(
              "内核状态:\n${crashInfo == null ? "未加载" : isLoadOk ? "加载成功---\n" + crashInfo.toString() : "加载失败---\n" + crashInfo.toString()}"),
          ],
        ),
      ),
    );
  }

  void showInputDialog({required ConfirmCallBack onConfirm, String defaultText = ""}) {
    final _controller = TextEditingController(text: defaultText);
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text("输入链接测试"),
          content: TextField(
            controller: _controller,
          ),
          actions: <Widget>[
            TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text("取消"),
            ),
            TextButton(
              onPressed: () async {
                Navigator.pop(context);
                onConfirm(_controller.text);
              },
              child: Text("跳转"),
            )
          ],
        );
      },
    );
  }

  var isLoad = false;

  void loadX5() async {
    if (isLoad) {
      showMsg("你已经加载过x5内核了,如果需要重新加载,请重启");
      return;
    }

    Map<Permission, PermissionStatus> statuses = await [
      Permission.phone,
      Permission.storage,
    ].request();
    if (!(statuses[Permission.phone]!.isGranted &&
        statuses[Permission.storage]!.isGranted)) {
      showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            content: Text("请同意所有权限后再尝试加载X5"),
            actions: [
              TextButton(
                onPressed: () {
                  Navigator.pop(context);
                },
                child: Text("取消"),
              ),
              TextButton(
                onPressed: () {
                  Navigator.pop(context);
                  loadX5();
                },
                child: Text("再次加载"),
              ),
              TextButton(
                onPressed: () {
                  Navigator.pop(context);
                  openAppSettings();
                },
                child: Text("打开设置页面"),
              ),
            ],
          );
        },
      );
      return;
    }

    await X5Sdk.setDownloadWithoutWifi(true);

    await X5Sdk.setX5SdkListener(X5SdkListener(
      onInstallFinish: (int code) {
        print("X5内核安装完成");
      },
      onDownloadFinish: (int code) {
        print("X5内核下载完成");
      },
      onDownloadProgress: (int progress) {
        print("X5内核下载中---$progress%");
      },
    ));
    print("----开始加载内核----");
    var isOk = await X5Sdk.init();
    print(isOk ? "X5内核成功加载" : "X5内核加载失败");

    var x5CrashInfo = await X5Sdk.getCrashInfo();
    print(x5CrashInfo);
    if (isOk) {
      x5CrashInfo = "tbs_core_version" + x5CrashInfo.split("tbs_core_version")[1];
    }
    setState(() {
      isLoadOk = isOk;
      crashInfo = x5CrashInfo;
    });

    isLoad = true;
  }

  void showMsg(String msg) {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          content: Text(msg),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text("我知道了"),
            )
          ],
        );
      },
    );
  }

  void openUrl(String url) {
    X5Sdk.openWebActivity(url, title: "web页面", callback: (url, headers) {
      print("拦截到url================$url");
      print("headers================$headers");
      openUrl(url);
    });
  }
}

typedef ConfirmCallBack = Function(String url);

通过上述内容,您可以更好地理解和使用x5_webview插件,希望对您有所帮助!


更多关于Flutter嵌入式WebView插件x5_webview的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter嵌入式WebView插件x5_webview的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,关于在Flutter中使用x5_webview插件来嵌入WebView,下面是一个基本的代码示例,展示如何集成和使用该插件。请注意,x5_webview是基于腾讯X5内核的WebView插件,因此你需要确保已经在你的Flutter项目中正确添加了该插件的依赖。

首先,确保在pubspec.yaml文件中添加x5_webview依赖:

dependencies:
  flutter:
    sdk: flutter
  x5_webview: ^最新版本号  # 请替换为实际的最新版本号

然后,运行flutter pub get来安装依赖。

接下来,你可以在你的Flutter项目中创建一个页面来使用x5_webview。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:x5_webview/x5_webview.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter WebView Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: WebViewDemoPage(),
    );
  }
}

class WebViewDemoPage extends StatefulWidget {
  @override
  _WebViewDemoPageState createState() => _WebViewDemoPageState();
}

class _WebViewDemoPageState extends State<WebViewDemoPage> {
  late X5WebViewController _controller;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('X5 WebView Demo'),
      ),
      body: X5WebView(
        initialUrl: 'https://www.example.com',  // 初始加载的URL
        javascriptMode: JavascriptMode.unrestricted,
        onWebViewCreated: (X5WebViewController webViewController) {
          _controller = webViewController;
          // 这里你可以进行一些额外的配置,比如添加JavaScript接口等
        },
        onPageFinished: (String url) {
          print('Page finished loading: $url');
        },
        onLoadError: (int code, String msg) {
          print('WebView load error: code=$code, msg=$msg');
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // 示例:在WebView中执行JavaScript代码
          await _controller.evalJavascript('alert("Hello from Flutter!");');
        },
        tooltip: 'Execute JS',
        child: Icon(Icons.code),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    // 清理WebView控制器资源
    _controller.dispose();
  }
}

在这个示例中,我们创建了一个包含X5WebView的页面。X5WebView接受几个重要的参数:

  • initialUrl:初始加载的网页URL。
  • javascriptMode:是否允许执行JavaScript,这里设置为JavascriptMode.unrestricted以允许执行JavaScript。
  • onWebViewCreated:当WebView创建完成时回调,你可以在这里获取X5WebViewController实例,用于后续操作如执行JavaScript代码。
  • onPageFinished:当网页加载完成时回调,这里用于打印加载完成的URL。
  • onLoadError:当网页加载出错时回调,这里用于打印错误信息。

此外,我们还添加了一个浮动操作按钮(FAB),用于在WebView中执行JavaScript代码,作为示例。

请注意,由于x5_webview插件依赖于腾讯X5内核,因此在Android平台上使用时,你需要在android/app/build.gradle文件中添加一些特定的配置来集成X5内核。这些配置通常包括下载X5内核的SDK并将其集成到你的Android项目中。具体步骤请参考x5_webview插件的官方文档或GitHub仓库。

希望这个示例对你有帮助!

回到顶部