赞
踩
// 获取指定命令的实例
val command = CommandHelper.INSTANCE.getCommand<CommandLogin>("login")
// 注册监听,必须在WebView启动前设置监听
command?.registerCommandMonitor(object : ICommandMonitor {
override fun onMonitor(parameters: Map<*, *>, callback: ICallbackFromMainToWebInterface) {
Toast.makeText(App.INSTANCE, "登录中...", Toast.LENGTH_LONG).show()
}
})
几乎所有的App都会用到WebView组件,用WebView承载业务功能也是一种选择,毕竟不用等待应用市场的审核,提升业务上线与bug修复的及时性,但WebView加载业务功能,也有很大的缺陷,体验不好(主要体现在加载、交互上)、耗内存;耗内存的问题这里提供多进程的设计方案,让WebView在单独的一个进程中运行,这样做的好处是分担主进程的内存压力,另外WebView进程发生崩溃了,也不会影响到主进程的正常运行。
WebView跨进程通信是本章的核心,另外也基于腾讯X5服务进行了二次封装,先简单总结下这个库的特点:
本章涉及的知识点:
在很多App应用上都会用到多进程,看看我们最熟悉微信,是怎样的
adb shell “ps |grep keyword”
keyword:是app的包名
com.tencent.mm:appbrand :小程序进程,微信每启动一个小程序,都会建立一个独立的进程 appbrand[x], 最多开5个进程。
微信将小程序放在独立的进程,其实是向系统再申请一块独立内存来使用,有效突破默认下的内存限制。同时在独立进程中运行,即使小程序Cash了,也不会影响主进程的正常运行,毕竟小程序是开放的,每个开发者写出代码质量是参差不齐的;放在独立进程是很有必要的。
同样地,WebView也是一样的,不同版本的内核可能会不一样,手机厂商可能也会修改定制自己的WebView,各种兼容性问题,每个人写的WebView代码质量都有不同,对内存管理认知程度也不一样,如果因为WebView的Crash影响体验是得不偿失的,因此把WebView放在独立进程也是必要的。
和启动普通Activity是一样的,如下
Intent intent = new Intent(this, WebViewActivity.class);
intent.putExtra(Constants.URL, url);
intent.putExtra(Constants.TITLE, title);
intent.putExtra(Constants.JS_OBJECT_NAME,"hYi");
startActivity(intent);
如果需要与Js进行交互的话,必须携带Constants.JS_OBJECT_NAME的参数。如果定制需要可以继承WebViewActivity和WebViewFragment进行修改。
如果不想使用默认的WebViewActivity,可以直接使用封装过WebView,如下:
mWebView = WebViewManager.newBuilder(this)
.setRootView(webViewContainer)
.injectedJsObject(mJsName)
.setWebUrl(mUrl)
.setWebViewCallback(this)
.build()
.start()
其中setViewContainer()是传入WebView的父容器。另外如果此时需要多进程的话,必须在你定制的Activity中声明process属性,如下:
<activity android:name=".ui.WebViewActivity" android:process=":webview" /> // 多进程多任务效果 <activity android:name=".ui.WebViewActivity$Small1" android:process=":app_small1" android:launchMode="singleTask" android:taskAffinity=".small1" /> <activity android:name=".ui.WebViewActivity$Small2" android:process=":app_small2" android:launchMode="singleTask" android:taskAffinity=".small2" /> <activity android:name=".ui.WebViewActivity$Small3" android:process=":app_small3" android:launchMode="singleTask" android:taskAffinity=".small3" /> <activity android:name=".ui.WebViewActivity$Small4" android:process=":app_small4" android:launchMode="singleTask" android:taskAffinity=".small4" />
关于Js与原生交互的功能实现,下面有讲。
整个架构的流程图:
核心类
如果在Js中发送命令后,即调用原生函数,需要实现对应的功能,实现Command接口即可,如下:
@AutoService(Command::class)
class CommandShowToast : BaseCommand() {
override fun commandName(): String {
return "showToast"
}
override fun execCommand(parameters: Map<*, *>, callback: ICallbackFromMainToWebInterface) {
LogUtils.d("executeCommand curr thread "+ Thread.currentThread().name)
Toast.makeText(App.INSTANCE, parameters["message"].toString(), Toast.LENGTH_SHORT).show()
}
}
在实现类中,需要添加类注解@AutoService(Command::class)
,目的是确保MainCommandManager能够找到并注册。
在executeCommand()方法中实现功能,其中commandName()方法是定义命令名称,需要与Js发送出来是对应一致的,否则executeCommand()方法是无法触发的,即Js调用原生函数会失败。如下
这需要与前端协调商量好,推荐前端直接使用 https://github.com/751496032/hYi-jssdk ,封装Js与Native交互函数,适配了Android与iOS系统,模仿微信公众号jssdk,前端可以通过CDN方式引用Js文件
function callAppToast(){
console.log("callAppToast.");
window.global.takeNativeAction("showToast", {message: "来自html的消息"});
}
global.takeNativeAction = function(commandName, parameters){
console.log("global takeNativeAction")
var request = {};
request.name = commandName;
request.param = parameters;
window.xxwebview.takeNativeAction(JSON.stringify(request));
}
Js发送携带的参数,可以在executeCommand()方法中parameters中取出。
其实整个流程就是基于命令模式来实现的,是这个方案的核心。开发者只需关注本身业务的实现,中间的流程无需去关心。
关于标准的命令模式可参考 > 标准的命令模式
对于WebView的性能,给人最直观的莫过于:打开速度比native慢。
是的,当我们打开一个WebView页面,页面往往会慢吞吞的loading很久,若干秒后才出现你所需要看到的页面。
对于一个普通用户来讲,打开一个WebView通常会经历以下几个阶段:
如果从程序上观察,WebView启动过程大概分为以下几个阶段:
在客户端能处理的更多是在第一阶段,WebView初始化阶段,这里的预加载就是将WebView提前初始化完成。WebViewPool
public X5WebView getX5WebView(Context context) {
// 使用栈顶的
X5WebView webView = mWebViewStackCached.pop();
if (webView == null) {
prepare(context);
webView = mWebViewStackCached.pop();
}
// WebView不为空,则开始使用预创建的WebView,并且替换Context
MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
contextWrapper.setBaseContext(context.getApplicationContext());
prepare(context);
return webView;
}
离线包方案,可以在闲时先把H5资源静默预下载到本地,然后需要的时候再去从本地加载H5,这种方案是可以缩短白屏的时间,对优化很有帮助,不过实现的难度就有些大了,需要前后端以及App端同时配合完成。
参考 https://github.com/al-liu/OCat-MobilePlatform
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。