###
是基于Android使用了WebKit内核的浏览器框架,和其他组件没有什么区别,主要作用就是加载一些基于HTML页面的基本信息
为什么使用WebView 优点:
1、兼容已有的项目 例如淘宝
2、可以动态更新 BUG的处理
基于以上好处,人们开始思考是否可以通过WebView去替代原有的Native原生开发。于是FaceBook去研究通过WebView去代替原有的Native开发,但是好景不长,facebook发现了WebView不是想象当中的那么好用,发现了如下缺点。
缺点:
1、使用webView的App统一耗电量都比较庞大
2、使用WebView的APP的加载速度也比较卡,
3、由于耗电量比较大导致手机容特别容易发热
WebView基本去使用 其实Google把相应的方式都封装在相应的WebKit内核当中所以对于客户端的我们来说我们只需要创建WebView的对象,通过该对象的loadUrl(String url)方法去加载即可。
1、在XMl布局文件当中引入WebView
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" > <WebView android:id ="@+id/webView" android:layout_width ="match_parent" android:layout_height ="match_parent" /> </LinearLayout >
2、在MainActivity当中对WebView控件进行设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private WebView mWebView;private String mUrl = "http://www.baidu.com" ;@Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView () { mWebView = (WebView) findViewById(R.id.webView); mWebView.loadUrl(mUrl); }
效果就是当我们的应用程序跑起来以后我们的WebView回去通过浏览器去加载我们的网页,所以我们去对WebView进行设置防止通过浏览器去进行加载
这个时候我们就需要自己去定义一个WebViewClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 mWebView.setWebViewClient(new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading (WebView view, String url) { view.loadUrl(url); return super .shouldOverrideUrlLoading(view, url); } });
WebView自定义Title 我们手机在显示WebView的时候会在标题的位置去显示当前加载的网页的地址之类的信息。
1、我们去修改XML布局,添加要显示地址的View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <RelativeLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" > <Button android:id ="@+id/back" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="返回" /> <TextView android:id ="@+id/title" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerInParent ="true" /> <Button android:id ="@+id/refresh" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_alignParentRight ="true" android:text ="刷新" /> </RelativeLayout > <WebView android:id ="@+id/webView" android:layout_width ="match_parent" android:layout_height ="match_parent" /> </LinearLayout >
2、对WebView控件进行设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 package pengchengliu.imooc.webview;import android.graphics.Bitmap;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.webkit.WebChromeClient;import android.webkit.WebView;import android.webkit.WebViewClient;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity { private String TAG = "WebViewTag" ; private WebView mWebView; Button mBackButton, mRefreshButton; private TextView mTitleTextView; String mUrl = "https://www.baidu.com" ; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i(TAG ,"Activity创建," + "线程Id为" + Thread.currentThread().getId()); initView(); } private void initView () { findView(); mWebView.setWebViewClient(new WebViewClient(){ @Override public void onPageStarted (WebView view, String url, Bitmap favicon) { Log.i(TAG ,"页面开始加载," + "线程Id为" + Thread.currentThread().getId()); super .onPageStarted(view, url, favicon); } @Override public void onPageCommitVisible (WebView view, String url) { Log.i(TAG ,"页面开始加载," + "线程Id为" + Thread.currentThread().getId()); super .onPageCommitVisible(view, url); } @Override public void onPageFinished (WebView view, String url) { Log.i(TAG ,"页面加载完成," + "线程Id为" + Thread.currentThread().getId()); super .onPageFinished(view, url); } @Override public boolean shouldOverrideUrlLoading (WebView view, String url) { view.loadUrl(url); return super .shouldOverrideUrlLoading(view, url); } }); mWebView.setWebChromeClient(new WebChromeClient(){ @Override public void onReceivedTitle (WebView view, String title) { mTitleTextView.setText(title); super .onReceivedTitle(view, title); } }); mWebView.loadUrl(mUrl); } private void findView () { mWebView = (WebView) findViewById(R.id.webView); mBackButton = (Button) findViewById(R.id.back); mRefreshButton = (Button) findViewById(R.id.refresh); mTitleTextView = (TextView) findViewById(R.id.title); MyListener myListener = new MyListener(); mBackButton.setOnClickListener(myListener); mRefreshButton.setOnClickListener(myListener); } private class MyListener implements View .OnClickListener { @Override public void onClick (View v) { switch (v.getId()){ case R.id.back: mWebView.goBack(); Log.i(TAG, mWebView.getUrl()); break ; case R.id.refresh: mWebView.reload(); Log.i(TAG, mWebView.getUrl()); break ; } } } }
使用WebView去下载文件 在下载之前必须要获取到文件的地址,需要DownloadListener接口的实现类,重写他的方法,而下载的具体url遍会出现在它的参数当中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private class WebViewDownload implements DownloadListener { @Override public void onDownloadStart (String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { Log.i("haha" , "这是一个下载地址, url ------ > " + url); if (!url.endsWith(".apk" )){ Log.i("haha" , "下载文件不是apk文件" ); return ; } new DownloadThread(url).start(); } }
主要就是两种方式,
1、通过WebView获取将要下载的文件地址,然后通过Intent将要下载的Uri地址传过去,让系统自己为我们下载。 1 2 3 Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); startActivity(intent);
2、通过WebView获取将要下载的文件地址,然后自己新建一个线程去进行下载。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class DownloadThread extends Thread { private String url ; DownloadThread(String url){ this .url = url; } @Override public void run () { try { Log.i("haha" , "开始下载" ); URL url = new URL(this .url); HttpURLConnection connect = (HttpURLConnection) url.openConnection(); connect.setDoInput(true ); connect.setDoOutput(true ); InputStream inputStream = connect.getInputStream(); if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ Log.e("haha" , "不存在SD卡,或者外部存储空间不足" ); return ; } File descFile = Environment.getExternalStorageDirectory(); File srcFile = new File(descFile, "temp.apk" ); OutputStream outputStream = new FileOutputStream(srcFile); byte [] bytes = new byte [6 * 1024 ]; int len ; while ((len = inputStream.read(bytes)) != -1 ){ outputStream.write(bytes, 0 , len); Log.i("haha" , "0,~~~" + len); } Log.i("haha" , "下载成功" ); } catch (IOException e) { Log.i("haha" , "下载失败" ); e.printStackTrace(); } } }
WebView对错误页面的处理 在手机没有网络的情况下,WebView会默认给我们展示一个断网页面,但是对于一个商业项目来说,展示这个页面略显粗糙,我们如何自定义错误页面呢?有以下两种方法
1 2 3 4 5 6 @Override public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error) { mWebView.loadData(file: super .onReceivedError(view, request, error); }
2、加载一个本地的Native页面,在相应的WebViewClient内部重写监听WebView加载错误的回调,然后在错误回调当中去做相应的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mWebView.setWebViewClient(new WebViewClient(){ @Override public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error) { Log.i("xixi" ,"onReceivedError," ); mErrorTextView .setText("404有错误" ); mWebView.setVisibility(View.GONE); super .onReceivedError(view, request, error); } }
WebView如何同步Cookie问题 开发过程中如果我们已经让用户登陆过了,在另外的页面是WebView,在刷到最新的WebView的时候会提示用户重新登录,但是如果我们把登录信息保存在Cookie当中,在刷WebView的时候一起传递给服务器,这样遍不会提示用户重新登录了。
这种问题就是客户端和服务器去同步Cookie问题。
流程就是:客户端先去登录,登录以后可以拿到相应的Cookie信息,当我们去第二次进行登陆的时候,拿着我们第一次登录Cookie信息,去设置相应的Cookie,已达到免登录的状态
问题 : 如何通过JavaAPI去进行POST请求,然后根据相应的POST请求拿到相应的Cookie,然后如何通过设置Cookie给WebView进行相应的设置,以至于达到免登录的状态
如何进行POST请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 CookieStore cookieStore = new BasicCookieStore(); CloseableHttpClient client = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build(); HttpPost httpPost = new HttpPost(requestUrl); List<NameValuePair> params = new ArrayList<>(); NameValuePair name = new BasicNameValuePair("" ,"" ); NameValuePair password = new BasicNameValuePair("" ,"" ); params.add(name);params.add(password); if (params.size() <= 0 ){ return ; } UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, charset); httpPost.setEntity(entity); CloseableHttpResponse execute = client.execute(httpPost);
如何获取Cookie
1 2 3 4 5 6 7 if (execute.getStatusLine().getStatusCode() == 200 ){ List<Cookie> cookies = cookieStore.getCookies(); for (Cookie tempCookie : cookies) { Log.i("cookies" , tempCookie.getName() + ": " + tempCookie.getValue() ); } }
如何给WebView设置Cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 private Handler mHandler = new Handler(){ @Override public void handleMessage (Message msg) { CookieSyncManager.createInstance(MainActivity.this ); CookieManager manager = CookieManager.getInstance(); manager.setAcceptCookie(true ); manager.setCookie(mUrl, msg.obj.toString()); CookieSyncManager.getInstance().sync(); mWebView.loadUrl(mUrl); super .handleMessage(msg); } };
WebView和JS混淆回调 首先我们应该明白,在正常的情况下WebView和JS是可以相互调用的通过@JavaScriptIntface注解,但是在我们的apk上线的时候我们需要给我们的apk打入一个replase包,让我们的apk当中的代码混淆,这样就会使我们原有的JS调不到我们的android端的接口。
解决方案:就是在打入混淆的时候,对我们要给Js回调的类和方法进行保护,这样当前类就不能混淆了。
在当前项目里面的proguard.cfg文件当中加入如下代码
1 2 3 -keep public class com.example.webview_01.WebHost{ public <methods> }
所涉及到的知识点:
WebHost.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class WebHost { private Context mContext; public WebHost (Context context) { this .mContext = context; } public void callJs () { Toast.makeText(mContext, "12334" , Toast.LENGTH_LONG).show(); } }
MainActivity.java
1 2 3 4 5 6 7 8 9 /** * Java 暴露给 Js 回调接口 */ @SuppressLint("AddJavascriptInterface") private void setWebViewJavaScriptInterface() { WebSettings settings = mWebView.getSettings(); settings.setJavaScriptEnabled(true); mWebView.addJavascriptInterface(new WebHost(MainActivity.this),"callJs"); }
WebView导致的远程注入的问题 在android本地和js交互的途中会出现种种的注入问题,大体的流程就是我们把客户端的对象暴露给了服务器,服务器当中可以拿到我们暴露给它的对象,拿到这个对象,服务器就可以拿到我们本地的Runtime对象,就哭执行种种命令来获取我们本地的信息。Google也是意识到这样的危险性,所以在版本4.2之前就已经被修复,浏览器厂商也是做了相应的BUG修复,但是在开发者做WebView的时候,需要注意。
解决办法:尽可能少的去做Java和JS之间的交互,最好的就是通过自定义协议来处理相应的问题。
WebView自定义协议拦截问题 就是客户端人员和Web前端人员进行商议得到一个协议,然后就是通过Url上面带有不同的参数在Android里面的回调根据不同的参数进行不同的参数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public boolean shouldOverrideUrlLoading (WebView view, String url) { if (url.endsWith("xxxxxxx" )){ return true ; } view.loadUrl(url); return super .shouldOverrideUrlLoading(view, url); }