Android WebView 全面干货指南

2、同1解决;

3、在创建 WebView 时,使用 removeJavascriptInterface 方法将系统注入的 searchBoxJavaBridge_ 对象删除。

4、当系统辅助功能服务被开启时,在 Android 4.4 以下的系统中,由系统提供的 WebView 组件都默认导出 ”accessibility” 和 ”accessibilityTraversal” 这两个接口,这两个接口同样存在远程任意代码执行的威胁,同样的需要通过 removeJavascriptInterface 方法将这两个对象删除。

super.removeJavascriptInterface("searchBoxJavaBridge_"); super.removeJavascriptInterface("accessibility"); super.removeJavascriptInterface("accessibilityTraversal");

以上都是系统机制层面上的漏洞,还有一些是使用 WebView 不挡产生的漏洞。

5、通过 WebSettings.setSavePassword(false) 关闭密码保存提醒功能,防止明文密码存在本地被盗用。

6、WebView 默认是可以使用 File 协议的,也就是 setAllowFileAccess(true),我们应该是主动设置为 setAllowFileAccess(false),防止加载本地文件,移动版的 Chrome 默认禁止加载 file 协议的文件

setAllowFileAccess(true);//设置为 false 将不能加载本地 html 文件 setAllowFileAccessFromFileURLs(false); setAllowUniversalAccessFromFileURLs(false); if (url.startsWith("file://") { setJavaScriptEnabled(false); } else { setJavaScriptEnabled(true); }

安全修复案例

推荐 SafeWebView 这个库中解决了 Android WebView 中 Js 注入漏洞问题,另外还包含了一些异常处理。可以自行下载阅读源码。


七、一些坑

主要总结 WebView 相关的疑难 bug,由于 Android 版本严重碎片化,在使用 WebView 的时候也会遇到各种个样的坑,特别是 4.4 之后更换了 WebView 内核,4.2 以下有部分漏洞,所以想把经历过的 WebView 这些坑记录下来,仅供参考。

1、android.webkit.AccessibilityInjector$TextToSpeechWrapper

java.lang.NullPointerException

at android.webkit.AccessibilityInjector$TextToSpeechWrapper$1.onInit(AccessibilityInjector.java:753)

at android.speech.tts.TextToSpeech.dispatchOnInit(TextToSpeech.java:640)

at android.speech.tts.TextToSpeech.initTts(TextToSpeech.java:619)

at android.speech.tts.TextToSpeech.(TextToSpeech.java:553)

at android.webkit.AccessibilityInjector$TextToSpeechWrapper.(AccessibilityInjector.java:676)

at android.webkit.AccessibilityInjector.addTtsApis(AccessibilityInjector.java:480)

at android.webkit.AccessibilityInjector.addAccessibilityApisIfNecessary(AccessibilityInjector.java:168)

at android.webkit.AccessibilityInjector.onPageStarted(AccessibilityInjector.java:340)

at android.webkit.WebViewClassic.onPageStarted(WebViewClassic.java:4480)

at android.webkit.CallbackProxy.handleMessage(CallbackProxy.java:366)

at android.os.Handler.dispatchMessage(Handler.java:107)

at android.os.Looper.loop(Looper.java:194)

at android.app.ActivityThread.main(ActivityThread.java:5407)

at java.lang.reflect.Method.invokeNative(Native Method)

at java.lang.reflect.Method.invoke(Method.java:525)

at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)

at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)

at dalvik.system.NativeStart.main(Native Method)

此问题在4.2.1和4.2.2比较集中,关闭辅助功能,google 下很多结果都是一样的。

修复方法:在初始化 WebView 时调用disableAccessibility方法即可。

public static void disableAccessibility(Context context) { if (Build.VERSION.SDK_INT == 17/*4.2 (Build.VERSION_CODES.JELLY_BEAN_MR1)*/) { if (context != null) { try { AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); if (!am.isEnabled()) { //Not need to disable accessibility return; } Method setState = am.getClass().getDeclaredMethod("setState", int.class); setState.setAccessible(true); setState.invoke(am, 0);/**{@link AccessibilityManager#STATE_FLAG_ACCESSIBILITY_ENABLED}*/ } catch (Exception ignored) { ignored.printStackTrace(); } } } }

2、android.content.pm.PackageManager$NameNotFoundException

AndroidRuntimeException: android.content.pm.PackageManager$NameNotFoundException: com.google.android.webview

at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4604)

at android.app.ActivityThread.access$1500(ActivityThread.java:154)

at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1389)

at android.os.Handler.dispatchMessage(Handler.java:102)

at android.os.Looper.loop(Looper.java:135)

at android.app.ActivityThread.main(ActivityThread.java:5302)

at java.lang.reflect.Method.invoke(Native Method)

at java.lang.reflect.Method.invoke(Method.java:372)

at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:916)

at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:711)

Caused by: android.util.AndroidRuntimeException: android.content.pm.PackageManager$NameNotFoundException: com.google.android.webview

at android.webkit.WebViewFactory.getFactoryClass(WebViewFactory.java:174)

at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:109)

at android.webkit.WebView.getFactory(WebView.java:2194)

at android.webkit.WebView.ensureProviderCreated(WebView.java:2189)

at android.webkit.WebView.setOverScrollMode(WebView.java:2248)

at android.view.View.(View.java:3588)

at android.view.View.(View.java:3682)

at android.view.ViewGroup.(ViewGroup.java:497)

at android.widget.AbsoluteLayout.(AbsoluteLayout.java:55)

at android.webkit.WebView.(WebView.java:544)

at android.webkit.WebView.(WebView.java:489)

at android.webkit.WebView.(WebView.java:472)

at android.webkit.WebView.(WebView.java:459)

at android.webkit.WebView.(WebView.java:449)

现象:在创建 WebView 时崩溃,跟进栈信息,我们需要在 setOverScrollMode 方法上加异常保护处理

修复方法:

try { super.setOverScrollMode(mode); } catch (Throwable e) { e.printStackTrace(); }

不过上面捕获的异常范围有点广,在github上找到一个更全面的修复方法

try{ super.setOverScrollMode(mode); } catch(Throwable e){ String messageCause = e.getCause() == null ? e.toString() : e.getCause().toString(); String trace = Log.getStackTraceString(e); if (trace.contains("android.content.pm.PackageManager$NameNotFoundException") || trace.contains("java.lang.RuntimeException: Cannot load WebView") || trace.contains("android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed")) { e.printStackTrace(); }else{ throw e; } }

3、android.webkit.WebViewClassic.clearView

at android.webkit.BrowserFrame.nativeLoadUrl(Native Method)

System.err: at android.webkit.BrowserFrame.loadUrl(BrowserFrame.java:279)

System.err: at android.webkit.WebViewCore.loadUrl(WebViewCore.java:2011)

System.err: at android.webkit.WebViewCore.access$1900(WebViewCore.java:57)

System.err: at android.webkit.WebViewCore$EventHub$1.handleMessage(WebViewCore.java:1303)

System.err: at android.os.Handler.dispatchMessage(Handler.java:99)

System.err: at android.os.Looper.loop(Looper.java:137)

System.err: at android.webkit.WebViewCore$WebCoreThread.run(WebViewCore.java:812)

System.err: at java.lang.Thread.run(Thread.java:856)

webcoreglue: *** Uncaught exception returned from Java call!

System.err: java.lang.NullPointerException

System.err: at android.webkit.WebViewClassic.clearView(WebViewClassic.java:2868)

System.err: at android.webkit.WebViewCore.setupViewport(WebViewCore.java:2497)

System.err: at android.webkit.WebViewCore.updateViewport(WebViewCore.java:2479)

System.err: at android.webkit.BrowserFrame.nativeLoadUrl(Native Method)

System.err: at android.webkit.BrowserFrame.loadUrl(BrowserFrame.java:279)

System.err: at android.webkit.WebViewCore.loadUrl(WebViewCore.java:2011)

System.err: at android.webkit.WebViewCore.access$1900(WebViewCore.java:57)

System.err: at android.webkit.WebViewCore$EventHub$1.handleMessage(WebViewCore.java:1303)

System.err: at android.os.Handler.dispatchMessage(Handler.java:99)

System.err: at android.os.Looper.loop(Looper.java:137)

这个bug是在某些设备上发生的,是在调用webView.destroy() 之前调用了loadurl操作发生的,也不是毕现问题,所以只能跟进源码查看,

在清空 webview destroy 时,调用清理方法,内部可能时机有问题,会出现,WebViewClassic 中 mWebViewCore 对象为null,其内部为handler消息机制。

修复方法:

public void logdUrl(final String url) { try { super.loadUrl(url); } catch (NullPointerException e) { e.printStackTrace(); } }


八、JSBridge

相信很多都或多或少的了解 JsBridge,不管是 iOS 平台还是 Android平台,特别是 Hybrid 应用,肯定是要用的 JsBridge 这个机制来建立 Native 和 Web 端的通信。

本部分简单阐述下 JsBridge 原理,以及分析两个实际案例。

JsBridge 介绍:

JSBridge 我们可以比喻成一座桥或者一根管道,一端是 Web一端是 Native。我们搭建这个通道的目的就是让 Native 和 Web 之间互相调用更为方便统一和简洁。

JSBridge 做得好的一个典型就是微信,微信给开发者提供了 JSSDK,该SDK中暴露了很多微信native层的方法,比如支付,定位等。使用起来非常方便。

JsBridge 原理:

前面我们分析了 WebView 如何于 JavaScript 交互的,JSBridge 就是在这些基础之上做扩展使它支持更复杂的功能,三种形式两种原理分析如下:

1、使用 addJavascriptInterface

原理:这是Android提供的Js与Native通信的官方解决方案,将 java 对象注入到 Js 中直接作为window的某一变量来使用。

2、WebViewClient 中 shouldOverrideUrlLoading (WebView view, String url)。

利用 scheme iframe 机制,只要有iframe 加载,shouldOverrideUrlLoading 方法就会有回调。可以构造一个特殊格式的url,使用shouldOverrideUrlLoading 方法拦截url,根据解析url来之行native方法逻辑。

3、利用 WebChromeClient 中的 onJsAlert、onJsConfirm、onJsPrompt 提示接口,同样也是拦截操作。

利用 js调用window对象的对应的方法,即 window.alert,window.confirm,window.prompt,WebChromeClient 对象中的三个方法 onJsAlert、onJsConfirm、onJsPrompt 就会被触发,有了js到native的通道,那么我们就可以制定协议来约束对方。最终我们选择使用 prompt 方法,onJsPrompt()方法的message参数的值正是Js的方法window.prompt()的message的值。

汇总:后面两种虽然形式不同,但是原理是相同的,都是对url或者参数做文章,通过制定参数协议,不管是url还是message,到native拦截处理。native 调用 Js 只有一种方式,就是使用loadUrl(js),js 为在web端定义好的javascript 函数。

以上就是所有 JsBridge 的原理,自己可以写给demo跑一下,下面看几个问题:

1、如何避免 JS、Android、iOS 相互调用时,需要事先“约定”方法名称和参数?

2、原生调用 JS 方法,能否类似原生开发一样,使用 Callback(block) 做为回调方式?

3、JS 调用原生能否使用 function 获得返回值?

问题来源于网络,基本上都是这几个疑问,下面我们带着疑问去分析三个个方案。

JsBridge 案例:
一、 H5与Native交互之JSBridge技术

本案例为有赞技术团队博客分享的H5与Native交互之JSBridge技术,并没有最终完全的代码,不过很清楚的分析了IOS和Android与Javascript的底层交互原理。

1、实现原理

通过schema方式,使用shouldOverrideUrlLoading方法对url协议进行解析。

var url = 'jsbridge://doAction?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com'; var iframe = document.createElement('iframe'); iframe.style.width = '1px'; iframe.style.height = '1px'; iframe.style.display = 'none'; iframe.src = url; document.body.appendChild(iframe); setTimeout(function() { iframe.remove(); }, 100);

可以到看有赞技术是通过自定义url协议来作为传输媒介,这样 Android 就可以拦截这个请求,从而解析出相应的方法和参数

不过此 url 中的参数是以键值对的方式传递,我是建议使用 Json 作为传输参数比较好,灵活清楚。

2、库的封装

有赞将 Js与 Native 通讯封装了一个通用的方法,我这里直接复制过来分析下:

YouzanJsBridge = { doCall: function(functionName, data, callback) { var _this = this; // 解决连续调用问题 if (this.lastCallTime && (Date.now() - this.lastCallTime) < 100) { setTimeout(function() { _this.doCall(functionName, data, callback); }, 100); return; } this.lastCallTime = Date.now(); data = data || {}; if (callback) { $.extend(data, { callback: callback }); } if (UA.isIOS()) { $.each(data, function(key, value) { if ($.isPlainObject(value) || $.isArray(value)) { data[key] = JSON.stringify(value); } }); var url = Args.addParameter('youzanjs://' + functionName, data); var iframe = document.createElement('iframe'); iframe.style.width = '1px'; iframe.style.height = '1px'; iframe.style.display = 'none'; iframe.src = url; document.body.appendChild(iframe); setTimeout(function() { iframe.remove(); }, 100); } else if (UA.isAndroid()) { window.androidJS && window.androidJS[functionName] && window.androidJS[functionName](JSON.stringify(data)); } else { console.error('未获取platform信息,调取api失败'); } } }

这样不管是和iOS通信还是 Android,都只需要调用 YouzanJsBridge.doCall() 方法即可,讲两个平台的不同屏蔽在封装基础上,这样也有利于 Web 端代码的整洁和代码兼容。

当然这里的Android 平台是没有 callback 回调的,如果你想实现两端互调的机制,请参考下一个案例,里面会详细介绍这部分。

3、一些优化

将项目通用方法抽象

例如:

1.getData(datatype, callback, extra) H5从Native APP获取数据

使用场景:H5需要从Native APP获取某些数据的时候,可以调用这个方法。

2.putData(datatype, data) H5告诉Native APP一些数据

使用场景:H5告诉Native APP一些数据,可以调用这个方法。

3.gotoWebview(url, page, data) Native APP新开一个Webview窗口,并打开相应网页

4.doAction(action, data) 功能上的一些操作

等等其他方法,我相信如果你自己写过 native和js 调用demo,上面抽象出来的方法并不陌生,所以,如果你的业务没有那么复杂,没有像微信那样,需要提供给数以万计开发者去用去扩展,这种抽象出一些通用方法的方式不是为一种节省成本,快速迭代,方便的方式。

小结:总之万变不离其宗,所有封装或者框架的东西,使用的东西都还是最基本的方法,只是对基础做一个什么样程度扩展,或深或浅,唯一的只要用着舒服就行。

二:JsBridge

1、使用方法:

注意Android 和 Web 使用方式

webView.registerHandler("submitFromWeb", new BridgeHandler() { @Override public void handler(String data, CallBackFunction function) { function.onCallBack("native submitFromWeb 方法, 返回 data"); } }); webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { @Override public void onCallBack(String data) { Log.i(TAG, "onCallBack : " + data); } }); window.WebViewJavascriptBridge.callHandler( 'submitFromWeb' , {'param': '中文测试'} , function(responseData) { document.getElementById("show").innerHTML = "send get responseData from Android 端, data = " + responseData } ); bridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); var responseData = "call functionInJs success,return android!"; responseCallback(responseData); });

2、关键类作用:

在讲 JsBridge 的实现之前,首先要讲下各个文件的作用

  • Message.java:JsBridge 中的消息对象,用来封装native与js交互时的json数据,包含:callid、responseid、responseData、handlerName等字段。

  • WebViewJavascriptBridge.js:改js文件会被注入到各个页面,和native中封装处理Message消息逻辑类似,同样提供了初始化、注册Handler、调用Handler等方法,之后js都是通过此文件中的方法和native统一沟通。

  • WebViewJavascriptBridge.java:native 端,Bridge接口类,定义了发送信息的方法,由BridgeWebView来实现,之后调用可以通过webview.send()方式调用。

  • BridgeWebView.java:WebView的子类,实现了WebViewJavascriptBridge接口,并提供了注册、调用 Handler等方法,之后都是通过webview.registerHandler()或者webview.callHandler()方式调用js.

  • BridgeWebViewClient.java:WebViewClient的子类,重写了ShouldOverrideUrlLoading,onPageFinish,onPageStart等方法。

  • BridgeHandler.java:作为native与web交互的通道。native通过Handler的名称来找到响应的Handler来操作,这样才能实现js回调给native端,也就是registerHandler时候注册的监听。

  • CallBackFunction.java:native定义的回调函数, Handler处理完成后,用来给Js发送消息,对应处理js中function中。

3、实现原理:

看完这些主要类的作用,在看流程就清晰了。

native 调 js:

1、WebView.callHandler(‘handlerName’,’{}’,callBack);

2、doSend 中组装 要传输的 Message 对象,并设置setCallbackId(生成唯一的id是为了方便在js回调回来的时候在android端查找对于的 callback)

3、dispatchMessage javascript:WebViewJavascriptBridge._handleMessageFromNative(message);方法 message 为上一步的Message 对象对应的 Json 数据。

4、在 Js _dispatchMessageFromNative 函数中,根据 messageJSON json 数据中的字段 handlerName 找到对对应的 handler 方法执行。

handler = messageHandlers[message.handlerName];handler 这个就是在 html 中 registerHandler 注册的回调function(data, responseCallback)函数

5、之后执行 html 中 responseCallback(responseData); 会触发 WebViewJavascriptBridge.js 文件中的 _doSend函数,_doSend 通过 messagingIframe.src 形式传给 android端,shouldOverrideUrlLoading 接受,并拦截内容处理。

6、第一次:拦截执行到 webView.flushMessageQueue()方法,并调用 responseCallbacks.put(jsUrl,returnCallback);,同时调用 javascript:WebViewJavascriptBridge._fetchQueue(); 来查询消息的返回值,并执行一次messagingIframe.src。

7、第二次:拦截执行到 webView.handlerReturnData() 方法,并调用上一步注册的 CallBackFunction.onCallBack,然后根据responseId 即 android 传过来的 message.callbackId,找到 使用者注册的 CallBackFunction 回调。

本次的流程就走完了,看几十遍就看懂了哈。

Js -> android:

1、window.WebViewJavascriptBridge.callHandler

2、执行 Js 函数 _doSend() 触发 messagingIframe.src

3、android 端拦截,shouldOverrideUrlLoading

4、第一次:拦截执行到 webView.flushMessageQueue()方法,并调用 responseCallbacks.put(jsUrl,returnCallback);,同时调用 javascript:WebViewJavascriptBridge._fetchQueue(); 来查询消息的返回值,并执行一次messagingIframe.src。

5、第二次:拦截执行到 webView.handlerReturnData() 方法,并调用上一步注册的 CallBackFunction.onCallBack -> responseId 为空 -> handler.handler(m.getData(), responseFunction)-> 外部回调( function.onCallBack(“native submitFromWeb 方法, 返回 data”);)-> queueMessage -> dispatchMessage ->loadUrl(javascriptCommand) -> 回调结束

注意两端的每一次通讯,Android 端的 shouldOverrideUrlLoading 方法都会执行两次,但是携带url不同,这里要注意两次访问是相互配合的,没有第一次的消息查询,也就不会有第二次的数据返回回调。

4、小结

通过url拦截方式,注入一个本地js文件,来桥接native和web,屏蔽了一些通性工作,在使用上方式相同,好理解,通过两次来回调用实现了可回调function。

github 上也有很多类似的方案,这里就不一一分析了,如果你不是看的这个库,建议好好看看,挺巧妙的机制。

这块逻辑也是看了好久,挺绕的,可以结合打log和debug来分析。

三:DSBridge-Android

1、使用方法

注意Android 和 Web 使用方式

webView.callHandler("addValue",new Object[]{1,"hello"},new CallBackFunction(){ @Override public void onCallBack(String retValue) { Log.d("jsbridge","call succeed,return value is "+retValue); } }); webView.callHandler("test",null); dsBridge.call("testNever", {msg: "testSyn"}); dsBridge.call("testNoArgAsyn", function (v) { alert(v); });

2、关键类作用:

  • DWebView:WebView的子类,提供了注册、调用 Handler等方法,之后都是通过 webview.callHandler()方式调用js.

  • CompletionHandler:作为native与web交互的通道,异步回调的关键操作类。

  • OnReturnValue:js 回调 android的接口,根据 handlerMap 储存的id作为 handler 标示即OnReturnValue实现类。

  • JsApi:需要注册到 Js java 类。

看完这些主要类的作用,在看流程就清晰了。

3、实现原理:

本质还是使用的系统默认 addJavascriptInterface 方式,其中做了扩展。

首先需要将这段js注入到 Web 页面

function getJsBridge() { window._dsf = window._dsf || {}; return { call: function(b, a, c) { "function" == typeof a && (c = a, a = {}); if ("function" == typeof c) { window.dscb = window.dscb || 0; var d = "dscb" + window.dscb++; window[d] = c; a._dscbstub = d } a = JSON.stringify(a || {}); return window._dswk ? prompt(window._dswk + b, a) : "function" == typeof _dsbridge ? _dsbridge(b, a) : _dsbridge.call(b, a) }, register: function(b, a) { "object" == typeof b ? Object.assign(window._dsf, b) : window._dsf[b] = a } } } dsBridge = getJsBridge();

native -> js:

1、webview.callHandler(method,args,handler);

2、拼接Js,并生成唯一的callID,将 handler保存到map中;

3、执行js,同时回调 java returnValue(),然后回调使用者调用。

js -> android:

1、dsBridge.call(method,json,function)-> native call(methodName,args);

2、通过反射来查找methodName对应的方法,并注册 CompletionHandler;

3、JsApi 通过 handler.complete() 方法,并拼装js方法和参数,再次调用js函数实现回调操作,并删除上一次的 callback id。

4、小结:

这个方案比较简单,使用了系统的注入方式,虽然 4.2以下存在漏洞,但是它里面只能反射包含 @JavascriptInterface 注解的方法,所以和4.2以上注入是一样的,也是安全的。

相对案例一,案例二实现方式比较简单粗暴,也比较容易懂。

不过这个库在我的 4.2 设备上有 bug,下面函数执行时找不到 dsBridge 对象,导致 Native 调用 Js 失败。

dsBridge.register('addValue',function(l,r){ return l+r; })

可能和内核执行有关系,我这做了修复,放到 function 函数中执行就可以了,如果你也遇到,可以参考我的修复方法 DSBridge-Android


九、WebView 缓存原理分析和应用

这部分内容可以参考这两篇博文:

写的已经很清楚了,我这里就不赘述了。

主要包含一下内容:

1、WebView 的5中缓存类型,以及每个缓存类型工作原理、相同点和不同点、。

2、缓存在手机上的存储。

3、每种缓存机制案例。

如果你想通过过滤来减缓 WebView 请求网络,可以参考 rexxar-android 中关于拦截url操作读取本地操作。


十、性能、体验分析与优化

参考美团:WebView性能、体验分析与优化

这部分美团的技术博客已经写的很好了,不仅从性能、内存消耗、体验、安全几个维度,来系统的分析客户端默认 WebView 的问题,还给出了对应的优化方案。

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

跨平台开发:Flutter.png

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

-android]( ) 中关于拦截url操作读取本地操作。


十、性能、体验分析与优化

参考美团:WebView性能、体验分析与优化

这部分美团的技术博客已经写的很好了,不仅从性能、内存消耗、体验、安全几个维度,来系统的分析客户端默认 WebView 的问题,还给出了对应的优化方案。

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

[外链图片转存中…(img-xH7hWaIz-1714299523225)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 12
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值