今年國慶假期終于可以憋在家里了不用出門了,不用出去看后腦了,真的是一種享受。這么好的光陰怎么浪費,睡覺、吃飯、打豆豆這怎么可能(耍多了也煩),完全不符合我們程序員的作風,趕緊起來把文章寫完。
這篇文章比較基礎,在國慶期間的業余時間寫的,這幾天又完善了下,力求把更多的前端所涉及到的關于文件上傳的各種場景和應用都涵蓋了,若有疏漏和問題還請留言斧正和補充。
以下是本文所涉及到的知識點,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小時內與您取得聯系。