入職新公司之後,從事了部分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框架不會簡單。