Webkit for Android分析
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
本文是在他人文章上針對android 4.0做了一些調整和補充,所有權歸原作者。
原文作者信息:
WebSite: http://www.jjos.org/
作者: 姜江 [email protected]
QQ: 457283
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
網上有許多webkit的分析文章,其中針對android porting的一篇文章WebKit – WebKit For Android,寫的非常好,分析得非常深入。不過這篇文章針對的Android版本比較老(具體版本無從考究),因此本文將在這篇文章的基礎上,加入android 4.0 webkit porting的一些內容。
一、Android WebKit簡介
Webkit是一個開源的瀏覽器排版和渲染引擎,包含WebCore和JavascriptCore。WebKit有衆多的實現(Qt、Gtk, windows, chromium, android, etc)。Android 4.0平臺的Web引擎框架採用了WebKit中的WebCore,javascript引擎則是採用google的V8引擎。Android 4.0的webkit採用了和chromium 12.0.742.130中webkit相同的codebase,webkit版本爲534.30。
二、Android WebKit模塊框架
Android平臺的WebKit上層由Java語言封裝,並且作爲API提供給Android應用開發者,而底層使用WebKit核心庫(WebCore)進行網頁排版。WebKit模塊分爲兩個部分: Java層和C層(webkit庫)。Java層和C層通過JNI相互調用,如圖1所示:
圖1 Android WebKit模塊框架
在webkit其它平臺的移植中,webkit層就是封裝WebCore,爲上層應用提供接口的。Android的平臺具有一定的特殊性,需要提供Java API接口,應用程序框架也是基於Java的,所以在Android的移植中,webkit層實際上被拆成兩部分,Java部分和C++部分,它們之間通過JNI接口進行通訊。JNI是一種雙向通訊機制,Java代碼可以調用C/C++代碼,C/C++代碼也可以調用Java代碼。
通常,WebCore中回調Java的代碼都位於WebKit(Android Implementation)層,但有一個例外,就是Source/WebCore/platform/android/GeolocationServiceBridge.cpp,該文件也包含回調到Java的代碼。
2.1 Java層框架
2.1.1 Java層源碼說明
Java層的代碼位於frameworks/base/core/java/android/webkit目錄下。各文件的簡單說明如下:
AccessibilityInjector.java | 爲WebView注入Accessibility |
BrowserFrame.java | 對WebCore中Frame對象的Java層封裝,用於創建WebCore中定義的Frame,以及爲該Frame對象提供Java層回調方法 |
CacheManager.java | Cache管理對象,負責Java層Cache對象管理 |
CallbackProxy.java | 該對象是用於處理WebCore與UI線程消息的代理類。當有Web事件產生時WebCore線程會調用該回調代理類,代理類會通過消息的方式通知UI線程,並且調用設置的客戶對象的回調函數。 |
CertTool.java | 證書工具 |
ClientCertRequestHandler.java | 處理客戶端證書請求 |
ConsoleMessage.java | 來自WebCore的Javascript控制檯消息 |
CookieManager.java | 根據RFC2109規範,管理cookies |
CookieSyncManager.java | Cookies同步管理對象,該對象負責同步RAM和Flash之間的Cookies數據。實際的物理數據操作在基類WebSyncManager中完成。 |
DateSorter.java | 日期排序 |
DebugFlags.java | 定義調試標誌 |
DeviceMotionAndroidOrientationManager.java | 用於實現DeviceMotion和DeviceOrientation |
DeviceMotionService.java | 實現SensorEventListener接口,處理動作 |
DeviceOrientationService.java | 實現SensorEventListener接口,處理方向變化 |
DownloadLister.java | 下載偵聽器接口 |
android 4.0 WebKit中不再使用 | |
FindActionModeCallback.java | ? |
FrameLoader.java | Frame載入器,用於載入網頁Frame數據 |
GeolocationPermission.java | 用於管理瀏覽器UI的位置信息權限 |
GeolocationService.java | 實現java側的GeolocationServiceAndroid |
HTML5Audio.java | HTML5 audio支持類 |
HTML5VideoFullScreen.java | 全屏視頻視圖,僅提供給瀏覽器使用 |
HTML5VideoInline.java | 內嵌視頻視圖,僅提供給瀏覽器使用 |
HTML5VideoView.java | 視頻視圖,僅提供給瀏覽器使用 |
HTML5VideoViewProxy.java | HTML5視頻視圖代理類 |
HttpAuthHandler.java | HTTP認證請求,需要用戶處理 |
HttpAuthHandlerImpl.java | HttpAuthHandler實現,僅用於Android Java HTTP stack |
JniUtil.java | 供JNI使用的實用類,用於獲取cache目錄等C代碼無法直接獲取的信息,以及讀取資源包中的文件等 |
JsPromptResult.java | Js結果提示對象,用於向用戶提示Javascript運行結果。 |
JsResult.java | Js結果對象,用於用戶交互 |
JWebCoreJavaBridge.java | 用Java與WebCore庫中Timer和Cookies對象交互的橋接代碼。 |
KeyStoreHandler.java | https相關處理 |
L10nUtils.java | 字符串國際化,在使用chrome http stack時用到 |
MimeTypeMap.java | MIME類型映射 |
MockGeolocation.java | 模擬地理位置信息 |
Network.java | 該對象封裝網絡連接邏輯,爲調用者提供更爲高級的網絡連接接口。 |
OverScrollGlow.java | 用於實現OverScroller效果 |
PerfChecker.java | 性能測試 |
Plugin.java | 插件處理相關 |
PluginData.java | 插件處理相關 |
PluginFullScreenHolder.java | 插件處理相關 |
PluginList.java | 插件處理相關 |
PluginManager.java | 插件處理相關 |
PluginStub.java | 插件處理相關 |
SearchBox.java | 定義搜索對話框接口 |
SearchBoxImpl.java | 搜索對話框接口實現 |
SelectActionModeCallback.java | ? |
SslCertLookupTable.java | https相關處理 |
SslClientCertLookupTable.java | https相關處理 |
SslErrorHandler.java | https相關處理 |
SslErrorHandlerImpl.java | https相關處理 |
URLUtil.java | URL處理實用類 |
ValueCallback.java | 回調接口,用於異步返回數據值 |
ViewManager.java | 子視圖管理類,主要用於管理插件視圖 |
ViewStateSerializer.java | WebView視圖序列化和反序列化 |
WebBackForwardList.java | 該對象包含WebView對象中顯示的歷史數據。 |
WebBackForwardListClient.java | 瀏覽歷史處理的客戶接口類,所有需要接收瀏覽歷史改變的類都需要實現該接口。 |
WebChromeClient.java | Chrome客戶基類,Chrome客戶對象在瀏覽器文檔標題、進度條、圖標改變時候會得到通知。 |
WebHistoryItem.java | 該對象用於保存一條網頁歷史數據 |
WebIconDatabase.java | 圖標數據庫管理對象,所有的WebView均請求相同的圖標數據庫對象 |
WebResourceResponse.java | 封裝某個資源的響應信息 |
WebSettings.java | WebView的管理設置數據,該對象數據是通過JNI接口從底層獲取。 |
WebStorage.java | 處理webstorage數據庫 |
WebSyncManager.java | 數據同步對象,用於RAM數據和FLASH數據的同步操作。 |
WebTextView.java | 在html文本輸入控件激活時,顯示系統原生編輯組件 |
WebView.java | Web視圖對象,用於基本的網頁數據載入、顯示等UI操作。 |
WebViewClient.java | Web視圖客戶對象,在Web視圖中有事件產生時,該對象可以獲得通知。 |
WebViewCore.java | 該對象對WebCore庫進行了封裝,將UI線程中的數據請求發送給WebCore處理,並且通過CallbackProxy的方式,通過消息通知UI線程數據處理的結果。 |
WebViewDatabase.java | 該對象使用SQLiteDatabase爲WebCore模塊提供數據存取操作。 |
WebViewFragment.java | 實現WebView嵌入到Fragment中 |
WebViewWorker.java | 實現html5 workers,在UI線程和webkit線程開啓單獨的線程 |
ZoomControlBase.java | 縮放控件接口 |
ZoomControlEmbedded.java | 內置縮放控件 |
ZoomControlExternal.java | 擴展縮放控件,已廢棄 |
ZoomManager.java | 維護WebView的縮放狀態 |
2.1.2 Java層主要類關係圖
WebKit Java層包含79個Java文件,主要的類關係圖如下:
1)WebView
WebView類是WebKit模塊Java層的視圖類,所有需要使用Web瀏覽功能的Android應用程序都要創建該視圖對象顯示和處理請求的網絡資源。目前,WebKit模塊支持HTTP、HTTPS、FTP以及javascript請求。WebView作爲應用程序的UI接口,爲用戶提供了一系列的網頁瀏覽、用戶交互接口,客戶程序通過這些接口訪問WebKit核心代碼。
2)WebViewDatabase
WebViewDatabase是WebKit模塊中針對SQLiteDatabase對象的封裝,用於存儲和獲取運行時瀏覽器保存的緩衝數據、歷史訪問數據、瀏覽器配置數據等。該對象是一個單實例對象,通過getInstance方法獲取WebViewDatabase的實例。WebViewDatabase是WebKit模塊中的內部對象,僅供WebKit框架內部使用。
3)WebViewCore
WebViewCore類是Java層與C層WebKit核心庫的交互類,客戶程序調用WebView的網頁瀏覽相關操作會轉發給BrowserFrame對象。當WebKit核心庫完成實際的數據分析和處理後會回調WebViweCore中定義的一系列JNI接口,這些接口會通過CallbackProxy將相關事件通知相應的UI對象。
4)CallbackProxy
CallbackProxy是一個代理類,用於UI線程和WebCore線程交互。該類定義了一系列與用戶相關的通知方法,當WebCore完成相應的數據處理,則會調用CallbackProxy類中對應的方法,這些方法通過消息方式間接調用相應處理對象的處理方法。
5)BrowserFrame
BrowserFrame類負責URL資源的載入、訪問歷史的維護、數據緩存等操作,該類會通過JNI接口直接與WebKit C層庫交互。
6)JWebCoreJavaBridge
該類爲Java層WebKit代碼提供與C層WebKit核心部分的Timer和Cookies操作相關的方法。
7)WebSettings
該對象描述了WEB瀏覽器訪問相關的用戶配置信息。
8)DownloadListener
下載偵聽接口,如果客戶代碼實現該接口,則在下載開始、失敗、掛起、完成等情況下,DownloadManagerCore對象會調用客戶代碼中實現的DwonloadListener方法。
9)WebBackForwardList
WebBackForwarList對象維護着用戶訪問歷史記錄,該類爲客戶程序提供操作訪問瀏覽器歷史數據的相關方法。
10)WebViewClient
WebViewClient類定義了一系列事件方法,如果Android應用程序設置了WebViewClient派生對象,則在頁面載入、資源載入、頁面訪問錯誤等情況發生時,該派生對象的相應方法會被調用。
11)WebBackForwardListClient
WebBackForwardListClient對象定義了對訪問歷史操作時可能產生的事件接口,當用戶實現了該接口,則在操作訪問歷史時(訪問歷史移除、訪問歷史清空等)用戶會得到通知。
12)WebChromeClient
WebChromeClient類定義了與瀏覽窗口修飾相關的事件。例如接收到Title、接收到Icon、進度變化時,WebChromeClient的相應方法會被調用。
2.1.3 流載入器(已廢棄)
在Android 4.0之前的版本,數據載入都是在Java層實現的,從4.0開始,Android webkit引入了chromium的部分代碼,輸入載入走的是C++代碼。不過原有的Java代碼仍然保留,可以在編譯webkit時用USE_CHROME_NETWORK_STACK宏進行切換。
2.2 C層框架
2.2.1 C類與Java類的關係
WebKit類一般被拆成兩個,Java類和C++類。比如在Java API部分,有一個WebView類,在C++部分,也有一個WebView類。WebViewCore, WebSettings等等也是同樣的。
需要注意的是,JNI是C語言接口,所以Java類並不能直接調用C++代碼,需要在C++代碼中export出C語言接口。所以代碼中使用了一個技巧,在Java類中定義一個int成員變量(實際上是一個指針),指向對應的C++類,如下圖所示:
1.BrowserFrame
與BrowserFrame Java類相對應的C++類爲WebFrame(文件名爲WebCoreFrameBridge.cpp),該類爲Dalvik虛擬機回調BrowserFrame類中定義的本地方法進行了封裝。與BrowserFrame中回調函數(Java層)相對應的C層結構定義如下:
struct WebFrame::JavaBrowserFrame
{
jweak mObj;
jweak mHistoryList; // WebBackForwardList object
jmethodID mStartLoadingResource;
jmethodID mMaybeSavePassword;
jmethodID mShouldInterceptRequest;
jmethodID mLoadStarted;
jmethodID mTransitionToCommitted;
jmethodID mLoadFinished;
jmethodID mReportError;
jmethodID mSetTitle;
jmethodID mWindowObjectCleared;
jmethodID mSetProgress;
jmethodID mDidReceiveIcon;
jmethodID mDidReceiveTouchIconUrl;
jmethodID mUpdateVisitedHistory;
jmethodID mHandleUrl;
jmethodID mCreateWindow;
jmethodID mCloseWindow;
jmethodID mDecidePolicyForFormResubmission;
jmethodID mRequestFocus;
jmethodID mGetRawResFilename;
jmethodID mDensity;
jmethodID mGetFileSize;
jmethodID mGetFile;
jmethodID mDidReceiveAuthenticationChallenge;
jmethodID mReportSslCertError;
jmethodID mRequestClientCert;
jmethodID mDownloadStart;
jmethodID mDidReceiveData;
jmethodID mDidFinishLoading;
jmethodID mSetCertificate;
jmethodID mShouldSaveFormData;
jmethodID mSaveFormData;
jmethodID mAutoLogin;
AutoJObject frame(JNIEnv* env) {
return getRealObject(env, mObj);
}
AutoJObject history(JNIEnv* env) {
return getRealObject(env, mHistoryList);
}
};
該結構作爲WebFrame(C層)的一個成員變量(mJavaFrame),在WebFrame構造函數中,用BrowserFrame(Java層)類的回調方法的method ID初始化JavaBrowserFrame結構的各個域。初始後,當WebCore(C層)在剖析網頁數據時,有Frame相關的資源改變,比如WEB頁面的主題變化,則會通過mJavaFrame結構,調用指定BrowserFrame對象的相應方法,通知Java層處理。
2.JWebCoreJavaBridge
與該對象相對應的C層對象爲JavaBridge,JavaBridge對象繼承了TimerClient, CookieClient, KeyGenerateorClient, FileSystemClient類,主要負責WebCore中的定時器和Cookie管理。與Java層JWebCoreJavaBridge類中方法method ID相關的是JavaBridege中幾個成員變量,在構造JavaBridge對象時,會初始化這些成員變量,之後有Timer或者Cookies事件產生,WebCore會通過這些ID值,回調對應JWebCoreJavaBridge的相應方法。
3.LoadListener
與該對象相關的C層對象爲WebCoreResourceLoader,與LoaderListener中回調函數(Java層)相對應的C層結構是struct resourceloader_t,該結構保存了LoadListener對象ID、CancelMethod ID以及DownloadFiledMethod ID等值。當有Cancel或者Download事件產生,WebCore會回調LoadListener類中的CancelMethod或者DownloadFileMethod。
4.WebViewCore
與WebViewCore相關的C類是WebViewCorel,定義了兩個數據結構,一個是WebViewCoreFields,對應於Java層WebViewCore對象的成員變量,另一個是WebViewCore::JavaGlue,對應於Java層WebViewCore對象的成員方法。定義如下:
// Field ids for WebViewCore
struct WebViewCoreFields {
jfieldID m_nativeClass;
jfieldID m_viewportWidth;
jfieldID m_viewportHeight;
jfieldID m_viewportInitialScale;
jfieldID m_viewportMinimumScale;
jfieldID m_viewportMaximumScale;
jfieldID m_viewportUserScalable;
jfieldID m_viewportDensityDpi;
jfieldID m_webView;
jfieldID m_drawIsPaused;
jfieldID m_lowMemoryUsageMb;
jfieldID m_highMemoryUsageMb;
jfieldID m_highUsageDeltaMb;
} gWebViewCoreFields;
// —————————————————————————-
struct WebViewCore::JavaGlue {
jweak m_obj;
jmethodID m_scrollTo;
jmethodID m_contentDraw;
jmethodID m_layersDraw;
jmethodID m_requestListBox;
jmethodID m_openFileChooser;
jmethodID m_requestSingleListBox;
jmethodID m_jsAlert;
jmethodID m_jsConfirm;
jmethodID m_jsPrompt;
jmethodID m_jsUnload;
jmethodID m_jsInterrupt;
jmethodID m_didFirstLayout;
jmethodID m_updateViewport;
jmethodID m_sendNotifyProgressFinished;
jmethodID m_sendViewInvalidate;
jmethodID m_updateTextfield;
jmethodID m_updateTextSelection;
jmethodID m_clearTextEntry;
jmethodID m_restoreScale;
jmethodID m_needTouchEvents;
jmethodID m_requestKeyboard;
jmethodID m_requestKeyboardWithSelection;
jmethodID m_exceededDatabaseQuota;
jmethodID m_reachedMaxAppCacheSize;
jmethodID m_populateVisitedLinks;
jmethodID m_geolocationPermissionsShowPrompt;
jmethodID m_geolocationPermissionsHidePrompt;
jmethodID m_getDeviceMotionService;
jmethodID m_getDeviceOrientationService;
jmethodID m_addMessageToConsole;
jmethodID m_formDidBlur;
jmethodID m_getPluginClass;
jmethodID m_showFullScreenPlugin;
jmethodID m_hideFullScreenPlugin;
jmethodID m_createSurface;
jmethodID m_addSurface;
jmethodID m_updateSurface;
jmethodID m_destroySurface;
jmethodID m_getContext;
jmethodID m_keepScreenOn;
jmethodID m_sendFindAgain;
jmethodID m_showRect;
jmethodID m_centerFitRect;
jmethodID m_setScrollbarModes;
jmethodID m_setInstallableWebApp;
jmethodID m_enterFullscreenForVideoLayer;
jmethodID m_setWebTextViewAutoFillable;
jmethodID m_selectAt;
AutoJObject object(JNIEnv* env) {
// We hold a weak reference to the Java WebViewCore to avoid memeory
// leaks due to circular references when WebView.destroy() is not
// called manually. The WebView and hence the WebViewCore could become
// weakly reachable at any time, after which the GC could null our weak
// reference, so we have to check the return value of this method at
// every use. Note that our weak reference will be nulled before the
// WebViewCore is finalized.
return getRealObject(env, m_obj);
}
};
WebViewCore類有個JavaGlue對象作爲成員變量,在構建WebViewCore對象時,用WebViewCore(Java層)中的方法ID值初始化該成員變量。並且會將構建的WebViewCore對象指針複製給WebViewCore(Java層)的mNativeClass,這樣將WebViewCore(Java層)和WebViewCore(C層)關聯起來。
5.WebSettings
與WebSettings相關的C層結構是struct FieldIds(文件名WebSettings.cpp),該結構保存了WebSettings類中定義的屬性ID以及方法ID,在構建FieldIds對象時,會設置這些方法和屬性的ID值。
6.WebView
與WebView相關的C層類是WebView,該類中的m_javaGlue中保存着WebView(Java層)中定義的屬性和方法ID,在WebView(C層)構造方法中初始化,並且將構造的WebView對象(C層)的指針,賦值給WebView類(Java層)的mNativeClass變量,這樣WebView(Java層)和WebView對象(C層)建立了關係。
三、基本流程分析
3.1 webkit初始化
Android提供了WebView類,該類提供客戶化瀏覽顯示的功能。如果客戶需要加入瀏覽器的支持,可像使用其它視圖類一樣加入應用程序,顯示給用戶。當客戶代碼中第一次生成WebView對象時,會初始化WebKit庫(包括Java層和C層兩個部分),之後用戶可以操作WebView對象完成網絡或者本地資源的訪問。
WebView對象的生成主要涉及4個類CallbackProxy、WebViewCore、WebViewDatabase以及BrowserFrame。其中CallbackProxy對象爲WebKit模塊中UI線程和WebKit類庫提供交互功能,WebViewCore是WebKit的核心層,負責與C層交互以及WebKit模塊C層類庫初始化,WebViewDatabase爲WebKit模塊運行時緩存、cookie等數據存儲提供支持,BrowserFrame用於創建WebCore中的Frame,併爲Frame提供Java層回調方法。WebKit模塊初始化流程如下:
實例化WebView
- 創建CallbackProxy對象
- 創建WebViewCore對象
- 調用System.loadLibrary載入webcore相關類庫(C層)
- 如果是第一次初始化WebViewCore對象,創建WebCoreTherad線程
- 創建EventHub對象,處理WebViewCore事件
- 獲取WebIconDatabase對象實例
- 向WebCoreThread發送初始化消息
- 創建BrowserFrame對象
- 向WebView發送WEBCORE_INTIALIZED_MSG_ID消息,通知初始化完成
- 獲取WebViewDatabase實例
- 調用init初始化WebView
- 收到WEBCORE_INITIALIZED_MSG_ID消息後,調用nativeCreate
3.1.1 JNI native方法註冊
在創建WebViewCore時進行,調用System.loadLibrary方法載入webcore相關類庫,該過程由Dalvik虛擬機完成,它會從動態鏈接庫目錄中尋找libWebCore.so類庫,載入到內存中,並且調用WebKit初始化模塊的JNI_OnLoad方法(代碼見WebCoreJniOnLoad.cpp)。WebKit模塊的JNI_OnLoad方法中完成了如下初始化操作:
1. 初始化JavaBridge[registerJavaBridge]
獲取JWebCoreJavaBridge類的mNativeBridge成員變量的fieldID,以及註冊JWebCoreJavaBridge類中的native方法
2. 初始化JniUtil[registerJniUtil]
註冊JniUtil類中的native方法
3. 初始化WebFrame[registerWebFrame]
獲取BrowserFrame類的mNativeFrame成員變量的ID,以及註冊BrowserFrame類中的native方法
4. 初始化WebCoreResourceLoader[registerResourceLoader]
獲取LoadListener類的mNativeLoader成員的ID,以及註冊LoadListener類中的native方法
5. 初始化WebViewCore[registerWebViewCore]
獲取WebViewCore類的java成員的ID,以及註冊WebViewCore類中的native方法
6. 初始化WebHistory[registerWebHistory]
獲取WebHistoryItem類的java成員的ID,以及註冊WebBackForwardList和WebHistoryItem類中的native方法
7. 初始化WebIconDatabase[registerWebIconDatabase]
註冊WebIconDatabase類中的native方法
8. 初始化WebSettings[registerWebSettings]
獲取WebSettings類的java成員的ID,以及註冊native方法
9. 初始化WebStorage[registerWebStorage]
註冊WebStorage類的native方法
10. 初始化WebView[registerWebView]
獲取WebView類的mNativeClass成員的ID,以及註冊native方法
11. 初始化ViewStateSerializer[registerViewStateSerializer]
註冊ViewStateSerializer類的native方法
12. 初始化GeolocationPermissions[registerGeolocationPermissions]
註冊GeolocationPermissions類的native方法
13. 初始化MockGeolocation[registerMochGeolocation]
註冊MockGeolocation類的native方法
14. 初始化HTML5Audio[registerMediaPlayerAudio]
註冊HTML5 Audio類的native方法
15. 初始化HTML5Video[registerMediaPlayerVideo]
註冊HTML5VideoViewProxy類的native方法
16. 初始化DeviceMotionAndOrientationManager[registerDeviceMotionAndOrientationManager]
註冊DeviceMotionAndOrientationManager類的native方法
17. 初始化CookieManager[registerCookieManager]
註冊CookieManager類的native方法
18. 初始化CacheManager[registerCacheManager]
註冊CacheManager類的native方法
3.1.2 UI線程和webcore線程
webcore線程在第一次創建WebViewCore對象時創建, 且只創建一次,該線程負責處理WebCore初始化事件。WebViewCore構造函數會被阻塞,直到WebCoreThread初始化完成。在WebViewCore對象構造最後一步,發送INITIALIZE消息給WebCoreThread,執行webcore相關的初始化(WebViewCore::initialize)。在WebViewCore::initialize方法中,會創建BrowserFrame對象,並且向WebView對象發送WEBCORE_INITIALIZED_MSG_ID消息。WebView收到消息後,會執行nativeCreate方法,創建c層的WebView對象。
3.1.3 初始化過程序列圖
3.2 loadData
loadData用於加載"data:”形式的url,通過該方法,可以將文件內容讀入到字符串,然後通過loadData進行加載,是最簡單的一種數據加載方法。比如:
webview.loadData(“<html><body>hello</body></html>”, "text/html”, "utf-8”);