關於H5框架的一些知識

關於H5框架的一些知識

入職新公司之後,從事了部分H5框架的工做,包括性能優化,新功能開發等。在這以前從沒有接觸過H5,因此接觸之後,就想弄明白底層的一些原理。javascript

  • 1 native層和H5層怎麼交互?
  • 2 Android H5底層的實現是怎麼樣的?
  • 3 chrome底層源碼是什麼樣的,結構複雜嗎?有哪些原理?
  • 4 目前大廠中,在用的H5框架是什麼樣的?

1 native層和H5層怎麼交互?

在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();
    }
});
複製代碼

2 Android H5底層的實現是怎麼樣的?

上面簡單介紹了一下H5中JS和java層的一個交互,api接口實際上是比較簡單的,可是Android系統底層的實現是什麼樣子的呢?你們必定用過chrome瀏覽器吧?chrome瀏覽器是基於google的chromium開源項目 www.chromium.org/ 項目,Android webview底層其實也是基於chromium來實現的。ios

webview的初始化

大體流程,在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);
}
複製代碼

3 chrome底層源碼是什麼樣的,結構複雜嗎?有哪些原理?

參考網上的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"... 複製代碼

4 目前大廠中,在用的H5框架是什麼樣的?

爲何要用H5框架呢?所謂的框架無非就是通用功能的一個組合。大廠的App中,有幾百個H5界面,若是每一個H5界面和java代碼的交互方式都不同,再加上Android和ios兩端,估計開發人員腦殼會爆炸,因此在此情境下,前端開發人員須要和客戶端人員肯定好一套協議,基於這套協議,咱們就能夠開發出一套H5的框架。同時H5框架須要具有加載離線包,api預加載包,WebView池,cookie注入檢查,UI操做,同層渲染等等太多的功能。顯然每一個H5框架不會簡單。

相關文章
相關標籤/搜索