2014年10月29日鳍贾,萬維網(wǎng)聯(lián)盟宣布,經(jīng)過接近8年的艱苦努力般贼,該標(biāo)準(zhǔn)(HTML5)規(guī)范終于制定完成愧哟。接著,整個(gè)移動(dòng)端開發(fā)領(lǐng)域開始了一場翻天覆地的改革變遷哼蛆,主要「受益」者蕊梧, ios , Android.
現(xiàn)如今的形勢無疑使這個(gè)行業(yè)的門檻抬的非常高,我們都知道以前會(huì)寫幾個(gè) demo 工作就沒問題了腮介,這是移動(dòng)開發(fā)正火熱的時(shí)期肥矢。之后接著有很多同學(xué)的自學(xué)效率不錯(cuò),憑著一己之力叠洗,培訓(xùn)也算甘改,每年大量新人涌入這個(gè)高薪行業(yè)!
前兩天吃飯的時(shí)候 HR 告訴我說灭抑,他說兩年前招 Android 的時(shí)候給出的價(jià)格是 15k十艾,而且還招不到,如今 10 都不愿意給了腾节,發(fā)一個(gè)職位來的人排著隊(duì)面試 忘嫉!可見移動(dòng)行業(yè)的現(xiàn)狀的確是跟之前的差距拉的太大。
包括我剛出來的時(shí)候基本沒有接觸過 WebApp 領(lǐng)域的東西案腺,更不會(huì)去嫻熟的利用 WebView 做著各種交互庆冕,以至于找工作就費(fèi)了很大的力氣,如今劈榨,Hybrid App 已然是絕對的主流地位访递!
什么是 Hybrid App
Native App(原生界面)+ WebApp (基于 H5 的 WebView 網(wǎng)頁)== Hybrid App(混合型)
那這樣一個(gè)公式就說的很清楚了,所謂的 Hybrid 就是原生與網(wǎng)頁混合開發(fā)同辣,有時(shí)候原生界面調(diào) H5 頁面拷姿,有時(shí) H5 網(wǎng)頁又來調(diào)原生惭载,為什么會(huì)衍生出這種開發(fā)模式呢?H5 可跨平臺(tái)跌前,一套頁面 ios 和 android 公用棕兼,開發(fā)成本較低陡舅,再者網(wǎng)頁不占內(nèi)存等等抵乓,一張圖來稍微概括:
![](https://github.com/smartbeng/smartbeng.github.io/raw/master/img/xxx.png)
關(guān)于三者之間的對比這只是片面的,更多詳細(xì)的資料大家自行搜索查看靶衍!雖然前兩者的體驗(yàn)與性能都不會(huì)優(yōu)于后者灾炭,但是大勢所趨,我們誰也改變不了颅眶!
- 使用 WebView 加載網(wǎng)頁
這里沒什么好說的蜈出,直接上代碼:
<!-- 頁面布局 -->
<WebView
android:id="@+id/web"
android:layout_width="match_parent
android:layout_height="match_parent" />
***第一部分 :
//加載顯示網(wǎng)頁
mWebView = (WebView) mMainContainer.findViewById(R.id.wv_container);
?// 設(shè)置加載網(wǎng)頁的地址
mWebView.loadUrl("http://www.baidu.com");?WebSettings settings = mWebView.getSettings();?
// 啟用 JavaScript 支持(WebView的設(shè)置)
settings.setJavaScriptEnabled(true);?
new WebViewClient解決跳轉(zhuǎn)到第三方瀏覽器*
這里面處理行對應(yīng)的頁面邏輯*/mWebView.setWebViewClient(new WebViewClient() {?
//處理頁面邏輯的方法 詳細(xì)舉例在下一片段
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
mWebView.loadUrl(url);
return true;
}?
// 頁面開始加載
@Override public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
if (!mDialog.isShowing())
mDialog.show(); //顯示加載進(jìn)度條
}
/**
* 頁面加載完成
* @param view
* @param url
*/ @Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (mDialog.isShowing())
mDialog.dismiss(); //加載完成 隱藏進(jìn)度條
}
});
我們首先利用 WebView 加載了這個(gè)網(wǎng)頁,其次給他設(shè)置涛酗,解決瀏覽器跳轉(zhuǎn)铡原,顯示加載進(jìn)度條等等!
相信大家都很明白這還只是入門 Demo 的寫法商叹,那么在項(xiàng)目中我們是怎么處理這些設(shè)置和邏輯呢燕刻?
第一 :實(shí)際項(xiàng)目中的 WebView 存在與多個(gè)地方
第二 :原生與 Web 混合型
第三 : 網(wǎng)頁大多數(shù)有他后臺(tái)自己加好的邏輯與功能(包括 js 方法),怎么配合剖笙!
第四:你必須要做的 Cookie 處理
那么我們將以這幾個(gè)問題開始進(jìn)行分享卵洗,所以接下來會(huì)開是一個(gè) HyBrid 主題系列的文章,估計(jì)會(huì)有好幾篇弥咪!
- 建立 Activity 管理類
這一步是項(xiàng)目開發(fā)必備过蹂,我們面對這越來越多的頁面的時(shí)候,如果不管理聚至,你總有無從下手的時(shí)候酷勺!
/**
* Activity 管理類
* Created by ShiYunpeng on 2016/12/28.
*/
public class ActivityCollector {?
private static List<Activity> activityList = new ArrayList<>();
//用來管理所有的activity?
/**
* 添加當(dāng)前的Activity到棧中
* @param activity
*/
public static void addActivity(Activity activity){
activityList.add(activity);
}?
/**
* 將當(dāng)前Activity移出返回棧
* @param activity
*/
public static void removeActivity(Activity activity){
activityList.remove(activity);
}?
/**
* 獲取到棧頂?shù)腁ctivity
* @return
*/
public static Activity getTopActivity(){
if (activityList.isEmpty()){
return null;
}else {
return activityList.get(activityList.size() - 1);
}
}
}
那么這三個(gè)方法解決了我們很多問題,包括由于資源未及時(shí)釋放以及讓你措手不及的 ANR扳躬,可別小看他脆诉!
以上寫法的應(yīng)用:在 Activity 中我們需要在項(xiàng)目初始化的時(shí),用我們建好的這個(gè)類 ActivityClooector.addActivity() 來添加我們的 Activity 到棧中坦报,同樣的方法库说,在 Activity 將要銷毀的時(shí)候去選擇 remove 他,后面我們會(huì)在一些重要的類中(非 Activity)去用 getToopActivity() 來獲取到我們的棧頂 Activity片择,非常重要潜的!
- 建立 WebViewController 管理類
那么這一步更加重要了,還記得我們第一小節(jié)是怎么設(shè)置 WebView 的嗎字管?我們是在加載他的 Activity 中來進(jìn)行設(shè)置的啰挪,那么當(dāng)有多個(gè)頁面時(shí)信不,又當(dāng)每個(gè)頁面加載 WebView 所要的設(shè)置數(shù)量不同的時(shí)候,就比較難處理了亡呵!
所以這里只用一個(gè)類來將項(xiàng)目中所有關(guān)于 WebView 的設(shè)置抽活,以及 WebView 單層面的處理都放在這里!
先行列出 WebView 中常用設(shè)置以及方法
WebSettings
//下面三個(gè)最常用锰什,基本都需要設(shè)置
setCacheMode 設(shè)置緩存的模式 eg: settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
setJavaSciptEnabled 設(shè)置是否支持Javascript eg: settings.setJavaScriptEnabled(true);
setDefaultTextEncodingName 設(shè)置在解碼時(shí)使用的默認(rèn)編碼 eg: settings.setDefaultTextEncodingName(“utf-8”);
setAllowFileAccess 啟用或禁止WebView訪問文件數(shù)據(jù) setBlockNetworkImage 是否顯示網(wǎng)絡(luò)圖像 setBuiltInZoomControls 設(shè)置是否支持縮放
setDefaultFontSize 設(shè)置默認(rèn)的字體大小
setFixedFontFamily 設(shè)置固定使用的字體
setLayoutAlgorithm 設(shè)置布局方式
setLightTouchEnabled 設(shè)置用鼠標(biāo)激活被選項(xiàng)
setSupportZoom 設(shè)置是否支持變焦
WebViewClient
mWebView.setWebViewClient(new WebViewClient(){
這里包含以下方法
});
方法:
onPageStarted 網(wǎng)頁開始加載
onReceivedError 報(bào)告錯(cuò)誤信息
onLoadResource 加載指定地址提供的資源
shouldOverrideUrlLoading 控制新的連接在當(dāng)前WebView中打開
onPageFinished 網(wǎng)頁加載完畢下硕,此方法并沒有方法名表現(xiàn)的那么美好,調(diào)用時(shí)機(jī)很不確定汁胆。如需監(jiān)聽網(wǎng)頁加載完成可以使用onProgressChanged梭姓,當(dāng)int progress返回100時(shí)表示網(wǎng)頁加載完畢。
doUpdate VisitedHistory 更新歷史記錄
onFormResubmission 應(yīng)用程序重新請求網(wǎng)頁數(shù)據(jù)
onScaleChanged WebView發(fā)生改變
**WebChromeClient** mWebView.setWebViewClient(new WebChromeClient(){
? 同樣例舉出常用方法
});
方法:
onProgressChanged 加載進(jìn)度條改變
onJsPrompt 用在解決4.2以下addJavascriptInterface漏洞問題
onCloseWindow 關(guān)閉WebView
onCreateWindow 創(chuàng)建WebView
onJsAlert 處理Javascript中的Alert對話框
onJsConfirm處理Javascript中的Confirm對話框
onJsPrompt處理Javascript中的Prompt對話框
onReceivedlcon 網(wǎng)頁圖標(biāo)更改
onReceivedTitle 網(wǎng)頁Title更改
onRequestFocus WebView顯示焦點(diǎn)
onConsoleMessage 在Logcat中輸出javascript的日志信息
下來列舉我對于此模塊的處理方法
我在這里建立了一個(gè) WebViewCtroller類嫩码,用于將所有能用到設(shè)置以及需要添加的功能都寫在里面誉尖,據(jù)我所知,只是現(xiàn)在普遍流行的一種寫法铸题,我們開始看铡恕,注釋很詳細(xì)!
//項(xiàng)目所在包路徑丢间,待會(huì)要用
package com.lansum.eip.webview;
public class WebViewController extends WebView {
//聲明上下文對象
private Context context;
//聲明WebViewController全局對象
private WebViewController control;
//網(wǎng)絡(luò)未加載完的loading圖片
private ImageView imageView;
//截取到的網(wǎng)址上的消息頭字符串 表示要進(jìn)入到那個(gè)頁面
private String sbFunName = "";
public WebViewController(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
webviewSettings();
}
public WebViewController(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
webviewSettings();
}
public WebViewController(Context context) {
super(context);
webviewSettings();
}
@SuppressLint("JavascriptInterface")
private void webviewSettings() {
control = this;
// TODO Auto-generated constructor stub
WebSettings webSettings = this.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setSupportZoom(false);
webSettings.setBuiltInZoomControls(false);
webSettings.setAllowFileAccess(true);
webSettings.setAllowContentAccess(true);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setDatabaseEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setGeolocationEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setAppCachePath(context.getCacheDir().getPath());
webSettings.setDefaultTextEncodingName("gbk");
// 屏幕自適應(yīng)
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webSettings.setDisplayZoomControls(false);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webSettings.setLoadsImagesAutomatically(true);
} else {
webSettings.setLoadsImagesAutomatically(false);
}
/**禁止屏幕自動(dòng)旋轉(zhuǎn)*/
this.setScrollBarStyle(this.SCROLLBARS_INSIDE_OVERLAY);
this.setHorizontalScrollBarEnabled(false);
this.setHorizontalFadingEdgeEnabled(false);
this.setVerticalFadingEdgeEnabled(false);
HtmlMessageForLocal newWebViewActivity = new HtmlMessageForLocal();
this.addJavascriptInterface(newWebViewActivity, "android");
/**
* 讓網(wǎng)頁的彈框轉(zhuǎn)化為原生化的彈框
*/
setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder builder = new AlertDialog.Builder(context).setTitle("錯(cuò)誤提醒")
.setMessage(message)
.setIcon(R.drawable.icon_login)
.setPositiveButton("好", new AlertDialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
builder.setCancelable(false);
builder.create();
builder.show();
return true;
}
});
setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 這些頁面添加頂部導(dǎo)航
if (url != null && !url.matches(".*"+ Constants.urlLogIn+".*")) {
Intent intent = new Intent(context, NewWebViewActivity.class);
intent.putExtra("url", url);
intent.putExtra("animation",R.anim.slide_right_out);
context.startActivity(intent);
//((Activity)context).overridePendingTransition(R.anim.slide_right_in, R.anim.none);
return true;
} else {
return false;
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
}
/**
* WebView開始加載
* 1.通過截取網(wǎng)頁 url 中指定的參數(shù)確定這個(gè)頁面
* 2.根據(jù)匹配結(jié)果而確定所要操作的是哪個(gè)頁面
* 3.注冊廣播接收器探熔,接受消息處理類中發(fā)過來的廣播去刷新數(shù)據(jù)
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
control.setTag("");
int hhh = url.indexOf("WebViewRefreshNotification=");
if (hhh != -1){
int xxx = hhh + 27;
sbFunName = url.substring(xxx);
int ttt = sbFunName.indexOf('&');
if (ttt!= -1){
sbFunName = sbFunName.substring(0,ttt);
}
}
/**
* 當(dāng)最終截取到的字符串不是空的時(shí)候
* 再去注冊廣播來接受發(fā)過來的廣播消息
*/
if (!sbFunName.equals("")){
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(sbFunName);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(sbFunName)) { //接受廣播
String name = intent.getStringExtra("name");
WebViewController.this.loadUrl("javascript:" + name);
}
}
}, intentFilter);
}
/**
* 動(dòng)態(tài)創(chuàng)建網(wǎng)絡(luò)加載中的GIf圖片
*/
imageView = new ImageView(ActivityCollector.getTopActivity().getApplicationContext());
ViewGroup.LayoutParams size = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 80);
imageView.setLayoutParams(size);
Glide.with(ActivityCollector.getTopActivity()).load(R.drawable.loading3).into(imageView);
ViewGroup parentLayout = (ViewGroup) WebViewController.this.getParent();
parentLayout.addView(imageView);
parentLayout.getPaddingRight();
imageView.setVisibility(View.VISIBLE);
}
/**
* WebView加載完成
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
imageView.setVisibility(View.GONE);
}
});
}
其實(shí)說起來代碼也不多,根據(jù)需求來添加即可千劈。注意祭刚,這個(gè)類世紀(jì)城于 WebView 的!
關(guān)于如何知道網(wǎng)頁服務(wù)端給我們傳過來的是什么參數(shù)墙牌,以便于我們將參數(shù)填到所需的地方涡驮,這里就以這個(gè)在 WebView 中彈出原生的提示框?yàn)槔?br> 如果不處理網(wǎng)頁的 js 彈框,他是這樣的:
那么這樣用戶體驗(yàn)就不好了喜滨!凡是網(wǎng)頁時(shí)有 js 彈框的地方捉捅,我們都可以用在 setWebChromeClient(new WebChromeClient() {} 中 重寫父類的 onJsAlert() 來講之轉(zhuǎn)換為原生彈框。
接下來我們打一個(gè)斷點(diǎn)來追蹤一下網(wǎng)頁需要什么參數(shù)讓我們傳虽风,關(guān)于網(wǎng)頁服務(wù)端的所有接口都是這樣來看參數(shù)的棒口,所以你看好了!
可以看到辜膝,我在沒有填密碼的時(shí)候點(diǎn)擊了修改密碼无牵,它就接著執(zhí)行了這個(gè)方法,并且有一個(gè) message 參數(shù)顯示的是提示信息厂抖,那么我們直接就將這個(gè) message 設(shè)置到我們原生彈框中所需要的消息就 OK 了茎毁!記住,后面的接口方法都是這么判斷,再說一遍七蜘!
重點(diǎn)來了谭溉,將設(shè)置應(yīng)用到 WebView 中去。
<com.lansum.eip.webview.WebViewController
android:id="@+id/main_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
還記得上面類第一行的包吧橡卤!沒錯(cuò)扮念,就是把這個(gè)類的包路徑直接添加到布局中,像添加自定義布局的方式一樣碧库,現(xiàn)在起我們的 WebView 就已經(jīng)具備了代碼中所有的設(shè)置柜与!
關(guān)于這個(gè)類(WebViewController),是我們專門用于處理 WebView 的類谈为,如何去搭配下一個(gè)重頭戲的類(HtmlMessageForLocal 類)旅挤!作用踢关,本地與WebView交互類伞鲫,根據(jù)點(diǎn)擊的WebView位置服務(wù)端發(fā)送相應(yīng)接口,本地去實(shí)現(xiàn)這些接口签舞!非常重要秕脓!
此次就到這里,我們下篇文章繼續(xù)儒搭,揭開這個(gè) HtmlMessageForLocal 類的真實(shí)面目吠架,并與我們的 WebViewController 類搭配起來完成所有網(wǎng)頁的處理,此次分享落幕搂鲫!