今年国庆假期终于可以憋在家里了不用出门了,不用出去看后脑了,真的是一种享受。这么好的光阴怎么浪费,睡觉、吃饭、打豆豆这怎么可能(耍多了也烦),完全不符合我们程序员的作风,赶紧起来把文章写完。
这篇文章比较基础,在国庆期间的业余时间写的,这几天又完善了下,力求把更多的前端所涉及到的关于文件上传的各种场景和应用都涵盖了,若有疏漏和问题还请留言斧正和补充。
以下是本文所涉及到的知识点,break or continue ?
原理很简单,就是根据 http 协议的规范和定义,完成请求消息体的封装和消息体的解析,然后将二进制内容保存到文件。
我们都知道如果要上传一个文件,需要把 form 标签的enctype设置为multipart/form-data,同时method必须为post方法。
那么multipart/form-data表示什么呢?
multipart互联网上的混合资源,就是资源由多种元素组成,form-data表示可以使用HTML Forms 和 POST 方法上传文件,具体的定义可以参考RFC 7578。
multipart/form-data 结构
看下 http 请求的消息体
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次请求要上传文件,其中boundary表示分隔符,如果要上传多个表单项,就要使用boundary分割,每个表单项由———XXX开始,以———XXX结尾。
每一个表单项又由Content-Type和Content-Disposition组成。
Content-Disposition: form-data 为固定值,表示一个表单元素,name 表示表单元素的 名称,回车换行后面就是name的值,如果是上传文件就是文件的二进制内容。
Content-Type:表示当前的内容的 MIME 类型,是图片还是文本还是二进制数据。
解析
客户端发送请求到服务器后,服务器会收到请求的消息体,然后对消息体进行解析,解析出哪是普通表单哪些是附件。
可能大家马上能想到通过正则或者字符串处理分割出内容,不过这样是行不通的,二进制buffer转化为string,对字符串进行截取后,其索引和字符串是不一致的,所以结果就不会正确,除非上传的就是字符串。
不过一般情况下不需要自行解析,目前已经有很成熟的三方库可以使用。
至于如何解析,这个也会占用很大篇幅,后面的文章在详细说。
使用 form 表单上传文件
在 ie时代,如果实现一个无刷新的文件上传那可是费老劲了,大部分都是用 iframe 来实现局部刷新或者使用 flash 插件来搞定,在那个时代 ie 就是最好用的浏览器(别无选择)。
DEMO
这种方式上传文件,不需要 js ,而且没有兼容问题,所有浏览器都支持,就是体验很差,导致页面刷新,页面其他数据丢失。
HTML
<form method="post" action="http://localhost:8100" enctype="multipart/form-data">
选择文件:
<input type="file" name="f1"/> input 必须设置 name 属性,否则数据无法发送<br/>
<br/>
标题:<input type="text" name="title"/><br/><br/><br/>
<button type="submit" id="btn-0">上 传</button>
</form>
复制代码
服务端文件的保存基于现有的库koa-body结合 koa2实现服务端文件的保存和数据的返回。
在项目开发中,文件上传本身和业务无关,代码基本上都可通用。
在这里我们使用koa-body库来实现解析和文件的保存。
koa-body 会自动保存文件到系统临时目录下,也可以指定保存的文件路径。
然后在后续中间件内得到已保存的文件的信息,再做二次处理。
NODE
/**
* 服务入口
*/
var http = require('http');
var koaStatic = require('koa-static');
var path = require('path');
var koaBody = require('koa-body');//文件保存库
var fs = require('fs');
var Koa = require('koa2');
var app = new Koa();
var port = process.env.PORT || '8100';
var uploadHost= `http://localhost:${port}/uploads/`;
app.use(koaBody({
formidable: {
//设置文件的默认保存目录,不设置则保存在系统临时目录下 os
uploadDir: path.resolve(__dirname, '../static/uploads')
},
multipart: true // 开启文件上传,默认是关闭
}));
//开启静态文件访问
app.use(koaStatic(
path.resolve(__dirname, '../static')
));
//文件二次处理,修改名称
app.use((ctx) => {
var file = ctx.request.files.f1;//得道文件对象
var path = file.path;
var fname = file.name;//原文件名称
var nextPath = path+fname;
if(file.size>0 && path){
//得到扩展名
var extArr = fname.split('.');
var ext = extArr[extArr.length-1];
var nextPath = path+'.'+ext;
//重命名文件
fs.renameSync(path, nextPath);
}
//以 json 形式输出上传文件地址
ctx.body = `{
"fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/')+1)}"
}`;
});
/**
* http server
*/
var server = http.createServer(app.callback());
server.listen(port);
console.log('demo1 server start ...... ');
复制代码
CODE
https://github.com/Bigerfe/fe-learn-code/
网页中常见的多媒体文件包括音频文件和视频文件,对于在线音频和视频,我们往往都是使用embed标签来插入。embed语法:
1 <embed src="”视频地址”" type="”audio/x-pn-realaudio-plugin”"
2 console="”Clip1〃" controls="”ControlPanel,StatusBar”" height="”330〃"
3 width="”450〃" autostart="”true”" title="undefined">
<embed src="要播放的文件网址" ;="" autostart="true" loop="true" width="400"
height="350">
html中网页中如何插入音频和视频?
举例1:插入音频文件
1 <title>插入音频文件</title>
2
3
4 <embed src="media/西班牙舞曲.mp3" width="400px" height="80px">
在浏览器预览效果如下:
说明:
我们可以看到,使用embed标签插入音频文件还会有一个播放界面,界面上有几个简单的功能按钮。
举例2:插入视频文件
1 <title>插入音频文件</title>
2
3
4 <embed src="media/小苹果.wmv" width="400px" height="80px">
在浏览器预览效果如下:
注意:
由于音频和视频文件比较大,所以在这里我们就不提供大家在线测试的功能。不过大家可以在自己计算机上面测试一下代码。
使用embed标签插入视频,在浏览器我们也可以看到,浏览器提供了一个简单的操作界面。embed标签支持的视频格式很多,大部分主流格式都支持。
embed标签能支持大部分格式的视频文件,反正主流的如.mp4、.avi、.rmvb等都支持。如果你使用embed标签不能播放视频,那就可能是你视频格式有问题或者编码有问题。你可以用格式工厂转换一下格式。
以上就是html中网页中如何插入音频和视频?
什么要学习Android与H5互调?
微信,QQ空间等大量软件都内嵌了H5,不得不说是一种趋势。Android与H5互调可以让我们的实现混合开发,至于混合开发就是在一个App中内嵌一个轻量级的浏览器,一部分原生的功能改为Html 5来开发。
优势:使用H5实现的功能能够在不升级App的情况下动态更新,而且可以在Android或iOS的App上同时运行,节约了成本,提高了开发效率。
原理:其实就是Java代码和JavaScript之间的调用。
开局插入一张文章的目录结构:
WebView简介
要实现Android与H5互调,WebView是一个很重要的控件,WebView可以很好地帮助我们展示html页面,所以有必要先了解一下WebView。
一丶WebView常用方法
//加载assets目录下的test.html文件 webView.loadUrl("file:///android_asset/test.html"); //加载网络资源(注意要加上网络权限) webView.loadUrl("http://blog.csdn.net");
webView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if("http://www.jikedaohang.com/".equals(url)) { view.loadUrl("https://www.baidu.com/"); } return true; } });
mWebView.setWebViewClient(new WebViewClient(){ @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { WebResourceResponse response = null; if (url.contains("logo")) { try { InputStream logo = getAssets().open("logo.png"); response = new WebResourceResponse("image/png", "UTF-8", logo); } catch (IOException e) { e.printStackTrace(); } } return response; } });
webView.setWebViewClient(new WebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); // 开始加载网页时处理 如:显示"加载提示" 的加载对话框 ... } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // 网页加载完成时处理 如:让 加载对话框 消失 ... } @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { super.onReceivedError(view, errorCode, description, failingUrl); // 加载网页失败时处理 如:提示失败,或显示新的界面 ... } });
webView.setWebViewClient(new WebViewClient() { @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); // 接受信任所有网站的证书 // handler.cancel(); // 默认操作 不处理 // handler.handleMessage(null); // 可做其他处理 } });
webView.setWebChromeClient(new WebChromeClient() { public void onProgressChanged(WebView view, int progress) { setTitle("页面加载中,请稍候..." + progress + "%"); setProgress(progress * 100); if (progress == 100) { //... } } });
//1.首先在WebView初始化时添加如下代码 if(Build.VERSION.SDK_INT >= 19) { /*对系统API在19以上的版本作了兼容。因为4.4以上系统在onPageFinished时再恢复图片加载时,如果存在多张图片引用的是相同的src时,会只有一个image标签得到加载,因而对于这样的系统我们就先直接加载。*/ webView.getSettings().setLoadsImagesAutomatically(true); } else { webView.getSettings().setLoadsImagesAutomatically(false); } //2.在WebView的WebViewClient子类中重写onPageFinished()方法添加如下代码: @Override public void onPageFinished(WebView view, String url) { if(!webView.getSettings().getLoadsImagesAutomatically()) { webView.getSettings().setLoadsImagesAutomatically(true); } }
class MyDownloadListenter implements DownloadListener{ @Override public void onDownloadStart(String url, String userAgent,String contentDisposition, String mimetype, long contentLength) { //下载任务...,主要有两种方式 //(1)自定义下载任务 //(2)调用系统的download的模块 Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent); } }
webview.setDownloadListener(new MyDownloadListenter());
public boolean onKeyDown(int keyCode, KeyEvent event) { //其中webView.canGoBack()在webView含有一个可后退的浏览记录时返回true if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) { webView.goBack(); return true; } return super.onKeyDown(keyCode, event); } }
二丶WebSettings配置
WebSettings webSettings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setDomStorageEnabled(true);
settings.setDatabasePath(cacheDirPath);
settings.setAppCachePath(cacheDirPath);
settings.setDefaultTextEncodingName(“utf-8”);
settings.setUseWideViewPort(false);
settings.setSupportZoom(true);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
settings.supportMultipleWindows();
settings.setAllowFileAccess(true);
settings.setNeedInitialFocus(true);
settings.setBuiltInZoomControls(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setLoadWithOverviewMode(true);
settings.setLoadsImagesAutomatically(true);
三丶WebViewClient 的回调方法列表
WebViewClient主要用来辅助WebView处理各种通知、请求等事件,通过setWebViewClient方法设置。
(1)更新历史记录
doUpdateVisitedHistory(WebView view, String url, boolean isReload)
(2)应用程序重新请求网页数据
onFormResubmission(WebView view, Message dontResend, Message resend)
(3)在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
onLoadResource(WebView view, String url)
(4)开始载入页面调用,通常我们可以在这设定一个loading的页面,告诉用户程序在等待网络响应。
onPageStarted(WebView view, String url, Bitmap favicon)
(5)在页面加载结束时调用。同样道理,我们知道一个页面载入完成,于是我们可以关闭loading 条,切换程序动作。
onPageFinished(WebView view, String url)
(6)报告错误信息
onReceivedError(WebView view, int errorCode, String description, String failingUrl)
(7)获取返回信息授权请求
onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,String realm)
(8)重写此方法可以让webview处理https请求。
onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)
(9)WebView发生改变时调用
onScaleChanged(WebView view, float oldScale, float newScale)
(10)Key事件未被加载时调用
onUnhandledKeyEvent(WebView view, KeyEvent event)
(11)重写此方法才能够处理在浏览器中的按键事件。
shouldOverrideKeyEvent(WebView view, KeyEvent event)
(12)在网页跳转时调用,这个函数我们可以做很多操作,比如我们读取到某些特殊的URL,于是就可以不打开地址,取消这个操作,进行预先定义的其他操作,这对一个程序是非常必要的。
shouldOverrideUrlLoading(WebView view, String url)
(13)在加载某个网页的资源的时候多次调用(已过时)
shouldInterceptRequest(WebView view, String url)
(14)在加载某个网页的资源的时候多次调用
shouldInterceptRequest(WebView view, WebResourceRequest request)
注意:
shouldOverrideUrlLoading在网页跳转的时候调用,且一般每跳转一次只调用一次。
shouldInterceptRequest只要是网页加载的过程中均会调用,资源加载的时候都会回调该方法,会多次调用。
四丶WebChoromeClient的回调方法列表
WebChromeClient主要用来辅助WebView处理Javascript的对话框、网站图标、网站标题以及网页加载进度等。通过WebView的setWebChromeClient()方法设置。
(1)监听网页加载进度
onProgressChanged(WebView view, int newProgress)
(2)监听网页标题 : 比如百度页面的标题是“百度一下,你就知道”
onReceivedTitle(WebView view, String title)
(3)监听网页图标
onReceivedIcon(WebView view, Bitmap icon)
Java和JavaScript互调
为方便展示,使用addJavascriptInterface方式实现与本地js交互(存在漏洞)。也可通过其他方式实现,比如拦截ur进行参数解析l等。
Java调JS
function javaCallJs(arg){ document.getElementById("content").innerHTML = ("欢迎:"+arg ); }
webView.loadUrl("javascript:javaCallJs("+"'"+name+"'"+")");
以上代码就是调用了JS中一个叫javaCallJs(arg)的方法,并传入了一个name参数。(具体效果下面有展示)
JS调java
webView.addJavascriptInterface(new JSInterface (),"Android");
class JSInterface { @JavascriptInterface public void showToast(String arg){ Toast.makeText(MainActivity.this,arg,Toast.LENGTH_SHORT).show(); } }
<input type="button" value="点击Android被调用" onclick="window.Android.showToast('JS中传来的参数')"/>
window.Android.showToast(‘JS中传来的参数’)”中的”Android”即addJavascriptInterface()中指定的,并且JS向java传递了参数,类型为String。而showToast(String arg)会以Toast的形式弹出此参数。
java与JS互调代码示例
先看效果图:
不好意思,传错了,是这张:
代码非常简单,并且加了注释,直接看代码就可以了。
<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <script type="text/javascript"> function javaCallJs(arg){ document.getElementById("content").innerHTML = ("欢迎:"+arg ); } </script> </head> <body> <div id="content"> 请在上方输入您的用户名</div> <input type="button" value="点击Android被调用" onclick="window.Android.showToast('JS中传来的参数')"/> </body> </html>
javaCallJs是java调用JS的方法,showToast方法是JS调用java的方法
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/ll_root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="20dp" android:background="#000088"> <EditText android:id="@+id/et_user" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="输入WebView中要显示的用户名" android:background="#008800" android:textSize="16sp" android:layout_weight="1"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="40dp" android:layout_marginRight="20dp" android:textSize="16sp" android:text="确定" android:onClick="click"/> </LinearLayout> </LinearLayout>
很简单,就是一个输入框和一个确定按钮,点击按钮会调用JS中的方法。
package com.wangjian.webviewdemo; import android.annotation.SuppressLint; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.webkit.JavascriptInterface; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private WebView webView; private LinearLayout ll_root; private EditText et_user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ll_root = (LinearLayout) findViewById(R.id.ll_root); et_user = (EditText) findViewById(R.id.et_user); initWebView(); } //初始化WebView private void initWebView() { //动态创建一个WebView对象并添加到LinearLayout中 webView = new WebView(getApplication()); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); webView.setLayoutParams(params); ll_root.addView(webView); //不跳转到其他浏览器 webView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); return true; } }); WebSettings settings = webView.getSettings(); //支持JS settings.setJavaScriptEnabled(true); //加载本地html文件 webView.loadUrl("file:///android_asset/JavaAndJavaScriptCall.html"); webView.addJavascriptInterface(new JSInterface(),"Android"); } //按钮的点击事件 public void click(View view){ //java调用JS方法 webView.loadUrl("javascript:javaCallJs(" + "'" + et_user.getText().toString()+"'"+")"); } //在页面销毁的时候将webView移除 @Override protected void onDestroy() { super.onDestroy(); ll_root.removeView(webView); webView.stopLoading(); webView.removeAllViews(); webView.destroy(); webView = null; } private class JSInterface { //JS需要调用的方法 @JavascriptInterface public void showToast(String arg){ Toast.makeText(MainActivity.this,arg,Toast.LENGTH_SHORT).show(); } } }
需要注意的地方
参考链接:安卓webview的一些坑
@Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); wv.saveState(outState); Log.e(TAG, "save state..."); }
恢复状态(在activity的onCreate(bundle savedInstanceState)里)
if(null!=savedInstanceState){ wv.restoreState(savedInstanceState); Log.i(TAG, "restore state"); }else{ wv.loadUrl("http://3g.cn"); }
其他一些常见问题:
1. WebViewClient.onPageFinished()。
你永远无法确定当WebView调用这个方法的时候,网页内容是否真的加载完毕了。当前正在加载的网页产生跳转的时候这个方法可能会被多次调用,StackOverflow上有比较具体的解释(How to listen for a Webview finishing loading a URL in Android?), 但其中列举的解决方法并不完美。所以当你的WebView需要加载各种各样的网页并且需要在页面加载完成时采取一些操作的话,可能WebChromeClient.onProgressChanged()比WebViewClient.onPageFinished()都要靠谱一些。
2. WebView后台耗电问题。
当你的程序调用了WebView加载网页,WebView会自己开启一些线程(?),如果你没有正确地将WebView销毁的话,这些残余的线程(?)会一直在后台运行,由此导致你的应用程序耗电量居高不下。对此我采用的处理方式比较偷懒,简单又粗暴(不建议),即在Activity.onDestroy()中直接调用System.exit(0),使得应用程序完全被移出虚拟机,这样就不会有任何问题了。
3. 切换WebView闪屏问题。
如果你需要在同一个ViewGroup中来回切换不同的WebView(包含了不同的网页内容)的话,你就会发现闪屏是不可避免的。这应该是Android硬件加速的Bug,如果关闭硬件加速这种情况会好很多,但无法获得很好的浏览体验,你会感觉网页滑动的时候一卡一卡的,不跟手。
4. 在某些手机上,Webview有视频时,activity销毁后,视频资源没有被销毁,甚至还能听到在后台播放。即便是像刚才那样各种销毁webview也无济于事,解决办法:在onDestory之前修改url为空地址。
5.WebView硬件加速导致页面渲染闪烁问题
关于Android硬件加速 开始于Android 3.0 (API level 11),开启硬件加速后,WebView渲染页面更加快速,拖动也更加顺滑。但有个副作用就是容易会出现页面加载白块同时界面闪烁现象。解决这个问题的方法是设置WebView暂时关闭硬件加速 代码如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
*请认真填写需求信息,我们会在24小时内与您取得联系。