/** * Taken from http://stackoverflow.com/a/8949378 */ package com.dotcool.reader.helper; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ZoomButtonsController; /** * see http://stackoverflow.com/questions/3130654/memory-leak-in-webview and * http://code.google.com/p/android/issues/detail?id=9375 * Note that the bug does NOT appear to be fixed in android 2.2 as romain claims * * Also, you must call {@link #destroy()} from your activity's onDestroy method. */ public class NonLeakingWebView extends WebView { private static final String TAG = NonLeakingWebView.class.toString(); private static Field sConfigCallback; private ZoomButtonsController zoom_controll; private static boolean showZoom; static { try { sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback"); sConfigCallback.setAccessible(true); } catch (Exception e) { // ignored } } public NonLeakingWebView(Context context) { super(context); init(context); } public NonLeakingWebView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public NonLeakingWebView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { if (!isInEditMode()) { setWebViewClient(new MyWebViewClient((Activity) context)); } // Create our ScaleGestureDetector mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); } @Override public void destroy() { super.destroy(); try { if (sConfigCallback != null) sConfigCallback.set(null, null); } catch (Exception e) { throw new RuntimeException(e); } } /** * Set option to display zoom control * http://stackoverflow.com/a/11901948 * * @param show */ @SuppressLint("NewApi") public void setDisplayZoomControl(boolean show) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { this.getSettings().setDisplayZoomControls(show); } else { // get the control try { Class webview = Class.forName("android.webkit.WebView"); Method method = webview.getMethod("getZoomButtonsController"); zoom_controll = (ZoomButtonsController) method.invoke(this, null); showZoom = show; } catch (Exception e) { Log.e(TAG, "Error when getting zoom control", e); } } } @Override public boolean onTouchEvent(MotionEvent ev) { super.onTouchEvent(ev); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB && zoom_controll != null) { // Hide the controlls AFTER they where made visible by the default implementation. zoom_controll.setVisible(showZoom); } checkZoomEvent(ev); return true; } protected static class MyWebViewClient extends WebViewClient { protected WeakReference<Activity> activityRef; public MyWebViewClient(Activity activity) { this.activityRef = new WeakReference<Activity>(activity); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { try { final Activity activity = activityRef.get(); if (activity != null) activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); } catch (RuntimeException ignored) { // ignore any url parsing exceptions } return true; } } /** * Enable onScaleChange for pinch zoom * http://android-developers.blogspot.sg/2010/06/making-sense-of-multitouch.html */ private float mPosX; private float mPosY; private float mLastTouchX; private float mLastTouchY; private static final int INVALID_POINTER_ID = -1; // The �active pointer� is the one currently moving our object. private int mActivePointerId = INVALID_POINTER_ID; private ScaleGestureDetector mScaleDetector; private float mScaleFactor = 1.f; private void checkZoomEvent(MotionEvent ev) { // Let the ScaleGestureDetector inspect all events. mScaleFactor = this.getScale(); mScaleDetector.onTouchEvent(ev); final int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { final float x = ev.getX(); final float y = ev.getY(); mLastTouchX = x; mLastTouchY = y; mActivePointerId = ev.getPointerId(0); break; } case MotionEvent.ACTION_MOVE: { final int pointerIndex = ev.findPointerIndex(mActivePointerId); final float x = ev.getX(pointerIndex); final float y = ev.getY(pointerIndex); // Only move if the ScaleGestureDetector isn't processing a gesture. if (!mScaleDetector.isInProgress()) { final float dx = x - mLastTouchX; final float dy = y - mLastTouchY; mPosX += dx; mPosY += dy; invalidate(); } mLastTouchX = x; mLastTouchY = y; break; } case MotionEvent.ACTION_UP: { mActivePointerId = INVALID_POINTER_ID; break; } case MotionEvent.ACTION_CANCEL: { mActivePointerId = INVALID_POINTER_ID; break; } case MotionEvent.ACTION_POINTER_UP: { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = ev.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastTouchX = ev.getX(newPointerIndex); mLastTouchY = ev.getY(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); } break; } } } private WebViewClient currentWebClient = null; @Override public void setWebViewClient(WebViewClient client) { super.setWebViewClient(client); this.currentWebClient = client; } private void triggerOnScaleChanged(float oldScale, float newScale) { if (currentWebClient != null) { currentWebClient.onScaleChanged(this, oldScale, newScale); } } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { float oldScale = mScaleFactor; mScaleFactor *= detector.getScaleFactor(); // Don't let the object get too small or too large. mScaleFactor = Math.max(0.5f, Math.min(mScaleFactor, 5.0f)); triggerOnScaleChanged(oldScale, mScaleFactor); return true; } } }