入职新公司之后,从事了部分H5框架的工做,包括性能优化,新功能开发等。在这以前从没有接触过H5,因此接触之后,就想弄明白底层的一些原理。javascript
在Android的SDK中,有一个WebView的类,这个类是一个网页界面的控件类。它有个方法是html
/** * @param object the Java object to inject into this WebView's JavaScript context. {@code null} values are ignored. * @param name the name used to expose the object in JavaScript */
public void addJavascriptInterface(Object object, String name) {
checkThread();
mProvider.addJavascriptInterface(object, name);
}
复制代码
addJavascriptInterface字面意思在java中添加js接口,也就是说,能够从js中调用这个的java层的接口。具体接口实现是什么样子呢?前端
testWeb.addJavascriptInterface(this, "android");
@JavascriptInterface
public String back() {
Log.i("jin", "我是JAVA中的back方法,你们好");
return "123";
}
复制代码
对应的从JS代码中怎么调用呢?注意addJavascriptInterface这个方法中的两个参数,第一个就是注入到JS上下文的对象,第二个参数就是在JS中可使用的对象的名字。因此我看看JS代码中的使用是什么样子的呢?java
<script type="text/javascript">
function sum(a,b){
console.log("sum sum sum");
return a+b;
}
function alertMessage(message){
alert(message);
}
function show(){
document.getElementById("p").innerHTML="hello,damo";
}
function s(){
alert("123");
console.log("test");
var result = window.android.back();
document.getElementById("p").innerHTML=result;
}
</script>
复制代码
注意是直接调用的window.android.back(),这样JS层就能够调用java层的方法,那么java层如何调用native层呢?只需一个方法便可:android
testWeb.evaluateJavascript("sum(5,20)", new
ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Toast.makeText(MainActivity.this, "JS返回告终果, :" + value, Toast.LENGTH_SHORT).
show();
}
});
复制代码
上面简单介绍了一下H5中JS和java层的一个交互,api接口实际上是比较简单的,可是Android系统底层的实现是什么样子的呢?你们必定用过chrome浏览器吧?chrome浏览器是基于google的chromium开源项目 www.chromium.org/ 项目,Android webview底层其实也是基于chromium来实现的。ios
大体流程,在MainActivity setContentView时候,会去加载页面,最后调用WebView的初始化,WebView初始化流程比较重,有个重要方法是 c++
private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
// As this can get called during the base class constructor chain, pass the minimum
// number of dependencies here; the rest are deferred to init().
mProvider = getFactory().createWebView(this, new PrivateAccess());
}
}
复制代码
会去先getFactory(),再去执行createWebView,getFactory是经过WebViewFactory.getProvider建立web
static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
// For now the main purpose of this function (and the factory abstraction) is to keep
// us honest and minimize usage of WebView internals when binding the proxy.
if (sProviderInstance != null) {
return sProviderInstance;
}
final int uid = android.os.Process.myUid();
if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
|| uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
|| uid == android.os.Process.BLUETOOTH_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
}
if (!isWebViewSupported()) {
// Device doesn't support WebView; don't try to load it, just throw.
throw new UnsupportedOperationException();
}
if (sWebViewDisabled) {
throw new IllegalStateException(
"WebView.disableWebView() was called: WebView is disabled");
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
Class<WebViewFactoryProvider> providerClass = getProviderClass();
Method staticFactory = null;
try {
staticFactory = providerClass.getMethod(
CHROMIUM_WEBVIEW_FACTORY_METHOD, WebViewDelegate.class);
} catch (Exception e) {
if (DEBUG) {
Log.w(LOGTAG, "error instantiating provider with static factory method", e);
}
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactoryProvider invocation");
try {
sProviderInstance = (WebViewFactoryProvider)
staticFactory.invoke(null, new WebViewDelegate());
if (DEBUG) {
Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
}
return sProviderInstance;
} catch (Exception e) {
Log.e(LOGTAG, "error instantiating provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
}
}
复制代码
里面有个关键方法是getProviderClass,这个方法是这样:chrome
private static Class<WebViewFactoryProvider> getProviderClass() {
Context webViewContext = null;
Application initialApplication = AppGlobals.getInitialApplication();
try {
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
"WebViewFactory.getWebViewContextAndSetProvider()");
try {
webViewContext = getWebViewContextAndSetProvider();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
sPackageInfo.versionName + " (code " + sPackageInfo.getLongVersionCode() + ")");
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
initialApplication.getAssets().addAssetPathAsSharedLibrary(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
getWebViewLibrary(sPackageInfo.applicationInfo));
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
try {
return getWebViewProviderClass(clazzLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (ClassNotFoundException e) {
Log.e(LOGTAG, "error loading provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (MissingWebViewPackageException e) {
Log.e(LOGTAG, "Chromium WebView package does not exist", e);
throw new AndroidRuntimeException(e);
}
}
复制代码
无非就是经过addAssetPathAsSharedLibrary去加载webview的资源,而后去loadNativeLibrary,回到上面经过反射构造好WebViewFactoryProvider,这个WebViewFactoryProvider就是WebViewChromiumFactoryProvider 这个代码在Android系统库里没有,是Chromium的源码,在Android里是以apk形式存在了,也就是刚才addAssetPathAsSharedLibrary去加载webview的资源, 在个人小米手机Android10.0 上加载的APK名字是 WebViewGoogle.apk就是webview实现的apk,下边那个是啥?暂时无论他 ApkAssets{path=/system/product/app/WebViewGoogle/WebViewGoogle.apk} ApkAssets{path=/system/product/app/TrichromeLibrary/TrichromeLibrary.apk}小程序
回到WebView中 ,mProvider = getFactory().createWebView(this, new PrivateAccess()); 这个mProvider就是 WebViewChromiumFactoryProvider,而后执行createWebView,最后获得的就是WebViewChromium类
public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
return new WebViewChromium(this, webView, privateAccess, mShouldDisableThreadChecking);
}
复制代码
参考网上的chromium源码下载,编译,顺利完成了chrome的编译,可是在个人mac上会crash掉,不过暂时不影响。看看chromium的源码目录,操做系统级别的目录吧,有点相似Android源码的感受(记得刚毕业接触Android源码,强哥曾经给我发过一个文档记录每一个Android目录的做用哈),
android_webview,这个前面说过,就是Android系统WebView的底层实现 chrome,chrome的源码 chromeos,chromeos操做系统的源码 fuchsia,fuchsi操做系统的源码 gpu,gpu相关的一些代码 ipc,进程间通讯,chrome是一个多进程的架构 v8,著名的JS的v8引擎 pdf,chrome中pdf插件。 cronet网络库,目前不少大厂,双端网络库底层都是cronet实现的。 等等等等。 总体代码结构,代码量都太庞大,有时间慢慢看吧,chrome的入口在chrome_main.cc中
#define DLLEXPORT __declspec(dllexport)
// We use extern C for the prototype DLLEXPORT to avoid C++ name mangling.
extern "C" {
DLLEXPORT int __cdecl ChromeMain(HINSTANCE instance, sandbox::SandboxInterfaceInfo* sandbox_info, int64_t exe_entry_point_ticks);
}
#elif defined(OS_POSIX)
extern "C" {
__attribute__((visibility("default")))
int ChromeMain(int argc, const char** argv);
}
#endif
#if defined(OS_WIN)
DLLEXPORT int __cdecl ChromeMain(HINSTANCE instance, sandbox::SandboxInterfaceInfo* sandbox_info, int64_t exe_entry_point_ticks) {
#elif defined(OS_POSIX)
int ChromeMain(int argc, const char** argv) {
int64_t exe_entry_point_ticks = 0;
#endif
#if defined(OS_WIN)
install_static::InitializeFromPrimaryModule();
#endif
ChromeMainDelegate chrome_main_delegate( base::TimeTicks::FromInternalValue(exe_entry_point_ticks));
content::ContentMainParams params(&chrome_main_delegate);
#if defined(OS_WIN)
// The process should crash when going through abnormal termination, but we
// must be sure to reset this setting when ChromeMain returns normally.
auto crash_on_detach_resetter = base::ScopedClosureRunner(
base::BindOnce(&base::win::SetShouldCrashOnProcessDetach,
base::win::ShouldCrashOnProcessDetach()));
base::win::SetShouldCrashOnProcessDetach(true);
base::win::SetAbortBehaviorForCrashReporting();
params.instance = instance;
params.sandbox_info = sandbox_info;
// Pass chrome_elf's copy of DumpProcessWithoutCrash resolved via load-time
// dynamic linking.
base::debug::SetDumpWithoutCrashingFunction(&DumpProcessWithoutCrash);
// Verify that chrome_elf and this module (chrome.dll and chrome_child.dll)
// have the same version.
if (install_static::InstallDetails::Get().VersionMismatch())
base::debug::DumpWithoutCrashing();
#else
params.argc = argc;
params.argv = argv;
base::CommandLine::Init(params.argc, params.argv);
#endif // defined(OS_WIN)
base::CommandLine::Init(0, nullptr);
const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
ALLOW_UNUSED_LOCAL(command_line);
#if defined(OS_MACOSX)
SetUpBundleOverrides();
#endif
// Start the sampling profiler as early as possible - namely, once the command
// line data is available. Allocated as an object on the stack to ensure that
// the destructor runs on shutdown, which is important to avoid the profiler
// thread's destruction racing with main thread destruction.
MainThreadStackSamplingProfiler scoped_sampling_profiler;
// Chrome-specific process modes.
#if defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_WIN)
if (command_line->HasSwitch(switches::kHeadless)) {
return headless::HeadlessShellMain(params);
}
#endif // defined(OS_LINUX) || defined(OS_MACOSX) || defined(OS_WIN)
int rv = content::ContentMain(params);
return rv;
}
复制代码
具体学习教程,参考老罗的Android之旅吧,从中仍是能学习到很多东西的blog.csdn.net/Luoshengyan… 这里想讲下微信小程序中的WebView同层渲染的原理在Android中的实现。所谓的同层渲染,是指java的view和webview在一层渲染,具体优势我不细说了。微信是修改的chrome的内核,扩充了chrome的plugin机制,好比在chrome中支持的pdf的plugin,embed标签,id是plugin。
<embed id="plugin" type="application/x-google-chrome-pdf" src="https://arxiv.org/pdf/1406.2661.pdf"... 复制代码
为何要用H5框架呢?所谓的框架无非就是通用功能的一个组合。大厂的App中,有几百个H5界面,若是每一个H5界面和java代码的交互方式都不同,再加上Android和ios两端,估计开发人员脑壳会爆炸,因此在此情境下,前端开发人员须要和客户端人员肯定好一套协议,基于这套协议,咱们就能够开发出一套H5的框架。同时H5框架须要具有加载离线包,api预加载包,WebView池,cookie注入检查,UI操做,同层渲染等等太多的功能。显然每一个H5框架不会简单。