手记

Flutter与H5混合入门:轻松搭建跨平台应用

概述

本文介绍了如何入门flutter与H5混合开发,涵盖了环境搭建、项目配置以及基础交互等内容,帮助开发者快速上手。文章详细讲解了Flutter和H5的技术特点,并提供了具体的配置步骤和示例代码,确保读者能够顺利进行混合应用开发。此外,还探讨了混合应用的性能优化和用户体验提升方法,为开发者提供了实用的建议和最佳实践。

引入与环境搭建
介绍Flutter和H5技术

Flutter是由Google开发的开源框架,用于构建移动应用。它使用Dart语言编写,支持Android与iOS平台的原生性能,同时提供跨平台的开发体验。Flutter具有丰富的动画和图形渲染能力,能够快速创建美观且流畅的用户界面。

H5(HTML5)是一种网页标准,它由HTML、CSS、JavaScript等技术组成。H5允许开发者创建交互性强、动态的网页,广泛应用于Web应用和移动应用中。H5页面可以轻松地在所有现代浏览器中运行,无需特定的插件或环境。

设置Flutter开发环境

安装Flutter开发环境需要一些步骤。首先,在Flutter官网下载适合的操作系统版本的安装包。以下是详细的安装步骤:

  1. 安装Flutter SDK:从Flutter官网下载Flutter SDK,解压到指定目录。
  2. 添加环境变量:将Flutter SDK的bin目录路径添加到系统环境变量中。
  3. 配置工具链:安装Android Studio和Android SDK,确保Android设备可以被Flutter识别。
  4. 安装Flutter插件:在Android Studio中安装Flutter插件,以获得代码编辑和调试的支持。
  5. 安装平台工具:安装iOS模拟器和开发工具链,例如Xcode。

示例代码:配置环境变量的示例(Windows系统)。

set PATH=C:\flutter\bin;%PATH%

在安装完成后,打开命令行工具,输入flutter doctor命令,检查所有依赖项是否安装成功。

配置Web环境以支持H5

要使Flutter项目支持H5页面,需要配置Web环境。首先,确保已经安装了Node.js。然后,按照以下步骤配置Flutter项目以支持Web:

  1. 创建Flutter Web项目:使用Flutter命令行工具创建一个新的Flutter Web项目。
flutter create my_web_project
cd my_web_project
  1. 配置Flutter Web:在项目根目录下,找到web目录,编辑index.html文件,将H5页面嵌入到Flutter应用中。

示例代码:在index.html文件中嵌入H5页面的示例。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Flutter Web Example</title>
</head>
<body>
<flutter-view></flutter-view>
<script src="main.dart.js" type="application/javascript"></script>
<script>
  document.addEventListener('DOMContentLoaded', function () {
    const h5Container = document.createElement('div');
    h5Container.id = 'h5-container';
    document.body.appendChild(h5Container);
    fetch('https://example.com/h5.html')
      .then(response => response.text())
      .then(data => {
        h5Container.innerHTML = data;
      });
  });
</script>
</body>
</html>

在上述示例代码中,index.html文件中嵌入了一个div元素,并通过fetch函数从指定URL获取H5页面的内容,将其插入到div中。

  1. 运行Flutter Web项目:使用Flutter命令行工具运行项目。
flutter run -d chrome

配置iOS环境以支持H5

  1. 安装Xcode。
  2. 在项目中运行flutter pub get来更新依赖。
  3. 在Xcode中打开项目文件,配置iOS工程。

示例代码:配置iOS工程

// 示例步骤:
// 1. 安装Xcode。
// 2. 在项目中运行`flutter pub get`来更新依赖。
// 3. 在Xcode中打开项目文件,配置iOS工程。

// 示例代码:配置iOS工程
void configureIOSProject() {
  // 在Xcode中打开项目文件
  openProjectInXcode('my_web_project');

  // 配置iOS开发环境
  // 添加必要的配置,如调整Info.plist、修改AppDelegate.m等
}
Flutter与H5基础
Flutter基础知识概述

Flutter主要由以下几个部分组成:

  1. Widget(组件):是Flutter应用的构建块。Widget定义了用户界面的外观和行为。
  2. State(状态):State对象包含了控件的状态。状态可以决定控件的显示方式,例如按钮是否按下等。
  3. StatelessWidgetStatefulWidget:这两类Widget分别是无状态组件和有状态组件。StatelessWidget没有内置的状态,其显示方式不随时间变化,而StatefulWidget可以随状态变化而变化。
  4. InheritedWidget:用于在组件树中传递数据或设置。
  5. BuildContext:表示组件在组件树中的位置,用于访问父组件的信息。
  6. Navigator:用于管理页面路由,实现导航功能。
  7. Animation:动画是Flutter的一大亮点,提供了丰富的动画实现方式。

示例代码:创建一个简单的Flutter应用,包含一个按钮和一个文本显示,使用StatefulWidget管理按钮的点击状态。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Demo'),
        ),
        body: MyButton(),
      ),
    );
  }
}

class MyButton extends StatefulWidget {
  @override
  _MyButtonState createState() => _MyButtonState();
}

class _MyButtonState extends State<MyButton> {
  bool isPressed = false;

  void onPressed() {
    setState(() {
      isPressed = !isPressed;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(isPressed ? 'Pressed' : 'Not Pressed'),
          ElevatedButton(
            onPressed: onPressed,
            child: Text('Toggle'),
          ),
        ],
      ),
    );
  }
}

在上述示例代码中,MyApp是一个简单的Flutter应用,包含一个Scaffold组件,其中包含一个AppBar和一个MyButton组件。MyButton是一个StatefulWidget,它拥有一个状态isPressed,点击按钮时,状态会切换,并触发重新构建。

H5基础知识概述

H5页面主要由以下几个部分组成:

  1. 标签(Tag):H5页面由多个HTML标签组成,每个标签定义不同的页面元素。
  2. 元素(Element):标签通常包含开始标签和结束标签,中间的内容称为元素。元素可以包含文本、嵌套的标签等。
  3. 属性(Attribute):属性是附加在标签上的指令,用于定义元素的特定行为或样式。
  4. CSS:CSS用于定义页面的样式,包括颜色、字体、布局等。
  5. JavaScript:JavaScript用于在页面中实现交互功能,处理用户输入、动态改变页面内容等。

示例代码:创建一个简单的H5页面,包含一个按钮和一个文本显示,使用JavaScript动态改变文本内容。

<!DOCTYPE html>
<html>
<head>
<title>H5 Demo</title>
<style>
body {
  text-align: center;
  font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<h1 id="text">Not Pressed</h1>
<button onclick="toggleText()">Toggle</button>

<script>
function toggleText() {
  const text = document.getElementById('text');
  text.innerText = text.innerText === 'Pressed' ? 'Not Pressed' : 'Pressed';
}
</script>
</body>
</html>

在上述示例代码中,<h1>标签用于显示文本,<button>标签用于触发点击事件。点击按钮时,JavaScript函数toggleText会被调用,改变<h1>标签中的文本内容。

Flutter项目与H5项目的交互

Flutter项目可以通过多种方式与H5项目交互:

  1. 嵌入H5页面:将H5页面嵌入到Flutter应用中,使用Webview插件或其他方式显示H5内容。
  2. 通信机制:设置Flutter和H5页面之间的通信机制,例如JavaScript接口、WebSocket或自定义消息传递协议。
  3. 事件处理:监听H5页面中的事件,并在Flutter中进行相应处理。
  4. 数据传递:通过JavaScript接口或WebSocket传递数据,实现场景之间的数据共享。

示例代码:在Flutter项目中嵌入H5页面,并通过JavaScript接口传递消息。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter WebView Example'),
        ),
        body: WebView(
          initialUrl: 'https://example.com/h5.html',
          javascriptMode: JavascriptMode.unrestricted,
          onMessage: (message) {
            print('Received message from H5: ${message.message}');
          },
        ),
      ),
    );
  }
}

在上述示例代码中,MyApp是一个简单的Flutter应用,包含一个WebView组件,用于显示H5页面。WebView组件的javascriptMode设置为JavascriptMode.unrestricted,允许运行JavaScript代码。通过onMessage回调函数,可以接收来自H5页面的消息。

示例代码:实现JavaScript通信的具体示例

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter WebView Example'),
        ),
        body: WebView(
          initialUrl: 'https://example.com/h5.html',
          javascriptMode: JavascriptMode.unrestricted,
          javascriptChannels: <JavascriptChannel>[
            JavascriptChannel(
              name: 'flutterHandler',
              onMessageReceived: (JavascriptMessage message) {
                print('Received message from H5: ${message.message}');
              },
            ),
          ].toSet(),
        ),
      ),
    );
  }
}

在上述示例代码中,WebView组件的javascriptChannels设置了名为flutterHandler的JavaScript通道,用于接收来自H5页面的消息。

混合应用开发实践
创建Flutter基础项目

创建一个新的Flutter项目,可以使用Flutter命令行工具或Flutter插件。以下是使用命令行工具创建项目的步骤:

  1. 打开命令行工具。
  2. 输入flutter create my_flutter_project命令,创建一个新的Flutter项目。
  3. 打开项目文件夹,编辑lib/main.dart文件,替换默认代码为自定义代码。

示例代码:创建一个简单的Flutter应用,显示一个文本和一个按钮。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('My Flutter App'),
        ),
        body: Center(
          child: Text('Hello, Flutter!'),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {},
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

在上述示例代码中,MyApp是一个简单的Flutter应用,包含一个AppBar、一个Scaffold组件和一个Center组件,显示一个文本和一个按钮。

在Flutter中嵌入H5页面

要将H5页面嵌入到Flutter应用中,可以使用webview_flutter插件。该插件允许在Flutter应用中显示Web页面。

  1. 在Flutter项目中添加webview_flutter插件。
  2. 在需要显示H5页面的屏幕上,使用WebView组件。

示例代码:在Flutter项目中使用webview_flutter插件显示H5页面。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter WebView Example'),
        ),
        body: WebView(
          initialUrl: 'https://example.com/h5.html',
          javascriptMode: JavascriptMode.unrestricted,
        ),
      ),
    );
  }
}

在上述示例代码中,MyApp是一个简单的Flutter应用,包含一个AppBar和一个WebView组件,显示指定的H5页面。

Flutter与H5通信的方法和技巧

Flutter应用可以通过JavaScript接口与H5页面进行通信。这可以通过WebView组件的addJavascriptChannel方法实现。

  1. 在Flutter中定义JavaScript通道。
  2. 在H5页面中调用JavaScript接口,向Flutter传递数据。
  3. 在Flutter中处理接收到的数据。

示例代码:在Flutter项目中定义JavaScript通道,并在H5页面中调用接口。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter WebView Example'),
        ),
        body: WebView(
          initialUrl: 'https://example.com/h5.html',
          javascriptMode: JavascriptMode.unrestricted,
          javascriptChannels: <JavascriptChannel>[
            JavascriptChannel(
              name: 'flutterHandler',
              onMessageReceived: (JavascriptMessage message) {
                print('Received message from H5: ${message.message}');
              },
            ),
          ].toSet(),
        ),
      ),
    );
  }
}

在上述示例代码中,MyApp是一个简单的Flutter应用,包含一个AppBar和一个WebView组件,显示指定的H5页面,并定义了一个名为flutterHandler的JavaScript通道。

示例代码:在H5页面中调用JavaScript接口,向Flutter传递数据。

<!DOCTYPE html>
<html>
<head>
<title>H5 Demo</title>
</head>
<body>
<script>
function sendMessage() {
  window.flutterHandler.postMessage('Hello, Flutter!');
}
</script>
<button onclick="sendMessage()">Send Message</button>
</body>
</html>

在上述示例代码中,<button>标签用于触发点击事件,点击按钮时,JavaScript函数sendMessage会被调用,向Flutter传递消息。

实战案例解析
构建实际应用案例

构建一个实际的混合应用案例,例如一个简单的新闻阅读器应用。该应用可以展示新闻列表,并在点击新闻条目时显示新闻详情。

创建Flutter项目

首先,使用flutter create命令创建一个新的Flutter项目。

flutter create news_app
cd news_app

添加依赖项

pubspec.yaml文件中添加必要的依赖项,例如webview_flutter用于显示H5页面。

dependencies:
  flutter:
    sdk: flutter
  webview_flutter: ^5.0.0

实现新闻列表

在Flutter项目中实现一个新闻列表,包含新闻标题和简短摘要。

示例代码:实现一个简单的新闻列表。

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

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

class MyNewsApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('News Reader'),
        ),
        body: ListView.builder(
          itemCount: 5,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text('News Title $index'),
              subtitle: Text('News Summary $index'),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => NewsDetailPage(),
                  ),
                );
              },
            );
          },
        ),
      ),
    );
  }
}

class NewsDetailPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('News Detail'),
      ),
      body: WebView(
        initialUrl: 'https://example.com/news/$index',
        javascriptMode: JavascriptMode.unrestricted,
      ),
    );
  }
}

在上述示例代码中,MyNewsApp是一个简单的Flutter应用,包含一个新闻列表。点击列表项时,会跳转到NewsDetailPage页面显示新闻详情。WebView组件用于显示指定的H5页面。

封装H5页面

在H5页面中实现新闻详情的显示,确保能够与Flutter应用进行通信。

示例代码:在H5页面中实现新闻详情的显示。

<!DOCTYPE html>
<html>
<head>
<title>News Detail</title>
<style>
body {
  font-family: Arial, sans-serif;
  text-align: center;
}
</style>
</head>
<body>
<h1>News Title</h1>
<p>News Summary</p>
<script>
function sendBackToFlutter() {
  window.flutterHandler.postMessage('Back to Flutter');
}
</script>
<button onclick="sendBackToFlutter()">Back</button>
</body>
</html>

在上述示例代码中,H5页面包含一个新闻标题和摘要,并提供一个按钮用于返回Flutter应用。

优化混合应用性能与用户体验

为了优化混合应用的性能和用户体验,可以考虑以下几个方面:

  • 减少加载时间:通过缓存和预加载技术减少H5页面的加载时间。
  • 提高渲染效率:合理使用WebView组件的配置选项,减少不必要的渲染操作。
  • 优化交互体验:确保H5页面和Flutter应用之间的交互流畅,无明显延迟。

示例代码:优化WebView组件的加载性能。

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

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

class MyNewsApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('News Reader'),
        ),
        body: ListView.builder(
          itemCount: 5,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text('News Title $index'),
              subtitle: Text('News Summary $index'),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => NewsDetailPage(),
                  ),
                );
              },
            );
          },
        ),
      ),
    );
  }
}

class NewsDetailPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('News Detail'),
      ),
      body: WebView(
        initialUrl: 'https://example.com/news/$index',
        javascriptMode: JavascriptMode.unrestricted,
        javascriptChannels: <JavascriptChannel>[
          JavascriptChannel(
            name: 'flutterHandler',
            onMessageReceived: (JavascriptMessage message) {
              print('Received message from H5: ${message.message}');
            },
          ),
        ].toSet(),
        initialScale: 1.0,
        zoomControlsEnabled: false,
      ),
    );
  }
}

在上述示例代码中,WebView组件的javascriptMode设置为JavascriptMode.unrestricted,并添加了一个名为flutterHandler的JavaScript通道,用于接收H5页面的消息。initialScale设置为1.0,防止页面缩放;zoomControlsEnabled设置为false,禁用缩放控件。

常见问题与解决方案
常见错误及解决方法

在Flutter与H5混合开发过程中,可能会遇到一些常见的错误,以下是一些常见的问题及其解决方法:

  • JavaScript调用失败:检查JavaScript接口是否正确定义,并确保Flutter应用中相应的方法已被实现。
  • 性能问题:优化WebView组件的配置选项,减少不必要的渲染操作,提高加载效率。
  • 界面布局不一致:确保H5页面在不同设备和浏览器上的兼容性,使用响应式设计和适配技术。

示例代码:解决JavaScript调用失败的问题。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter WebView Example'),
        ),
        body: WebView(
          initialUrl: 'https://example.com/h5.html',
          javascriptMode: JavascriptMode.unrestricted,
          javascriptChannels: <JavascriptChannel>[
            JavascriptChannel(
              name: 'flutterHandler',
              onMessageReceived: (JavascriptMessage message) {
                print('Received message from H5: ${message.message}');
              },
            ),
          ].toSet(),
        ),
      ),
    );
  }
}

在上述示例代码中,WebView组件的javascriptMode设置为JavascriptMode.unrestricted,并添加了一个名为flutterHandler的JavaScript通道,用于接收H5页面的消息。

调试与测试技巧

调试和测试是混合应用开发的重要环节,以下是一些调试和测试的技巧:

  • 使用浏览器开发者工具:利用浏览器的开发者工具调试H5页面,查看网络请求、元素样式和JavaScript错误。
  • 模拟真实环境:使用模拟器或真机测试Flutter应用,确保在不同设备上的兼容性和性能。
  • 编写单元测试:编写单元测试代码,确保各个组件和功能的正确性。

示例代码:使用浏览器开发者工具调试H5页面。

<!DOCTYPE html>
<html>
<head>
<title>H5 Debugging</title>
<style>
body {
  font-family: Arial, sans-serif;
  text-align: center;
}
</style>
</head>
<body>
<h1>Debugging Page</h1>
<p id="debugText">Debug Text</p>
<script>
function debug() {
  console.log('Debugging message');
  // 调试信息输出
}
</script>
<button onclick="debug()">Debug</button>
</body>
</html>

在上述示例代码中,H5页面包含一个文本元素和一个按钮,点击按钮时,JavaScript函数debug会被调用,输出调试信息。

示例代码:编写单元测试代码

import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';

void main() {
  testWidgets('Check if WebView is loading correctly', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    await tester.pump(Duration(seconds: 5)); // 等待5秒加载完成
    expect(find.byType(WebView), findsOneWidget);
  });
}

在上述示例代码中,编写了一个简单的单元测试,用于检查WebView是否正确加载。

Flutter与H5混合开发的最佳实践

以下是一些Flutter与H5混合开发的最佳实践:

  • 明确划分职责:将Flutter应用和H5页面的职责明确划分,避免功能重叠。
  • 统一风格:确保Flutter和H5页面在风格上保持一致,提高用户体验。
  • 优化交互体验:确保Flutter应用和H5页面之间的交互流畅,无明显延迟。
  • 性能优化:优化WebView组件的配置选项,减少不必要的渲染操作,提高加载效率。

示例代码:优化WebView组件的配置选项,提高加载效率。

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter WebView Example'),
        ),
        body: WebView(
          initialUrl: 'https://example.com/h5.html',
          javascriptMode: JavascriptMode.unrestricted,
          javascriptChannels: <JavascriptChannel>[
            JavascriptChannel(
              name: 'flutterHandler',
              onMessageReceived: (JavascriptMessage message) {
                print('Received message from H5: ${message.message}');
              },
            ),
          ].toSet(),
          gestureNavigationEnabled: true,
          initialScale: 1.0,
          zoomControlsEnabled: false,
        ),
      ),
    );
  }
}

在上述示例代码中,WebView组件的gestureNavigationEnabled设置为true,启用手势导航;initialScale设置为1.0,防止页面缩放;zoomControlsEnabled设置为false,禁用缩放控件。

结语与进阶方向
Flutter与H5混合开发的优势

Flutter与H5混合开发具有以下优势:

  • 跨平台支持:Flutter应用可以在Android和iOS上运行,而H5页面可以在所有现代浏览器中运行。
  • 快速迭代:Flutter提供了高效的编译和热重载功能,适合快速迭代开发。
  • 丰富功能:Flutter拥有丰富的动画和图形渲染能力,H5页面则可以实现复杂的交互功能。
深入学习资源推荐

以下是一些深入学习Flutter与H5混合开发的资源:

  • 官方文档:Flutter官方文档提供了详细的API参考和开发指南。
  • 在线课程:慕课网提供了大量的Flutter和H5相关的在线课程。
  • 开源项目:GitHub上有很多优秀的Flutter与H5混合开发的开源项目,可以学习和参考。

示例代码:推荐学习资源

- Flutter官方文档:https://flutter.dev/docs
- 慕课网Flutter课程:https://www.imooc.com/course/list/flutter
- GitHub开源项目:https://github.com/flutter/flutter/tree/master/examples
未来技术趋势与发展方向

随着技术的发展,Flutter与H5混合开发将会更加普及和成熟。未来可能会出现以下发展趋势:

  • 更完善的工具链:开发工具和插件将进一步完善,提高开发效率和体验。
  • 更好的性能优化:优化混合应用的性能和加载速度,提高用户体验。
  • 更多的应用场景:混合应用将被应用于更多的场景,包括企业应用、游戏等。

通过不断学习和实践,掌握Flutter与H5混合开发的技术,可以创建出更加强大和灵活的应用。

0人推荐
随时随地看视频
慕课网APP