標籤:hybrid app android cordova phonegap
本篇文章是Cordova Android源碼分析系列文章的第二篇,主要分析CordovaWebView和CordovaWebViewClient類,通過分析代碼可以知道Web網頁載入的過程,錯誤出來,多執行緒等。
CordovaWebView類分析
CordovaWebView類繼承了Android WebView類,這是一個很自然的實現,共1000多行代碼。包含了PluginManager pluginManager,BroadcastReceiver receiver,CordovaInterface cordova, CordovaWebViewClient viewClient,CordovaChromeClient chromeClient,NativeToJsMessageQueue jsMessageQueue ,ExposedJsApi exposedJsApi,CordovaResourceApi resourceApi等重要的成員變數,與其它核心類關聯起來。
提供了4個建構函式:CordovaWebView(Context context),CordovaWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) ,分別對應Android WebView類的相應建構函式。這些建構函式首先調用WebView的相應建構函式,然後初始化cordova類變數,按情況依次調用自身的setWebChromeClient,initWebViewClient,loadConfiguration,setup方法。
setWebChromeClient方法設定WebChromeClient,調用了WebView類的setWebChromeClient方法。
initWebViewClient方法根據Android SDK版本的不同,分別調用setWebViewClient,針對IceCreamSandwich版本,調用setWebViewClient(new IceCreamCordovaWebViewClient(this.cordova, this))。暫時不知道具體的原因。
/** * set the WebViewClient, but provide special case handling for IceCreamSandwich. */ private void initWebViewClient(CordovaInterface cordova) { if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB || android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { this.setWebViewClient(new CordovaWebViewClient(this.cordova, this)); } else { this.setWebViewClient(new IceCreamCordovaWebViewClient(this.cordova, this)); } }
setup方法的作用是初始化WebView。首先啟用JavaScript,就像我們自己使用WebView時一樣:
WebSettings settings = this.getSettings(); settings.setJavaScriptEnabled(true); settings.setJavaScriptCanOpenWindowsAutomatically(true); settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
針對HTC 2.x devices系列裝置設定nav dump(針對ICS禁用,針對Jellybean 4.2降級),具體實現是通過反射拿到setNavDump方法,如果裝置型號中包含HTC並且SDK版本小於11(HONEYCOMB),設定setNavDump(true)。
設定不儲存網頁表單資料,大家可以放心了!
//We don‘t save any form data in the application
settings.setSaveFormData(false);
settings.setSavePassword(false);
後面是設定databasePath,是否開啟偵錯模式等,依次調用了setDomStorageEnabled,setGeolocationEnabled,setAppCacheMaxSize,setAppCachePath,setAppCacheEnabled等WebSetting的方法,從名字就可以很容易的理解作用。
然後註冊了一個receiver,監聽的IntentFilter action是ACTION_CONFIGURATION_CHANGED。
最後初始化了pluginManager,jsMessageQueue,exposedJsApi,resourceApi成員變數。
下面我們看下Web頁面載入的流程,先看loadUrl方法,它其實是調用了loadUrlIntoView方法。loadUrlIntoView方法首先會初始化外掛程式管理器pluginManager,然後建立了2個Runnable對象loadError和timeoutCheck,loadError用於通知用戶端viewClient錯誤資訊,timeoutCheck用於檢查頁面逾時,最後在UI線程中調用loadUrlNow(url)。注意timeoutCheck任務是線上程池中啟動並執行。loadUrlNow方法最終調用了WebView的loadUrl(url)方法。
// Load url this.cordova.getActivity().runOnUiThread(new Runnable() { public void run() { cordova.getThreadPool().execute(timeoutCheck); me.loadUrlNow(url); } });
CordovaWebViewClient類分析
CordovaWebViewClient類繼承了android WebViewClient,實現了CordovaWebView的回掉函數,這些回掉函數在渲染文檔的過程中會被觸發,例如onPageStarted(),shouldOverrideUrlLoading()等方法。
方法public boolean shouldOverrideUrlLoading(WebView view, String url) 為上層的web應用提供了url載入時處理的機會,js中的exec方法會被攔截,交給handleExecUrl(url)方法處理。如果uri是以tel,sms,geo,market等開頭,這裡會通過Intent啟用相關的App處理。如果是我們自己App或檔案,則會啟動一個新的Activity,包含一個新的CordovaWebView,主要當按返回鍵時,可以返回我們的應用。
方法public void onPageStarted(WebView view, String url, Bitmap favicon) 通知應用頁面開始載入,如果頁面中包含frameset或iframe,這些嵌入的頁面載入時是不會觸發onPageStarted的。通過this.appView.postMessage方法發送通知給所有外掛程式。
@Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); isCurrentlyLoading = true; LOG.d(TAG, "onPageStarted(" + url + ")"); // Flush stale messages. this.appView.jsMessageQueue.reset(); // Broadcast message that page has loaded this.appView.postMessage("onPageStarted", url); // Notify all plugins of the navigation, so they can clean up if necessary. if (this.appView.pluginManager != null) { this.appView.pluginManager.onReset(); } }
方法public void onPageFinished(WebView view, String url) 的實作類別似onPageStarted,需要注意的是頁面載入完成後延時2秒才會停止進度條,目的是防止出現js錯誤或cordova沒有初始化的情況,做法是建立一個線程,sleep 2s,然後在UI線程發送通知spinner stop給所有外掛程式。
CordovaResourceApi類分析
Cordova Android使用okhttp架構作為網路訪問基礎架構,okhttp 是一個 Java 的 HTTP+SPDY 用戶端開發包,同時也支援 Android。
CordovaResourceApi封裝了okhttp,主要提供了3個功能:
1.讀寫Url的助手方法
例如處理assets, resources, content providers, files, data URIs, http[s]
可以用來查詢檔案類型和內容長度
2.允許外掛程式重新導向Url
3.通過createHttpConnection()方法暴露Cordova內建的okhttp庫
這個類比較簡單,先是建立了靜態變數OkHttpClient httpClient和jsThread,這樣整個應用只有一個okhttp執行個體,是輕量級實現。
然後建構函式是
public CordovaResourceApi(Context context, PluginManager pluginManager) {
this.contentResolver = context.getContentResolver();
this.assetManager = context.getAssets();
this.pluginManager = pluginManager;
}
可以看到初始化了contentResolver,assetManager和pluginManager這個類成員變數。
public Uri remapUri(Uri uri) {
assertNonRelative(uri);
Uri pluginUri = pluginManager.remapUri(uri);
return pluginUri != null ? pluginUri : uri;
}
重新導向Url的方法,url必須是絕對路徑,最終實現在pluginManager.remapUri(uri)
@Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); // Ignore excessive calls. if (!isCurrentlyLoading) { return; } isCurrentlyLoading = false; LOG.d(TAG, "onPageFinished(" + url + ")"); /** * Because of a timing issue we need to clear this history in onPageFinished as well as * onPageStarted. However we only want to do this if the doClearHistory boolean is set to * true. You see when you load a url with a # in it which is common in jQuery applications * onPageStared is not called. Clearing the history at that point would break jQuery apps. */ if (this.doClearHistory) { view.clearHistory(); this.doClearHistory = false; } // Clear timeout flag this.appView.loadUrlTimeout++; // Broadcast message that page has loaded this.appView.postMessage("onPageFinished", url); // Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly if (this.appView.getVisibility() == View.INVISIBLE) { Thread t = new Thread(new Runnable() { public void run() { try { Thread.sleep(2000); cordova.getActivity().runOnUiThread(new Runnable() { public void run() { appView.postMessage("spinner", "stop"); } }); } catch (InterruptedException e) { } } }); t.start(); } // Shutdown if blank loaded if (url.equals("about:blank")) { appView.postMessage("exit", null); } }
public File mapUriToFile(Uri uri) 返回url指向的檔案,url可以是file://或content格式,這個方法必須啟動並執行後台線程的斷言,不能運行在UI線程和WebCore線程。
方法public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException 用來開啟指定的uri,skipThreadCheck設定是否檢查是否在後台線程運行,返回值OpenForReadResult是個靜態內部類,提供了uri,inputStream,mimeType,內容長度length,AssetFileDescriptor參數,方便我們使用。
下篇文章分析Cordova外掛程式架構,主要涉及CordovaPlugin和PluginManager類。