Home

smartbeng

做好今天的自己!

Blog About Email Github

2017-04-28
当下主流 APP , Hybrid app 中需要熟练掌握的交互知识(一)

2014年10月29日,万维网联盟宣布,经过接近8年的艰苦努力,该标准(HTML5)规范终于制定完成。接着,整个移动端开发领域开始了一场翻天覆地的改革变迁,主要「受益」者, ios , Android.

现如今的形势无疑使这个行业的门槛抬的非常高,我们都知道以前会写几个 demo 工作就没问题了,这是移动开发正火热的时期。之后接着有很多同学的自学效率不错,凭着一己之力,培训也算,每年大量新人涌入这个高薪行业!

前两天吃饭的时候 HR 告诉我说,他说两年前招 Android 的时候给出的价格是 15k,而且还招不到,如今 10 都不愿意给了,发一个职位来的人排着队面试 !可见移动行业的现状的确是跟之前的差距拉的太大。

包括我刚出来的时候基本没有接触过 WebApp 领域的东西,更不会去娴熟的利用 WebView 做着各种交互,以至于找工作就费了很大的力气,如今,Hybrid App 已然是绝对的主流地位!

什么是 Hybrid App

Native App(原生界面)+ WebApp (基于 H5 的 WebView 网页)== Hybrid App(混合型)

那这样一个公式就说的很清楚了,所谓的 Hybrid 就是原生与网页混合开发,有时候原生界面调 H5 页面,有时 H5 网页又来调原生,为什么会衍生出这种开发模式呢?H5 可跨平台,一套页面 ios 和 android 公用,开发成本较低,再者网页不占内存等等,一张图来稍微概括:

ggg

关于三者之间的对比这只是片面的,更多详细的资料大家自行搜索查看!虽然前两者的体验与性能都不会优于后者,但是大势所趋,我们谁也改变不了!

1. 使用 WebView 加载网页

这里没什么好说的,直接上代码:

<!-- 页面布局 -->
<WebView
android:id="@+id/web"
android:layout_width="match_parent
android:layout_height="match_parent" />
/**
*第一部分 :记载显示网页
*/
mWebView = (WebView) mMainContainer.findViewById(R.id.wv_container);
// 设置加载网页的地址
mWebView.loadUrl("http://www.baidu.com");
WebSettings settings = mWebView.getSettings();
// 启用 JavaScript 支持(WebView的设置)
settings.setJavaScriptEnabled(true);
/**
* new WebViewClient
* 解决跳转到第三方浏览器
* 这里面处理行对应的页面逻辑
*/
mWebView.setWebViewClient(new WebViewClient() {
//处理页面逻辑的方法 详细举例在下一片段
@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(); //显示加载进度条
}
/**
* 页面加载完成
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (mDialog.isShowing())
mDialog.dismiss(); //加载完成 隐藏进度条
}
});

我们首先利用 WebView 加载了这个网页,其次给他设置,解决浏览器跳转,显示加载进度条等等!

相信大家都很明白这还只是入门 Demo 的写法,那么在项目中我们是怎么处理这些设置和逻辑呢?

第一 :实际项目中的 WebView 存在与多个地方

第二 :原生与 Web 混合型

第三 : 网页大多数有他后台自己加好的逻辑与功能(包括 js 方法),怎么配合!

第四:你必须要做的 Cookie 处理

那么我们将以这几个问题开始进行分享,所以接下来会开是一个 HyBrid 主题系列的文章,估计会有好几篇!

2. 建立 Activity 管理类

这一步是项目开发必备,我们面对这越来越多的页面的时候,如果不管理,你总有无从下手的时候!

/**
* Activity 管理类
* Created by ShiYunpeng on 2016/12/28.
*/
public class ActivityCollector {
private static List<Activity> activityList = new ArrayList<>(); //用来管理所有的activity
/**
* 添加当前的Activity到栈中
* @param activity
*/
public static void addActivity(Activity activity){
activityList.add(activity);
}
/**
* 将当前Activity移出返回栈
* @param activity
*/
public static void removeActivity(Activity activity){
activityList.remove(activity);
}
/**
* 获取到栈顶的Activity
* @return
*/
public static Activity getTopActivity(){
if (activityList.isEmpty()){
return null;
}else {
return activityList.get(activityList.size() - 1);
}
}
}

那么这三个方法解决了我们很多问题,包括由于资源未及时释放以及让你措手不及的 ANR,可别小看他!

以上写法的应用:在 Activity 中我们需要在项目初始化的时,用我们建好的这个类 ActivityClooector.addActivity() 来添加我们的 Activity 到栈中,同样的方法,在 Activity 将要销毁的时候去选择 remove 他,后面我们会在一些重要的类中(非 Activity)去用 getToopActivity() 来获取到我们的栈顶 Activity,非常重要!

3. 建立 WebViewController 管理类

那么这一步更加重要了,还记得我们第一小节是怎么设置 WebView 的吗?我们是在加载他的 Activity 中来进行设置的,那么当有多个页面时,又当每个页面加载 WebView 所要的设置数量不同的时候,就比较难处理了!

所以这里只用一个类来将项目中所有关于 WebView 的设置,以及 WebView 单层面的处理都放在这里!

先行列出 WebView 中常用设置以及方法

  1. WebSettings
//下面三个最常用,基本都需要设置
setCacheMode 设置缓存的模式 eg: settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
setJavaSciptEnabled 设置是否支持Javascript eg: settings.setJavaScriptEnabled(true);
setDefaultTextEncodingName 设置在解码时使用的默认编码 eg: settings.setDefaultTextEncodingName(“utf-8”);
setAllowFileAccess 启用或禁止WebView访问文件数据
setBlockNetworkImage 是否显示网络图像
setBuiltInZoomControls 设置是否支持缩放
setDefaultFontSize 设置默认的字体大小
setFixedFontFamily 设置固定使用的字体
setLayoutAlgorithm 设置布局方式
setLightTouchEnabled 设置用鼠标激活被选项
setSupportZoom 设置是否支持变焦
  1. WebViewClient

设置WebViewClient:mWebView.setWebViewClient(new WebViewClient(){

这里包含以下方法

});

方法:

onPageStarted 网页开始加载
onReceivedError 报告错误信息
onLoadResource 加载指定地址提供的资源
shouldOverrideUrlLoading 控制新的连接在当前WebView中打开
onPageFinished 网页加载完毕,此方法并没有方法名表现的那么美好,调用时机很不确定。如需监听网页加载完成可以使用onProgressChanged,当int progress返回100时表示网页加载完毕。
doUpdate VisitedHistory 更新历史记录
onFormResubmission 应用程序重新请求网页数据
onScaleChanged WebView发生改变
  1. WebChromeClient mWebView.setWebViewClient(new WebChromeClient(){

    ​ 同样例举出常用方法

    });

方法:

onProgressChanged 加载进度条改变
onJsPrompt 用在解决4.2以下addJavascriptInterface漏洞问题
onCloseWindow 关闭WebView
onCreateWindow 创建WebView
onJsAlert 处理Javascript中的Alert对话框
onJsConfirm处理Javascript中的Confirm对话框
onJsPrompt处理Javascript中的Prompt对话框
onReceivedlcon 网页图标更改
onReceivedTitle 网页Title更改
onRequestFocus WebView显示焦点
onConsoleMessage 在Logcat中输出javascript的日志信息

下来列举我对于此模块的处理方法

我在这里建立了一个 WebViewCtroller类,用于将所有能用到设置以及需要添加的功能都写在里面,据我所知,只是现在普遍流行的一种写法,我们开始看,注释很详细!

//项目所在包,待会要用
package com.lansum.eip.webview;
public class WebViewController extends WebView {
//声明上下文对象
private Context context;
//声明WebViewController全局对象
private WebViewController control;
//网络未加载完的loading图片
private ImageView imageView;
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");
// 屏幕自适应
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);
}
/**禁止屏幕自动旋转*/
this.setScrollBarStyle(this.SCROLLBARS_INSIDE_OVERLAY);
this.setHorizontalScrollBarEnabled(false);
this.setHorizontalFadingEdgeEnabled(false);
this.setVerticalFadingEdgeEnabled(false);
/*下面的类是一个专门处理网页与本地交互的类
*这里的意思是先创建这个类的对象
*然后经本地所有服务端的接口封装在了这个类对象中
*这样在这个类中处理交互的时候就很方便了
*这个在后续中详细解释
*/
HtmlMessageForLocal newWebViewActivity = new HtmlMessageForLocal();
this.addJavascriptInterface(newWebViewActivity, "android");
/**
* 让网页的弹框转化为原生化的弹框
* onJsAlert 这个方法名是死的
*/
setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder builder = new AlertDialog.Builder(context).setTitle("错误提醒")
/*注意,这里的 message 是服务端网页传过来的参数
*只需要经这个参数设置到弹出框中即可
*这里不同的服务方会有不同需求
*/
.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) {
// 这些页面添加顶部导航
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);
return true;
} else {
return false;
}
}
/**
*网络错误时的回调方法
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
}
/**
* WebView开始加载
* @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);
/**
* 动态创建网络加载中的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) {
//让加载的GIf动图消失
imageView.setVisibility(View.GONE);
}
});
}

其实说起来代码也不多,根据需求来添加即可。注意,这个类世纪城于 WebView 的!

关于如何知道网页服务端给我们传过来的是什么参数,以便于我们将参数填到所需的地方,这里就以这个在 WebView 中弹出原生的提示框为例!

如果不处理网页的 js 弹框,他是这样的:

DiaLog.png

那么这样用户体验就不好了!凡是网页时有 js 弹框的地方,我们都可以用在 setWebChromeClient(new WebChromeClient() {} 中 重写父类的 onJsAlert() 来讲之转换为原生弹框。

接下来我们打一个断点来追踪一下网页需要什么参数让我们传,关于网页服务端的所有接口都是这样来看参数的,所以你看好了!

funName

可以看到,我在没有填密码的时候点击了修改密码,它就接着执行了这个方法,并且有一个 message 参数显示的是提示信息,那么我们直接就将这个 message 设置到我们原生弹框中所需要的消息就 OK 了!记住,后面的接口方法都是这么判断,再说一遍!

重点来了,将设置应用到 WebView 中去。

<com.lansum.eip.webview.WebViewController
android:id="@+id/main_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

还记得上面类第一行的包吧!没错,就是把这个类的包路径直接加载布局中,像添加自定义布局的方式一样,现在起我们的 WebView 就已经具备了代码中所有的设置!

关于这个类(WebViewController),也我们专门用于处理 WebView 的看,如何去搭配下一个重头戏的类(HtmlMessageForLocal 类)!作用,本地与WebView交互类,根据点击的WebView位置服务端发送相应接口,本地去实现这些接口!非常重要!

此次就到这里,我们下篇文章继续,揭开这个 HtmlMessageForLocal 类的真实面目,并与我们的 WebViewController 类搭配起来完成所有网页的处理,此次分享落幕!

微信公众号「smartbeng」,吐血版珍藏持续更新,一个会爱上的公众号。

webwxgetmsgimg.jpg


smartbeng

scribble

Blog About Email Github