闲置不用的智能手机可以用来搭建本地web站点,托管静态网页,主要应用场景是把保存的电子资料放在本地web站点中供一家人(或者室友)访问,方便管理和查阅学习资料,还有其它自己想不到的应用场景
看懂此文章需要满足以下条件
- 熟悉使用AndroidStudio开发工具
- 会开发Flutter项目,了解插件相关知识
- 会开发网页模板项目,了解部署静态网页托管
Pub get
让工具联网加载好以下插件dependencies:
flutter:
sdk: flutter
# 在这里对应下添加即可...
dhttpd: ^4.0.1 #HTTP服务支持
path_provider: ^2.0.9 #文件路径适配器
ai_barcode: ^3.0.1 #二维码(条形码)
url_launcher: ^6.1.0 #可打开访问链接
import 'dart:io';
import 'package:ai_barcode/ai_barcode.dart';
import 'package:dhttpd/dhttpd.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '本地web服务',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
/// 本地web服务,静态网页托管
class _MyHomePageState extends State<MyHomePage> {
//...
}
_MyHomePageState
类这里,写好代码如下/// 本地web服务,静态网页托管
class _MyHomePageState extends State<MyHomePage> {
Dhttpd? _dpd;
String _path = "";
String _host = "";
late CreatorController _creatorController;
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;//获取屏幕大小
double vw = size.width < size.height ? size.width : size.height;//取最小的,例如屏宽度
return Scaffold(
body: SingleChildScrollView(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: vw*0.6,//屏宽度的十分之六
height: vw*0.6,
//加边框
decoration: const ShapeDecoration(
shape: RoundedRectangleBorder(
side: BorderSide(
color: Colors.black12,
width: 15,
),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
),
//二维码组件
child: PlatformAiBarcodeCreatorWidget(
creatorController: _creatorController,
initialValue: "请连接路由器局域网无AP隔离的WIFI",
),
//加外边距
margin: const EdgeInsets.fromLTRB(0, 50, 0, 20),
),
const Text("手机浏览器扫此码即可访问"),
const Divider(height: 1,),
//站点服务的状态展示
Padding(padding: const EdgeInsets.all(20), child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("站点地址: http://$_host"),
Text("存放路径: $_path"),
],
),),
],
),
),
),
//打开链接地址访问
floatingActionButton: FloatingActionButton(
onPressed: _launchUrl,
tooltip: 'View',
child: const Icon(Icons.web),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
@override
void initState() {
// TODO: implement initState
super.initState();
_creatorController = CreatorController();
//初始化本地Web服务
_initLocalServer();
}
void _initLocalServer() async {
//获取内置sd卡存放目录
var dir = await getExternalStorageDirectory();
if (dir!=null) {
var webDir = Directory("${dir.path}/www");
if (!webDir.existsSync()) {
webDir.createSync();//若没有www文件夹就创建一个
}
var webFile = File("${dir.path}/www/index.html");
if (!webFile.existsSync()) {
var html = await rootBundle.loadString("assets/www/index.html");
webFile.writeAsStringSync(html);//若没有index.html网页文件就创建一个
}
String? _address;
//遍历查找ip地址
var interfaces = await NetworkInterface.list(includeLoopback: false, type: InternetAddressType.any);
for (var interface in interfaces) {
debugPrint("### name: ${interface.name} ");
int i = 0;
for (var address in interface.addresses) {
debugPrint("${i++}. ${address.address}\n");
//把找到的第一个ip地址赋值给_address
_address ??= address.address;
}
}
//如果有ip地址,就传给本地web服务器
if (_address!=null) {
_dpd = await Dhttpd.start(path: webDir.path, address: _address);
}else {
_dpd = await Dhttpd.start(path: webDir.path);
}
//更新状态显示
setState(() {
if (_dpd!=null) {
_path = _dpd!.path;
_host = "${_dpd!.host}:${_dpd!.port}";
} else {
_path = dir.path;
}
});
//如果有ip地址,就重新生成二维码,方便其它手机扫码访问
if (_address!=null){
_creatorController.updateValue(value: "http://$_host");
}
}
}
//打开链接地址访问
void _launchUrl() async {
Uri _url = Uri.parse("http://$_host");
if (!await launchUrl(_url)) throw 'Could not launch $_url';
}
@override
void dispose() {
// TODO: implement dispose
if (_dpd!=null){
_dpd!.destroy();//退出时不忘关闭服务器
}
super.dispose();
}
}
小提示
代码中有个assets/www/index.html
是网页文件,这里就不讲了,自己写网页放在项目中即可
如果还不熟悉,请直接看笔者TA远方上传的项目源码,直接编译运行
- 如果没有可用的WIFI路由器(默认开启了AP隔离,无法在局域网内互相访问),就在智能手机上设置打开WIFI热点,开启后,再运行此项目编译的APP,就能看到可以访问的IP地址
- 如果IP地址出现
localhost
,127.0.0.1
,0.0.0.0
,则这个除了本机能访问外,其它手机或电脑是不能访问到的- 如果有自己开发的网页项目,就在手机上打开文件管理,在指定的存放路径文件夹www下,将里面的所有文件(包括
index.html
)替换为自己的即可