瀏覽器在各個平臺都有,最容易拿到源碼的就是Android的;雖然有現成的可用,還是自己分析一下;
Android的WebView.java是一個內置的支持瀏覽器的視圖View,查看源碼目錄frameworks\base\core\java\android\webkit
下面有多個java源文件,第一個爲WebView.java,這個類可不小,將近8000行;
WebView provides no browser-like widgets, does not enable JavaScript and web page errors are ignored. If your goal is only
to display some HTML as a part of your UI, this is probably fine
但是該WebView只支持展示Web Page,不支持JavaScript和頁面內容;不過可以借鑑一下進行改造。
@Widget
public class WebView extends AbsoluteLayout
implements ViewTreeObserver.OnGlobalFocusChangeListener,
ViewGroup.OnHierarchyChangeListener {
WebView繼承AbsoluteLayout,實現OnGlobalFocusChangeListener和OnHierarchyChangeListener
構造函數
protected WebView(Context context, AttributeSet attrs, int defStyle,
Map<String, Object> javascriptInterfaces) {
super(context, attrs, defStyle);
init();//初始化
mCallbackProxy = new CallbackProxy(context, this);//回調
mViewManager = new ViewManager(this);//View manager
mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); //WebViewCore
mDatabase = WebViewDatabase.getInstance(context);//WebViewDataBase
mScroller = new OverScroller(context);
updateMultiTouchSupport(context);
}
看看如下幾個方法
private void init() {
setWillNotDraw(false);
setFocusable(true);
setFocusableInTouchMode(true);
setClickable(true);
setLongClickable(true);
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
int slop = configuration.getScaledTouchSlop();
mTouchSlopSquare = slop * slop;
mMinLockSnapReverseDistance = slop;
slop = configuration.getScaledDoubleTapSlop();
mDoubleTapSlopSquare = slop * slop;
final float density = getContext().getResources().getDisplayMetrics().density;
// use one line height, 16 based on our current default font, for how
// far we allow a touch be away from the edge of a link
mNavSlop = (int) (16 * density);
// density adjusted scale factors
DEFAULT_SCALE_PERCENT = (int) (100 * density);
mDefaultScale = density;
mActualScale = density;
mInvActualScale = 1 / density;
mTextWrapScale = density;
DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
mMaximumFling = configuration.getScaledMaximumFlingVelocity();
mOverscrollDistance = configuration.getScaledOverscrollDistance();
mOverflingDistance = configuration.getScaledOverflingDistance();
}
void updateMultiTouchSupport(Context context) {
WebSettings settings = getSettings();
final PackageManager pm = context.getPackageManager();
mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
&& settings.supportZoom() && settings.getBuiltInZoomControls();
mAllowPanAndScale = pm.hasSystemFeature(
PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
if (mSupportMultiTouch && (mScaleDetector == null)) {
mScaleDetector = new ScaleGestureDetector(context,
new ScaleDetectorListener());
} else if (!mSupportMultiTouch && (mScaleDetector != null)) {
mScaleDetector = null;
}
}
一個大方法,該PrivateHandler內部多達600多行代碼
/**
* General handler to receive message coming from webkit thread
*/
class PrivateHandler extends Handler {
@Override
public void handleMessage(Message msg) {
還有一個DataSetObserver
private class SingleDataSetObserver extends DataSetObserver {
最後面多個被JNI調用的方法
和native的接口方法
private native int nativeCacheHitFramePointer();
private native Rect nativeCacheHitNodeBounds();
private native int nativeCacheHitNodePointer();
/* package */ native void nativeClearCursor();
private native void nativeCreate(int ptr);
private native int nativeCursorFramePointer();
private native Rect nativeCursorNodeBounds();
private native int nativeCursorNodePointer();
/* package */ native boolean nativeCursorMatchesFocus();
private native boolean nativeCursorIntersects(Rect visibleRect);
private native boolean nativeCursorIsAnchor();
private native boolean nativeCursorIsTextInput();
private native Point nativeCursorPosition();
private native String nativeCursorText();
/**
* Returns true if the native cursor node says it wants to handle key events
* (ala plugins). This can only be called if mNativeClass is non-zero!
*/
private native boolean nativeCursorWantsKeyEvents();
private native void nativeDebugDump();
private native void nativeDestroy();
private native boolean nativeEvaluateLayersAnimations();
private native void nativeExtendSelection(int x, int y);
private native void nativeDrawExtras(Canvas canvas, int extra);
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
private native void nativeFindNext(boolean forward);
/* package */ native int nativeFocusCandidateFramePointer();
/* package */ native boolean nativeFocusCandidateHasNextTextfield();
/* package */ native boolean nativeFocusCandidateIsPassword();
private native boolean nativeFocusCandidateIsRtlText();
private native boolean nativeFocusCandidateIsTextInput();
/* package */ native int nativeFocusCandidateMaxLength();
/* package */ native String nativeFocusCandidateName();
private native Rect nativeFocusCandidateNodeBounds();
/**
* @return A Rect with left, top, right, bottom set to the corresponding
* padding values in the focus candidate, if it is a textfield/textarea with
* a style. Otherwise return null. This is not actually a rectangle; Rect
* is being used to pass four integers.
*/
private native Rect nativeFocusCandidatePaddingRect();
/* package */ native int nativeFocusCandidatePointer();
private native String nativeFocusCandidateText();
private native int nativeFocusCandidateTextSize();
/**
* Returns an integer corresponding to WebView.cpp::type.
* See WebTextView.setType()
*/
private native int nativeFocusCandidateType();
private native boolean nativeFocusIsPlugin();
private native Rect nativeFocusNodeBounds();
/* package */ native int nativeFocusNodePointer();
private native Rect nativeGetCursorRingBounds();
private native String nativeGetSelection();
private native boolean nativeHasCursorNode();
private native boolean nativeHasFocusNode();
private native void nativeHideCursor();
private native boolean nativeHitSelection(int x, int y);
private native String nativeImageURI(int x, int y);
private native void nativeInstrumentReport();
/* package */ native boolean nativeMoveCursorToNextTextInput();
// return true if the page has been scrolled
private native boolean nativeMotionUp(int x, int y, int slop);
// returns false if it handled the key
private native boolean nativeMoveCursor(int keyCode, int count,
boolean noScroll);
private native int nativeMoveGeneration();
private native void nativeMoveSelection(int x, int y);
private native boolean nativePointInNavCache(int x, int y, int slop);
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
private native void nativeRecordButtons(boolean focused,
boolean pressed, boolean invalidate);
private native void nativeResetSelection();
private native void nativeSelectAll();
private native void nativeSelectBestAt(Rect rect);
private native int nativeSelectionX();
private native int nativeSelectionY();
private native int nativeFindIndex();
private native void nativeSetExtendSelection();
private native void nativeSetFindIsEmpty();
private native void nativeSetFindIsUp(boolean isUp);
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
private native void nativeSetRootLayer(int layer);
private native void nativeSetSelectionPointer(boolean set,
float scale, int x, int y);
private native boolean nativeStartSelection(int x, int y);
private native void nativeSetSelectionRegion(boolean set);
private native Rect nativeSubtractLayers(Rect content);
private native int nativeTextGeneration();
// Never call this version except by updateCachedTextfield(String) -
// we always want to pass in our generation number.
private native void nativeUpdateCachedTextfield(String updatedText,
int generation);
private native boolean nativeWordSelection(int x, int y);
// return NO_LEFTEDGE means failure.
private static final int NO_LEFTEDGE = -1;
private native int nativeGetBlockLeftEdge(int x, int y, float scale);
另外一個類WebViewCore.java,代碼大概2000多行
static {
// Load libwebcore during static initialization. This happens in the
// zygote process so it will be shared read-only across all app
// processes.
try {
System.loadLibrary("webcore");
} catch (UnsatisfiedLinkError e) {
Log.e(LOGTAG, "Unable to load webcore library");
}
}
典型的加載native的code的代碼庫webcore
看下構造行數
public WebViewCore(Context context, WebView w, CallbackProxy proxy,
Map<String, Object> javascriptInterfaces) {
// No need to assign this in the WebCore thread.
mCallbackProxy = proxy;
mWebView = w;
mJavascriptInterfaces = javascriptInterfaces;
// This context object is used to initialize the WebViewCore during
// subwindow creation.
mContext = context;
// We need to wait for the initial thread creation before sending
// a message to the WebCore thread.
// XXX: This is the only time the UI thread will wait for the WebCore
// thread!
synchronized (WebViewCore.class) {
if (sWebCoreHandler == null) {
// Create a global thread and start it.
Thread t = new Thread(new WebCoreThread());
t.setName(THREAD_NAME);
t.start();
try {
WebViewCore.class.wait();
} catch (InterruptedException e) {
Log.e(LOGTAG, "Caught exception while waiting for thread " +
"creation.");
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
}
// Create an EventHub to handle messages before and after the thread is
// ready.
mEventHub = new EventHub();
// Create a WebSettings object for maintaining all settings
mSettings = new WebSettings(mContext, mWebView);
// The WebIconDatabase needs to be initialized within the UI thread so
// just request the instance here.
WebIconDatabase.getInstance();
// Create the WebStorage singleton and the UI handler
WebStorage.getInstance().createUIHandler();
// Create the UI handler for GeolocationPermissions
GeolocationPermissions.getInstance().createUIHandler();
// Send a message to initialize the WebViewCore.
Message init = sWebCoreHandler.obtainMessage(
WebCoreThread.INITIALIZE, this);
sWebCoreHandler.sendMessage(init);
}
該類中存在大量的JNI方法
一個WebCoreThread
private static class WebCoreThread implements Runnable {
public void run() {
Looper.prepare();
Assert.assertNull(sWebCoreHandler);
synchronized (WebViewCore.class) {
sWebCoreHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case INITIALIZE:
WebViewCore core = (WebViewCore) msg.obj;
core.initialize();
break;
case REDUCE_PRIORITY:
// 3 is an adjustable number.
Process.setThreadPriority(
Process.THREAD_PRIORITY_DEFAULT + 3 *
Process.THREAD_PRIORITY_LESS_FAVORABLE);
break;
case RESUME_PRIORITY:
Process.setThreadPriority(
Process.THREAD_PRIORITY_DEFAULT);
break;
}
}
};
WebViewCore.class.notify();
}
Looper.loop();
}
事件處理類
class EventHub {
轉換消息方法
private void transferMessages() {
mTid = Process.myTid();
mSavedPriority = Process.getThreadPriority(mTid);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (DebugFlags.WEB_VIEW_CORE) {
多達400行;
WebView的官方文檔
http://developer.android.com/reference/android/webkit/WebView.html
專注設計,專注實現。
需要權限:
<uses-permission android:name="android.permission.INTERNET" />
用例1:
點擊某個按鈕或者圖片,觸發顯示對應的網頁
Uri uri =
Uri.parse("http://www.example.com");
Intent intent =
new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
用例2:
WebView控件加載指定的URL的網頁
webview.loadUrl("http://slashdot.org/");
// OR, you can also load from an HTML string:
String summary =
"<html><body>You scored <b>192</b> points.</body></html>";
webview.loadData(summary,
"text/html",
null);
使用案例:
創建設置WebChromeClient子類或者WebViewClient子類
修改WebSetting,setJavaScriptEnabled
在WebView中注入Java Object
addJavaScriptInterface(Object,String),該對象可以在頁面對應的context中的JavaScript所獲取到
getWindow().requestFeature(Window.FEATURE_PROGRESS);
webview.getSettings().setJavaScriptEnabled(true);
final Activity activity
= this;
webview.setWebChromeClient(new
WebChromeClient()
{
public void onProgressChanged(WebView view,
int progress)
{
// Activities and WebViews measure progress with different scales.
// The progress meter will automatically disappear when we reach 100%
activity.setProgress(progress
* 1000);
}
});
webview.setWebViewClient(new
WebViewClient()
{
public void onReceivedError(WebView view,
int errorCode,
String description,
String failingUrl)
{
Toast.makeText(activity,
"Oh no! " + description,
Toast.LENGTH_SHORT).show();
}
});
webview.loadUrl("http://slashdot.org/");