Web前端最全H5与原生混合开发总结(2),Web前端进阶面试资料无偿分享

React

  • 介绍一下react

  • React单项数据流

  • react生命周期函数和react组件的生命周期

  • react和Vue的原理,区别,亮点,作用

  • reactJs的组件交流

  • 有了解过react的虚拟DOM吗,虚拟DOM是怎么对比的呢

  • 项目里用到了react,为什么要选择react,react有哪些好处

  • 怎么获取真正的dom

  • 选择react的原因

  • react的生命周期函数

  • setState之后的流程

  • react高阶组件知道吗?

  • React的jsx,函数式编程

  • react的组件是通过什么去判断是否刷新的

  • 如何配置React-Router

  • 路由的动态加载模块

  • Redux中间件是什么东西,接受几个参数

  • redux请求中间件如何处理并发

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • Update:

*/

public final class JsExecutor {

private static final String TAG = “JsExecutor”;

private JsExecutor() {

}

/**

  • JS方法不带参,且无返回值时用此方法

  • @param webView

  • @param jsCode

*/

public static void executeJsRaw(@NonNull WebView webView, @NonNull String jsCode) {

executeJsRaw(webView, jsCode, null);

}

/**

  • JS方法带参,且有返回值时用此方法

  • @param webView

  • @param jsCode

  • @param callback

*/

public static void executeJsRaw(@NonNull WebView webView, @NonNull String jsCode, @Nullable ValueCallback callback) {

if (Build.VERSION.SDK_INT >= 19) {

webView.evaluateJavascript(jsCode, callback);

} else {

// 注意这里,这种方式没有直接的结果回调,不过可以迂回解决,比如我们可以

// 执行JS的一个固定的方法,并传入类型参数,然后在JS方法中根据这个类型参

// 数去匹配方法并执行,执行完成后再调用我们注入的相应回调方法将结果传回

// 来,这样就可以解决结果回调问题了,如果要适配 Android 4.4 以下的版本则可以这么做。

webView.loadUrl(jsCode);

}

}

/**

  • JS方法带参,且有返回值时用此方法

  • @param webView

  • @param methodName

  • @param callback

  • @param params

*/

public static void executeJs(@NonNull WebView webView, @NonNull CharSequence methodName, @Nullable ValueCallback callback, @NonNull CharSequence… params) {

StringBuilder sb = new StringBuilder();

sb.append(“javascript:”)

.append(methodName)

.append(“(”);

if (params != null && params.length > 0) {

for (int i = 0; i < params.length; i++) {

sb.append(“”")

.append(params[i])

.append(“”");

if (i < params.length - 1)

sb.append(“,”);

}

}

sb.append(“);”);

Log.i(TAG, "executeJs: " + sb);

executeJsRaw(webView, sb.toString(), callback);

}

/**

  • JS方法带参,且无返回值时用此方法

  • @param webView

  • @param methodName

  • @param params

*/

public static void executeJs(@NonNull WebView webView, @NonNull CharSequence methodName, @NonNull CharSequence… params) {

executeJs(webView, methodName, null, params);

}

}

这里直接将WebView视为我们执行JS代码的工具,如下示例是给H5传递当前网络类型,由于整合了JS代码的拼接过程,因此只需要传入具体方法名称和方法的字符串参数即可。

JsExecutor.executeJs(webView, “onNetStatusChanged”, netType);

  • 对于JsInterfaces(JS调用Android) , 我们需要在我们需要注入的方法前加上注解@JavascriptInterface才能将方法暴露出去,然后将包含此方法的类对象注入进去,如下一个实际场景, H5需要从Android原生中获取用户的账号信息,那么可以这么写:

先注入包含对应方法的H5JsStorage类对象:

H5JsStorage h5JsStorage = new H5JsStorage(this, mUser); webView.addJavascriptInterface(h5JsStorage, "h5JsStorage");

其中getUserAccountInfo的声明如下:

public class H5JsStorage implements IH5JsStorage { // ... @JavascriptInterface public String getUserAccountInfo(){ return String.format("{\"userAccount\":\"%s\", \"password\":\"%s\", \"userIncrId\":\"%s\", \"orgId\":\"%s\"}", mUser.getUserAccount(), mUser.getPassword(), mUser.getUserIncrId(), mUser.getOrgId()); } // ... }

以上便是H5与原生交互的交互过程,具体代码在文章末尾会给出GitHub地址。

三、WebView 启动速度优化、多模块包自动更新

1. WebView启动速度优化

我们先来做个实验,测试一下包含WebViewActivity在优化前后的启动速度,可以这么做:根据Activity的生命周期,在onCreate的第一行处记录下初始时间,在onStart最后一行记录下结束时间,然后计算时间差,作为衡量启动速度的参照,多次测试,记录时间差。结果如下:

//--------------------------------------------- // 不做任何处理, Mi6 android 8.0 I/Main2Activity: onStart: total cost:150 ms I/Main2Activity: onStart: total cost:44 ms I/Main2Activity: onStart: total cost:33 ms I/Main2Activity: onStart: total cost:54 ms I/Main2Activity: onStart: total cost:35 ms I/Main2Activity: onStart: total cost:34 ms I/Main2Activity: onStart: total cost:34 ms // 优化后 初始化耗时 I/MyWebViewHolder: prepareWebView: total cost: 131 ms I/MyWebViewHolder: prepareWebView: total cost: 121 ms I/MyWebViewHolder: prepareWebView: total cost: 121 ms I/MyWebViewHolder: prepareWebView: total cost: 117 ms I/MyWebViewHolder: prepareWebView: total cost: 110 ms I/MyWebViewHolder: prepareWebView: total cost: 116 ms I/MyWebViewHolder: prepareWebView: total cost: 116 ms // 之后耗时 I/Main2Activity: onStart: total cost:26 ms I/Main2Activity: onStart: total cost:20 ms I/Main2Activity: onStart: total cost:22 ms I/Main2Activity: onStart: total cost:17 ms I/Main2Activity: onStart: total cost:19 ms I/Main2Activity: onStart: total cost:21 ms //--------------------------------------------- // 模拟器 android 9.0 I/Main2Activity: onStart: total cost:292 ms I/Main2Activity: onStart: total cost:50 ms I/Main2Activity: onStart: total cost:49 ms I/Main2Activity: onStart: total cost:54 ms I/Main2Activity: onStart: total cost:43 ms I/Main2Activity: onStart: total cost:47 ms I/Main2Activity: onStart: total cost:39 ms I/Main2Activity: onStart: total cost:41 ms // 优化后 初始化耗时 I/MyWebViewHolder: prepareWebView: total cost: 177 ms I/MyWebViewHolder: prepareWebView: total cost: 169 ms I/MyWebViewHolder: prepareWebView: total cost: 183 ms I/MyWebViewHolder: prepareWebView: total cost: 159 ms // 之后 耗时 I/Main2Activity: onStart: total cost:40 ms I/Main2Activity: onStart: total cost:27 ms I/Main2Activity: onStart: total cost:34 ms I/Main2Activity: onStart: total cost:34 ms I/Main2Activity: onStart: total cost:33 ms I/Main2Activity: onStart: total cost:30 ms //--------------------------------------------- // MT6592 android 4.4 不做处理 I/Main2Activity: onStart: total cost:141 ms I/Main2Activity: onStart: total cost:46 ms I/Main2Activity: onStart: total cost:43 ms I/Main2Activity: onStart: total cost:42 ms I/Main2Activity: onStart: total cost:44 ms I/Main2Activity: onStart: total cost:46 ms // 优化后 初始化耗时 I/MyWebViewHolder: prepareWebView: total cost: 182 ms I/MyWebViewHolder: prepareWebView: total cost: 50 ms I/MyWebViewHolder: prepareWebView: total cost: 54 ms I/MyWebViewHolder: prepareWebView: total cost: 53 ms I/MyWebViewHolder: prepareWebView: total cost: 54 ms I/MyWebViewHolder: prepareWebView: total cost: 56 ms // 之后耗时 I/Main2Activity: onStart: total cost:36 ms I/Main2Activity: onStart: total cost:34 ms I/Main2Activity: onStart: total cost:30 ms I/Main2Activity: onStart: total cost:31 ms I/Main2Activity: onStart: total cost:32 ms

根据以上结果可以看出,优化后要比优化前的启动速度快个10~20秒,且抖动较小。可以注意到其中包含一个叫做prepareWebView的时间差,据此,聪明的你肯定能想到我所谓的优化是做了什么操作。嗯~,其实就是使用WebView之前,在合适的地方和时机先将其初始化,之后复用这个创建好的实例,这里我是这么写的:

/** * @Author horseLai * CreatedAt 2018/12/10 10:11 * Desc: 用于持有MyWebView实例,减少每次都重新创建和销毁造成的开销 * Update: */ public final class MyWebViewHolder { private static final String TAG = "MyWebViewHolder"; private MyWebView mWebView; private static MyWebViewHolder sMyWebViewHolder; private View pageNoneNet; private boolean mShouldClearHistory = false; public boolean shouldClearHistory() { return mShouldClearHistory; } public void shouldClearHistory(boolean shouldClearHistory) { this.mShouldClearHistory = shouldClearHistory; } private MyWebViewHolder() { } public static MyWebViewHolder getHolder() { if (sMyWebViewHolder != null) return sMyWebViewHolder; synchronized (MyWebViewHolder.class) { if (sMyWebViewHolder == null) { sMyWebViewHolder = new MyWebViewHolder(); } } return sMyWebViewHolder; } /** * 务必在使用WebView前调用此方法进行初始化 * * @param context */ public void prepareWebView(Context context) { long start = System.currentTimeMillis(); if (mWebView != null) return; synchronized (this) { if (mWebView == null) { mWebView = new MyWebView(context); } } Log.i(TAG, "prepareWebView: total cost: " + (System.currentTimeMillis() - start) + " ms"); Log.d(TAG, "prepare MyWebView OK..."); } public MyWebView getMyWebView() { return mWebView; } public void detach() { if (mWebView != null) { Log.d(TAG, "detach MyWebView, but not destroy..."); ((ViewGroup) mWebView.getParent()).removeView(mWebView); mWebView.removeAllViews(); mWebView.clearAnimation(); mWebView.clearFormData(); // mWebView.clearHistory(); mShouldClearHistory = true; mWebView.getSettings().setJavaScriptEnabled(false); } } public void attach(ViewGroup parent, int index) { if (mWebView != null) { Log.d(TAG, "attach MyWebView, index of ViewGroup is " + index); WebSettings settings = mWebView.getSettings(); // 不加此配置会无法加载显示界面 settings.setDomStorageEnabled(true); settings.setSupportZoom(false); settings.setJavaScriptEnabled(true); settings.setUseWideViewPort(true); mWebView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mWebView.setVerticalScrollBarEnabled(false); mWebView.setHorizontalScrollBarEnabled(false); // 在WebView上层覆盖一个用于提示如错误等信息的布局层, FrameLayout frameLayout = new FrameLayout(parent.getContext()); frameLayout.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); frameLayout.addView(mWebView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); pageNoneNet = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_null_net, frameLayout, false); frameLayout.addView(pageNoneNet); pageNoneNet.setVisibility(View.GONE); pageNoneNet.findViewById(R.id.btn_try).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { pageNoneNet.setVisibility(View.GONE); mWebView.reload(); } }); parent.addView(frameLayout, index); } } public void showNoneNetPage() { if (pageNoneNet != null) pageNoneNet.setVisibility(View.VISIBLE); } public void hideNoneNetPage() { if (pageNoneNet != null) pageNoneNet.setVisibility(View.GONE); } public void attach(ViewGroup parent) { attach(parent, parent.getChildCount()); } public void destroy() { if (mWebView != null) { Log.d(TAG,"destroy MyWebView..."); mWebView.destroy(); } } public void pause() { if (mWebView != null) { Log.d(TAG,"pause MyWebView..."); mWebView.onPause(); } } public void resume() { if (mWebView != null) { Log.d(TAG,"resume MyWebView..."); mWebView.onResume(); } } public void removeJSInterfaces(String... names) { if (names == null || names.length == 0) return; for (String name : names) { Log.d(TAG,String.format("removeJSInterfaces:: %s ..", name)); mWebView.removeJavascriptInterface(name); } } }

然后在合适的地方初始化:

@Override protected void onCreate(Bundle savedInstanceState) { // ... MyWebViewHolder.getHolder().prepareWebView(this); }

添加到布局中:

LinearLayout parent= findViewById(R.id.parent); MyWebViewHolder.getHolder().attach(parent);

onDestroy时从界面中解除绑定:

@Override protected void onDestroy() { // ... MyWebViewHolder.getHolder().detach(); }

2. 多模块包自动更新

支持多模块自动更新的目的是方便更新维护,减少用户升级所带来的流量开支,每个模块包之间可以是相互独立的,也方便于团队开发,仅需要和前端约定好文件目录即可。

先来看看H5模块的自动更新流程(完整更新):

在这里插入图片描述

上面是模块包的完整更新过程,还可以进行补丁更新,而所谓补丁更新就是,下载的更新包中仅仅包含需要更新的文件,因而对应于上面流程而言,就是少了删除本地旧版本文件的过程,而直接解压替换对应文件。这种更新方式有以下优缺点:

  1. 可以极大的减少更新时对用户的流量消耗,且速度极快。

  2. 但是需要前端明确抽取所更新的文件,否则会出现问题,可能这个过程会繁琐点。

  3. 如果使用类似于VueJs这种模板框架编写的界面,因为需要编译为JS代码,然后仅剩一个index.html入口,导致抽取定位繁琐,且每次编译出来的文件名可能不一样,因此不能使用补丁更新这种方式,只能分包,然后进行完整更新。

具体代码比较多,就补贴了,请看 github这里, 其中H5ManagerSettingsH5Manager配置信息与无关逻辑的抽离类。

四、建立公用工具集

上面已经逐个介绍了混合开发中交互与更新的逻辑,工具集已经放到 githubH5MixDevelopTools,感兴趣的童鞋可以看看,虽然这里我并没有把JS接口和html界面放上去。

在这里插入图片描述

遇到的实际问题与解决办法:(以项目中使用VueJs作为模板引擎来编写H5页面为例)

1. 界面加载不出来,显示空白,怎么办?

解决办法:给WebView加上下面配置即可

mWebView.getSettings().setDomStorageEnabled(true);

2. 联调时发现总是找不到定义的交互接口方法,怎么办?

原因与解决办法:首先,默认情况下,VueJs在对代码进行混淆处理,因此如果你遇到了这个问题,那么请手动配置以关闭混淆(具体做法请自行查找吧)。如果已经不混淆了,但是依然找不到对应的方法,怎么办?我和我的小伙伴是将接口文件放到components中将其视作一个组件来使用的,然后具体到接口方法的话,将方法挂到window对象下,如下示例:

// 挂载方法

window.showToast = function(msg){

UI.showToast(msg);

}

分享

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值