加载本地 加载HTML文本 加载网页数据和本地文件合并后显示
src=>main=>assets目录下创建news_top.html和news_bottom.html
news_top.html
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 <!doctype html > <html > <head > <meta charset ="UTF-8" > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" /> <style > body { font-size : 16px ; line-height :200% !important ; letter-spacing :2px !important ; text-align : start; word-wrap :break-word !important ; } img { max-width : 100% !important ; height : auto; border-radius : 4px ; } p { padding : 0 !important ; } </style > </head > <body > <script type ='text/javascript' > window .onload = function ( ){ var news_imgs = document .getElementsByTagName ('img' ); for (var i in news_imgs){ news_img[i].src = news_img[i].src +"!newinfo" ; } } </script >
news_bottom.html
XML
1 2 3 4 5 6 7 8 <WebView android:id ="@+id/webView" android:layout_width ="match_parent" android:layout_height ="0dp" android:layout_marginStart ="0dp" android:layout_marginEnd ="0dp" android:layout_weight ="1" android:background ="@color/zj_gay_f9" />
Activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fun initWebView (html: String ) { val webSettings = webView.getSettings(); webView.getSettings().setJavaScriptEnabled(true ); webView.getSettings().setDefaultTextEncodingName("UTF -8" ); webSettings.setAllowFileAccess(true ); webSettings.setAllowFileAccessFromFileURLs(true ); webSettings.setAllowContentAccess(true ); webSettings.setDomStorageEnabled(true ); webView.getSettings().setAllowFileAccessFromFileURLs(true ); val tophtml = ZJFileUtils.ReadTxtFile(mContext, "news_top.html" ) val bottomhtml = ZJFileUtils.ReadTxtFile(mContext, "news_bottom.html" ) val html_all = tophtml + html + bottomhtml webView.loadData(html_all, "text/html; charset=UTF-8" , null ) }
中文乱码
使用 loadData方法官方推荐的写法在部分手机上会中文乱码,即使指定utf-8、gbk、gb2312也一样。
1 webView.loadData(data, "text/html" , "UTF -8" );
解决方法
1 2 webView.getSettings().setDefaultTextEncodingName("UTF -8" ); webView.loadData(data, "text/html; charset=UTF-8" , null );
官方真是坑啊!!!
Utils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ZJFileUtils { public static String ReadTxtFile (Context mContext, String filename) { StringBuilder sb = new StringBuilder (); try { InputStream instream = mContext.getAssets().open(filename); InputStreamReader inputreader = new InputStreamReader (instream); BufferedReader buffreader = new BufferedReader (inputreader); String line; while ((line = buffreader.readLine()) != null ) { sb.append(line + "\n" ); } instream.close(); } catch (java.io.FileNotFoundException e) { Log.d("TestFile" , "The File doesn't not exist." ); } catch (IOException e) { Log.d("TestFile" , e.getMessage()); } return sb.toString(); } }
加载本地HTML文件 xieyi.html 放在 src=>main=>assets目录下
1 2 3 4 5 6 7 8 9 10 11 fun initWebView () { var webSettings = webView.getSettings(); webView.getSettings().setJavaScriptEnabled(false ); webSettings.setAllowFileAccess(true ); webSettings.setAllowFileAccessFromFileURLs(true ); webSettings.setAllowContentAccess(true ); webSettings.setDomStorageEnabled(true ); webView.getSettings().setAllowFileAccessFromFileURLs(true ); webView.loadUrl("file:///android_asset/xieyi.html" ); }
加载URL 权限
application同级添加
1 <uses-permission android:name ="android.permission.INTERNET" />
application属性添加下面代码来允许http请求
1 android:usesCleartextTraffic="true"
模板
1 2 3 4 <WebView android:id ="@+id/webView" android:layout_width ="match_parent" android:layout_height ="match_parent" />
加载网址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @SuppressLint("SetJavaScriptEnabled" ) private fun loadUrl (url: String ) { val webSettings = webView.getSettings(); webSettings.javaScriptEnabled = true ; webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); webView.setWebChromeClient(WebChromeClient()); webView.setWebViewClient(WebViewClient()); webView.loadUrl(url); webView.setWebViewClient(object : WebViewClient() { override fun shouldOverrideUrlLoading ( view: WebView ?, request: WebResourceRequest ? ) : Boolean { view?.loadUrl(request?.url.toString()) return true } }); }
原生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 <?xml version="1.0" encoding="utf-8" ?> <android.support.v7.widget.LinearLayoutCompat xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".activity.MainActivity" > <RelativeLayout android:layout_width ="match_parent" android:layout_height ="24dp" android:background ="@color/purple_700" > </RelativeLayout > <RelativeLayout android:layout_width ="match_parent" android:layout_height ="44dp" android:background ="@color/purple_700" android:gravity ="center_vertical" > <RelativeLayout android:id ="@+id/back_btn" android:layout_width ="44dp" android:layout_height ="match_parent" android:layout_alignParentStart ="true" android:clickable ="true" android:focusable ="true" > <ImageView android:layout_width ="18dp" android:layout_height ="18dp" android:layout_centerInParent ="true" android:src ="@mipmap/iv_back_withe" /> </RelativeLayout > <TextView android:layout_width ="wrap_content" android:layout_height ="match_parent" android:text ="国家中小学网络云平台" android:textColor ="@color/white" android:layout_centerHorizontal ="true" android:gravity ="center_vertical" android:textSize ="18sp" /> <android.support.v7.widget.LinearLayoutCompat android:layout_width ="wrap_content" android:layout_alignParentEnd ="true" android:layout_height ="match_parent" android:orientation ="horizontal" > <RelativeLayout android:id ="@+id/home_btn" android:layout_width ="64dp" android:layout_height ="match_parent" android:clickable ="true" android:focusable ="true" > <TextView android:text ="首页" android:textSize ="16sp" android:textColor ="@color/white" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerInParent ="true" /> </RelativeLayout > <RelativeLayout android:id ="@+id/refresh_btn" android:layout_width ="64dp" android:layout_height ="match_parent" android:clickable ="true" android:focusable ="true" > <TextView android:text ="刷新" android:textSize ="16sp" android:textColor ="@color/white" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_centerInParent ="true" /> </RelativeLayout > </android.support.v7.widget.LinearLayoutCompat > </RelativeLayout > <WebView android:id ="@+id/mWebView" android:layout_width ="match_parent" android:layout_height ="0dp" android:layout_weight ="1" /> </android.support.v7.widget.LinearLayoutCompat >
Activity 引用 1 2 3 4 5 6 7 8 9 import android.webkit.*import android.view.KeyEventimport android.app.ProgressDialogimport android.content.DialogInterfaceimport org.jetbrains.anko.doAsyncimport org.jetbrains.anko.uiThread
代码
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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 import android.annotation .SuppressLintimport android.app.Activityimport android.app.ProgressDialogimport android.content.DialogInterfaceimport android.content.pm.PackageManagerimport android.graphics.Bitmapimport android.os.Bundleimport android.support.v4.app.ActivityCompatimport android.support.v7.app.AppCompatActivityimport android.util.Logimport android.view.KeyEventimport android.view.Viewimport android.widget.Toastimport com.xhkjedu.eduyun_android.Rimport com.xhkjedu.eduyun_android.utils.AndroidFileUtilimport kotlinx.android.synthetic.main.activity_main.*import org.jetbrains.anko.doAsyncimport org.jetbrains.anko.uiThreadimport android.webkit.*class MainActivity : AppCompatActivity () { var TAG = "MainActivity" var mUrl = "https://h5.zxx.edu.cn/syncClassroom" override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) verifyStoragePermissions(this ) loadUrl(mUrl); back_btn.setOnClickListener { onBackPressed() } refresh_btn.setOnClickListener { mWebView.reload() } home_btn.setOnClickListener { loadUrl(mUrl) mWebView.postDelayed( Runnable() { mWebView.clearHistory() }, 1000 ); } } @SuppressLint("SetJavaScriptEnabled" ) private fun loadUrl (url: String ) { val webSettings = mWebView.settings; webSettings.javaScriptEnabled = true ; webSettings.javaScriptCanOpenWindowsAutomatically = true ; webSettings.useWideViewPort = true ; webSettings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL; webSettings.displayZoomControls = false ; webSettings.allowFileAccess = true ; webSettings.builtInZoomControls = false ; webSettings.setSupportZoom(false ); webSettings.loadWithOverviewMode = true ; webSettings.mixedContentMode = WebSettings.LOAD_NORMAL; webSettings.userAgentString = "Microsoft Edge" ; webSettings.domStorageEnabled = true ; webSettings.setGeolocationEnabled(true ); webSettings.setAppCacheMaxSize(Long .MAX_VALUE); webSettings.setPluginState(WebSettings.PluginState.ON_DEMAND); webSettings.cacheMode = WebSettings.LOAD_DEFAULT; val jsstr = AndroidFileUtil.getFromAssets(this , "m_inject.js" ) mWebView.webChromeClient = object : WebChromeClient() { override fun onProgressChanged (p0: WebView ?, p1: Int ) { super .onProgressChanged(p0, p1) if (p1 == 100 ) { mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> { override fun onReceiveValue (p0: String ?) { p0?.let { doAsync { Thread.sleep(300 ) uiThread { mWebView.visibility = View.VISIBLE hideProgress() } } } } }) } } }; mWebView.webViewClient = object : WebViewClient() { override fun onPageStarted (p0: WebView ?, p1: String ?, p2: Bitmap ?) { super .onPageStarted(p0, p1, p2) Log.e(TAG, "onPageStarted" ) showProgress() mWebView.visibility = View.INVISIBLE; } override fun onPageFinished (p0: WebView ?, p1: String ?) { super .onPageFinished(p0, p1) Log.e(TAG, "onPageFinished" ) } }; mWebView.loadUrl(url) } override fun onDestroy () { if (mWebView != null ) { mWebView.destroy() } super .onDestroy() } private val REQUEST_EXTERNAL_STORAGE = 1 private val PERMISSIONS_STORAGE = arrayOf( "android.permission.READ_EXTERNAL_STORAGE" , "android.permission.WRITE_EXTERNAL_STORAGE" , "android.permission.READ_PHONE_STATE" ) fun verifyStoragePermissions (activity: Activity ?) { try { val permission = ActivityCompat.checkSelfPermission( activity!!, "android.permission.WRITE_EXTERNAL_STORAGE" ) if (permission != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE ) } } catch (e: Exception) { e.printStackTrace() } } private var progressDialog: ProgressDialog? = null fun showProgress () { if (progressDialog == null ) { progressDialog = ProgressDialog.show(this @MainActivity , "" , "页面加载中" ) progressDialog?.setOnKeyListener(object : DialogInterface.OnKeyListener { override fun onKey (dialog: DialogInterface ?, keyCode: Int , p2: KeyEvent ?) : Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { dialog?.cancel(); return true ; } return false ; } }) } } fun hideProgress () { progressDialog?.dismiss() progressDialog = null } private var exitTime: Long = 0 override fun onBackPressed () { if (mWebView.canGoBack()) { mWebView.goBack(); } else { if ((System.currentTimeMillis() - exitTime) > 1200 ) { Toast .makeText( applicationContext, "再按一次退出程序" , Toast.LENGTH_SHORT ) .show(); exitTime = System.currentTimeMillis(); } else { finish(); System.exit(0 ); } } } }
注意几点
拦截弹窗 1 2 3 4 5 6 7 8 9 mWebView.webChromeClient = object : WebChromeClient() { override fun onJsAlert (wv: WebView ?, url: String ?, message: String ?, result: JsResult ?) : Boolean { if (message!=null ){ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); } result?.cancel() return true } };
拦截请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mWebView.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading ( view: WebView ?, request: WebResourceRequest ? ) : Boolean { if (request?.url != null ){ val url = request.url.toString() if (url.contains("www.psvmc.cn" )){ view?.loadUrl(url) } } return true } }
加载中Dialog 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 mWebView.webChromeClient = object : WebChromeClient() { override fun onProgressChanged (p0: WebView ?, p1: Int ) { super .onProgressChanged(p0, p1) if (p1 == 100 ) { mWebView.evaluateJavascript(jsstr, object : ValueCallback<String> { override fun onReceiveValue (p0: String ?) { p0?.let { doAsync { Thread.sleep(200 ) uiThread { mWebView.visibility = View.VISIBLE hideProgress() } } } } }) } } }; mWebView.webViewClient = object : WebViewClient() { override fun onPageStarted (p0: WebView ?, p1: String ?, p2: Bitmap ?) { super .onPageStarted(p0, p1, p2) Log.e(TAG, "onPageStarted" ) showProgress() mWebView.visibility = View.INVISIBLE; } override fun onPageFinished (p0: WebView ?, p1: String ?) { super .onPageFinished(p0, p1) Log.e(TAG, "onPageFinished" ) } }; private var progressDialog: ProgressDialog? = null fun showProgress () { if (progressDialog == null ) { progressDialog = ProgressDialog.show(this @MainActivity , "" , "页面加载中" ) progressDialog?.setOnKeyListener( object :DialogInterface.OnKeyListener{ override fun onKey (dialog: DialogInterface ?, keyCode: Int , p2: KeyEvent ?) : Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { dialog?.cancel(); return true ; } return false ; } }) } } fun hideProgress () { progressDialog?.dismiss() progressDialog = null }
注意
不建议在onPageFinished中调用,这个方法调用时页面未必加载完毕,并且可能被回调多次。
建议在onProgressChanged回调中,并且进度为100的时候调用,进度100同样会被调用多次,所以我们要进行判断,保证加载中弹窗唯一。
注入JS的时候尽管我们在JS执行返回的时候调用Webview显示,但仍会出现看到注入前的页面样式,所以这里延迟显示了。
Dialog不能通过当前页面的onBackPressed事件中取消,只能在dialog上进行监听事件。
显示进度条
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 fun showProgress () { if (progressDialog == null ) { progressDialog = ProgressDialog(this ) progressDialog?.setTitle("请稍后" ); progressDialog?.setMessage("加载中..." ); progressDialog?.max = 100 ; progressDialog?.progress = 0 ; progressDialog?.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog?.setCanceledOnTouchOutside(false ) progressDialog?.show(); progressDialog?.setOnKeyListener(object : DialogInterface.OnKeyListener { override fun onKey (dialog: DialogInterface ?, keyCode: Int , p2: KeyEvent ?) : Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { dialog?.cancel(); return true ; } return false ; } }) doAsync { Thread.sleep(10000 ) uiThread { hideProgress() } } } } fun setProgressValue (p:Int ) { progressDialog?.progress = p }
点击外部不隐藏
1 progressDialog?.setCanceledOnTouchOutside(false )
处理返回按键 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private var exitTime: Long = 0 override fun onBackPressed () { if (mWebView.canGoBack()) { mWebView.goBack(); } else { if ((System.currentTimeMillis() - exitTime) > 1200 ) { Toast .makeText( applicationContext, "再按一次退出程序" , Toast.LENGTH_SHORT ) .show(); exitTime = System.currentTimeMillis(); } else { finish(); System.exit(0 ); } } }
返回首页 为了让返回按钮控制WebView“返回某一个指定页”,我处理了按钮消息并加入相关逻辑。
我需要在特定的时候调用WebView.clearHistory(),不料clearHistory()并未起作用。
原因是clearHistory()只清除当前页之前的历史记录。
假设当前页面为A,我调用WebView.clearHistory()然后loadUrl(B),接着回退还是会退到A。
所以正确的调用时机是在B完全载入之后再调用WebView.clearHistory()
1 2 3 4 5 6 7 8 home_btn.setOnClickListener { mWebView.loadUrl(homeurl) mWebView.postDelayed( Runnable() { mWebView.clearHistory() }, 1000 ); }
刷新 1 2 3 refresh_btn.setOnClickListener { mWebView.reload() }
注入脚本 隐藏元素 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 function hide_dom ( ){ var cls_arr_delay = [ ".fm-navbar" ]; let hasload = false ; for (var i in cls_arr_delay) { var dom_all = document .querySelectorAll (cls_arr_delay[i]); if (dom_all && dom_all.length >0 ) { dom_all.forEach (dom => { dom.style .display = "none" ; }); hasload = true ; } } let course_detail_player = document .querySelector (".course-detail-player" ); course_detail_player.style .height = "auto" ; if (hasload && window .myinter ){ clearInterval (window .myinter ); } }; window .myinter = setInterval (()=> { hide_dom (); },100 ); hide_dom ();document .oncontextmenu = function (ev ) { ev.preventDefault (); }; document .onselectstart = function (ev ) { ev.preventDefault (); }; window .onhashchange = function ( ) { let dom = document .querySelector ("body" ); dom.style .display = "none" ; hide_dom (); setTimeout (()=> { dom.style .display = "block" ; },100 ); };
删除
1 2 3 4 5 6 7 8 9 10 11 12 var remove_arr = [ ".shici_tab" , ".action" ]; for (var i in remove_arr) { var dom_all = document .querySelectorAll (remove_arr[i]); if (dom_all) { dom_all.forEach (dom => { dom.remove (); }); } }
其它
1 2 3 4 var dom7 = document .querySelector (".comm-content-left" );if (dom7) { dom7.style .width = "100%" ; }
注意
有时我们发现有些页面的跳转,虽然URL发生变化了,但是Android回调没有触发,这是因为页面内使用的hash跳转,这时候就只能使用JS监听了。
示例
1 2 3 4 5 6 7 8 window .onhashchange = function ( ) { let dom = document .querySelector ("body" ); dom.style .display = "none" ; hide_dom (); setTimeout (()=> { dom.style .display = "block" ; },100 ); };
禁用复制事件 1 2 3 4 5 6 7 document .oncontextmenu = function (ev ) { ev.preventDefault (); }; document .onselectstart = function (ev ) { ev.preventDefault (); };