#### 基础使用目录介绍
- 21.loadUrl(url)流程分析
- 22.js的调用时机分析
- 23.如何使用DeepLink
- 24.应用被作为三方浏览器打开
- 25.理解WebView独立进程
- 26.使用外部浏览器下载
- 27.tel,sms等协议用法
- 29.关于拦截处理注意要点
- 30.FileChooser文件处理
### 11.WebView.loadUrl(url)流程
- WebView.loadUrl(url)加载网页做了什么?
- 加载网页是一个复杂的过程,在这个过程中,我们可能需要执行一些操作,包括:
- 加载网页前,重置WebView状态以及与业务绑定的变量状态。WebView状态包括重定向状态(mTouchByUser)、前端控制的回退栈(mBackStep)等,业务状态包括进度条、当前页的分享内容、分享按钮的显示隐藏等。
- 加载网页前,根据不同的域拼接本地客户端的参数,包括基本的机型信息、版本信息、登录信息以及埋点使用的Refer信息等,有时候涉及交易、财产等还需要做额外的配置。
- 开始执行页面加载操作时,会回调WebViewClient.onPageStarted(webView,url,favicon)。在此方法中,可以重置重定向保护的变量(mRedirectProtected),当然也可以在页面加载前重置,由于历史遗留代码问题,此处尚未省去优化。
- 加载页面的过程中回调哪些方法?
- WebChromeClient.onReceivedTitle(webview, title),用来设置标题。需要注意的是,在部分Android系统版本中可能会回调多次这个方法,而且有时候回调的title是一个url,客户端可以针对这种情况进行特殊处理,避免在标题栏显示不必要的链接。
- WebChromeClient.onProgressChanged(webview, progress),根据这个回调,可以控制进度条的进度(包括显示与隐藏)。一般情况下,想要达到100%的进度需要的时间较长(特别是首次加载),用户长时间等待进度条不消失必定会感到焦虑,影响体验。其实当progress达到80的时候,加载出来的页面已经基本可用了。事实上,国内厂商大部分都会提前隐藏进度条,让用户以为网页加载很快。
- WebViewClient.shouldInterceptRequest(webview, request),无论是普通的页面请求(使用GET/POST),还是页面中的异步请求,或者页面中的资源请求,都会回调这个方法,给开发一次拦截请求的机会。在这个方法中,我们可以进行静态资源的拦截并使用缓存数据代替,也可以拦截页面,使用自己的网络框架来请求数据。包括后面介绍的WebView免流方案,也和此方法有关。
- WebViewClient.shouldOverrideUrlLoading(webview, request),如果遇到了重定向,或者点击了页面中的a标签实现页面跳转,那么会回调这个方法。可以说这个是WebView里面最重要的回调之一,后面WebView与Native页面交互一节将会详细介绍这个方法。
- WebViewClient.onReceivedError(webview,handler,error),加载页面的过程中发生了错误,会回调这个方法。主要是http错误以及ssl错误。在这两个回调中,我们可以进行异常上报,监控异常页面、过期页面,及时反馈给运营或前端修改。在处理ssl错误时,遇到不信任的证书可以进行特殊处理,例如对域名进行判断,针对自己公司的域名“放行”,防止进入丑陋的错误证书页面。也可以与Chrome一样,弹出ssl证书疑问弹窗,给用户选择的余地。
- 加载页面结束回调哪些方法
- 会回调WebViewClient.onPageFinished(webview,url)。
- 这时候可以根据回退栈的情况判断是否显示关闭WebView按钮。通过mActivityWeb.canGoBackOrForward(-1)判断是否可以回退。
### 12.js的调用时机分析
- **onPageFinished()或者onPageStarted()方法中注入js代码**
- 做过WebView开发,并且需要和js交互,大部分都会认为js在WebViewClient.onPageFinished()方法中注入最合适,此时dom树已经构建完成,页面已经完全展现出来。但如果做过页面加载速度的测试,会发现WebViewClient.onPageFinished()方法通常需要等待很久才会回调(首次加载通常超过3s),这是因为WebView需要加载完一个网页里主文档和所有的资源才会回调这个方法。
- 能不能在WebViewClient.onPageStarted()中注入呢?答案是不确定。经过测试,有些机型可以,有些机型不行。在WebViewClient.onPageStarted()中注入还有一个致命的问题——这个方法可能会回调多次,会造成js代码的多次注入。
- 从7.0开始,WebView加载js方式发生了一些小改变,**官方建议把js注入的时机放在页面开始加载之后**。
- **WebViewClient.onProgressChanged()方法中注入js代码**
- WebViewClient.onProgressChanged()这个方法在dom树渲染的过程中会回调多次,每次都会告诉我们当前加载的进度。
- 在这个方法中,可以给WebView自定义进度条,类似微信加载网页时的那种进度条
- 如果在此方法中注入js代码,则需要避免重复注入,需要增强逻辑。可以定义一个boolean值变量控制注入时机
- 那么有人会问,加载到多少才需要处理js注入逻辑呢?
- 正是因为这个原因,页面的进度加载到80%的时候,实际上dom树已经渲染得差不多了,表明WebView已经解析了标签,这时候注入一定是成功的。在WebViewClient.onProgressChanged()实现js注入有几个需要注意的地方:
- 1 上文提到的多次注入控制,使用了boolean值变量控制
- 2 重新加载一个URL之前,需要重置boolean值变量,让重新加载后的页面再次注入js
- 3 如果做过本地js,css等缓存,则先判断本地是否存在,若存在则加载本地,否则加载网络js
- 4 注入的进度阈值可以自由定制,理论上10%-100%都是合理的,不过建议使用了75%到90%之间可以。
### 13.如何使用DeepLink
- 假设一个场景:
- 小明告诉小新,一鹿有车APP上有一个很有创意的抽奖活动,小新想要参与这个活动:
- 如果小新已经安装了APP,他需要找到且打开APP,然后找到相应的活动,共计2步;
- 如果小新没有安装APP,他需要在应用市场搜索一鹿有车APP、下载、打开APP且找到相应的活动,共计4步;
- 什么是DeepLink
- Deep Link,又叫deep linking,中文翻译作深层链接。简单地从用户体验来讲,Deep Link,就是可以让你在手机的浏览器/Google Search上点击搜索的结果,便能直接跳转到已安装的应用中的某一个页面的技术。
- 什么是Deferred DeepLink
- 相比DeepLink,它增加了判断APP是否被安装,用户匹配的2个功能;
- 1.当用户点击链接的时候判断APP是否安装,如果用户没有安装时,引导用户跳转到应用商店下载应用。
- 2.用户匹配功能,当用户点击链接时和用户启动APP时,分别将这两次用户Device Fingerprint(设备指纹信息)传到服务器进行模糊匹配,使用户下载且启动APP时,直接打开相应的指定页面。
- 什么是AppLink
- AppLink相对复杂,需要App与Web协作完成系统验证,但可以保证直接唤起目标App,无需用户二次选择或确认
- DeepLink和AppLink用到的核心技术
- URL SCHEMES。不论是IOS还是Android。
- 比如微信:URL Schemes:weixin://dl/moments(打开微信朋友圈)
- DeepLink与AppLink,本质上都是基于Intent框架,使App能够识别并处理来自系统或其他App的某种特殊URL,在原生App之间相互跳转,实现良好的用户体验
- 如何实现DeepLink实践该方案
- 1.指定scheme跳转规则,比如暂时是这样设定的:yilu://link/?page=main
- 2.被唤起方,客户端需要配置清单文件activity。关于SchemeActivity注意查看下面代码:
```
//解析数据
@Override
public void onCreate(Bundle savesInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent=getIntent();
String action=intent.getAction();
Uri data=intent.getData();
//解析data
String scheme=data.getScheme();
String host=data.getHost();
String path=data.getPath();
int port=data.getPort();
Set paramKeySet=data.getQueryParameterNames();
}
```
- 3.唤起方也需要操作
```
Intent intent=new Intent();
intent.setData(Uri.parse("yilu://link/?page=main"));
startActivity(intent);
```
- 如何避免通过deep link打开多个应用实例
- http://stackoverflow.com/a/25997627
- 具体可以看这篇文章:https://www.jianshu.com/p/127c80f62655
### 14.应用被作为第三方浏览器打开
- 微信里的文章页面,可以选择“在浏览器打开”。现在很多应用都内嵌了WebView,那是否可以使自己的应用作为第三方浏览器打开此文章呢?
- 在Manifest文件中,给想要接收跳转的Activity添加配置:
```
```
- 然后在 X5WebViewActivity 中获取相关传递数据。具体可以看lib中的X5WebViewActivity类代码。
```
public class X5WebViewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_view);
getIntentData();
initTitle();
initWebView();
webView.loadUrl(mUrl);
// 处理 作为三方浏览器打开传过来的值
getDataFromBrowser(getIntent());
}
/**
* 使用singleTask启动模式的Activity在系统中只会存在一个实例。
* 如果这个实例已经存在,intent就会通过onNewIntent传递到这个Activity。
* 否则新的Activity实例被创建。
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
getDataFromBrowser(intent);
}
/**
* 作为三方浏览器打开传过来的值
* Scheme: https
* host: www.jianshu.com
* path: /p/yc
* url = scheme + "://" + host + path;
*/
private void getDataFromBrowser(Intent intent) {
Uri data = intent.getData();
if (data != null) {
try {
String scheme = data.getScheme();
String host = data.getHost();
String path = data.getPath();
String text = "Scheme: " + scheme + "\n" + "host: " + host + "\n" + "path: " + path;
Log.e("data", text);
String url = scheme + "://" + host + path;
webView.loadUrl(url);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
```
- 一些重点说明
- 在微信中“通过浏览器”打开自己的应用,然后将自己的应用切到后台。重复上面的操作,会一直创建应用的实例,这样肯定是不好的,为了避免这种情况我们设置启动模式为:launchMode="singleTask"。
### 15.理解WebView独立进程
- WebView实例在Android7.0系统以后,已经可以选择运行在一个独立进程上7;8.0以后默认就是运行在独立的沙盒进程中。
- Android7.0系统以后,WebView相对来说是比较稳定的,无论承载WebView的容器是否在主进程,都不需要担心WebView崩溃导致应用也跟着崩溃。然后7.0以下的系统就没有这么幸运了,特别是低版本的WebView。考虑应用的稳定性,我们可以把7.0以下系统的WebView使用一个独立进程的Activity来包装,这样即使WebView崩溃了,也只是WebView所在的进程发生了崩溃,主进程还是不受影响的。
- 该方案是考拉的分享,具体内容可以看这篇文章。[如何设计一个优雅健壮的Android WebView](https://blog.klmobile.app/2018/02/27/design-an-elegant-and-powerful-android-webview-part-two/)
```
public static Intent getWebViewIntent(Context context) {
Intent intent;
if (isWebInMainProcess()) {
intent = new Intent(context, MainWebviewActivity.class);
} else {
intent = new Intent(context, WebviewActivity.class);
}
return intent;
}
public static boolean isWebInMainProcess() {
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N;
}
```
### 16.使用外部浏览器下载
- 比如,打开一个h5的链接,遇到下载链接,即跳转到外部浏览器打开;其他的则在webView中打开。如何操作呢?代码如下所示:
```
private class MyX5WebViewClient extends X5WebViewClient {
public MyX5WebViewClient(X5WebView webView, Context context) {
super(webView, context);
}
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
//返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
boolean activityAlive = X5WebUtils.isActivityAlive(WebViewActivity.this);
if (!activityAlive){
return false;
}
if (TextUtils.isEmpty(url)) {
return false;
}
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
//如果是返回数据
if (url.contains(".apk")) {
//为false调用系统浏览器或第三方浏览器
Uri issuesUrl = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, issuesUrl);
WebViewActivity.this.startActivity(intent);
return false;
}else {
mWebView.loadUrl(url);
return true;
}
//return super.shouldOverrideUrlLoading(webView, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView webView, WebResourceRequest request) {
//返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
boolean activityAlive = X5WebUtils.isActivityAlive(WebViewActivity.this);
if (!activityAlive){
return false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String url = request.getUrl().toString();
if (TextUtils.isEmpty(url)) {
return false;
}
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
//如果是返回数据
if (url.contains(".apk")) {
//为false调用系统浏览器或第三方浏览器
Uri issuesUrl = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, issuesUrl);
WebViewActivity.this.startActivity(intent);
return false;
}else {
mWebView.loadUrl(url);
return true;
}
}else {
return super.shouldOverrideUrlLoading(webView, request);
}
}
}
```
### 17.tel,sms等协议用法
- 网页中tel,sms,mailTo,Intent,Market协议,那么他们分别都是怎么用的呢
- tel:协议---拨打电话
- 在html中
```
电话给我
```
- 在java中
```
//tel:协议---拨打电话
if(url.startsWith("tel:")) {
//直接调出界面,不需要权限
Intent sendIntent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(sendIntent);
//或者
//直接拨打,需要权限
//Intent sendIntent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
//startActivity(sendIntent);
//否则键盘回去,页面显示"找不到网页"
return true;
}
```
- sms:协议---发送短信
- 在html中
```
调出发短信界面
调出发短信界面显示号码
调出发短信界面显示号码和发送内容
ios调出发短信界面显示号码和发送内容
调出发短信界面给多个号码发内容
调出发短信界面显示号码
调出发短信界面给多个号码发内容
```
- 在java中
```
if(url.startsWith("sms:")||url.startsWith("smsto:")||url.startsWith("mms:")||url.startsWith("mmsto:")) {
//直接调出界面,不需要权限
Intent sendIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(sendIntent);
//或者
//打开短信页面,不需要权限
//Intent sendIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
//startActivity(sendIntent);
//或者
//import android.telephony.SmsManager;
//SmsManager smsg = SmsManager.getDefault();//----看不到已发送信息。。。
//smsg.sendTextMessage("10086", null, "tttttt", null, null);
//或者
//---可以看到已发的信息
//ContentValues values = new ContentValues();
//values.put("address", "10086");
//values.put("body", "contents");
//ContentResolver contentResolver = getContentResolver();
//contentResolver.insert(Uri.parse("content://sms/sent"), values);
// contentResolver.insert(Uri.parse("content://sms/inbox"), values);
//
//
//
//否则键盘回去,页面显示"找不到网页"
return true;
}
```
- mailto:协议---发送邮件
- 在html中
```
邮件
```
- 在java中
```
if (url.startsWith("mailto:")) {
//打开发邮件窗口
Intent mailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
startActivity(mailIntent);
//
return true;
}
```
### 29.关于拦截处理注意要点
### 30.FileChooser文件处理
- https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650238618&idx=1&sn=1b791c0df4e48b317b986e7cbb07bb1d&chksm=88639ff5bf1416e3b7b50879677dc1ff4da1a833f3bba9b0abc52ee21b81a89ce1d863bec7a1&scene=38#wechat_redirect