使用 Mockito 单元测试

目录


1. 需求知识

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

2.2. 存根(Stub) vs. 模拟对象 (Mock)

2.3. 行为测试 vs. 状态测试

2.4. 生成模拟对象

3. 模拟框架( Mock Framework)

4. Mockito

4.1. 使用 Mockito 模拟对象

4.2. 使用 Mockito

4.3. Mockito的限制

4.4. 模拟对象的配置

4.5. 验证模拟对象的行为

4.6. Spy

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

5.2. 安装

6. 链接和参考

1.需求知识

该教程需要理解单元测试和熟悉JUnit框架的使用。

如果您不熟悉JUnit,请阅读JUnit教程。

2. 使用 存根(Stub) 和 模拟对象(Mock Object) 进行测试

2.1. 为什么需要模拟?

一个单元测试需要在隔离的环境下执行。如果可以的话需要消除其他依赖的服务影响。但实际上,软件中是充满依赖关系的.我们会基于service类写操作类,而service类又是基于数据访问类(DAOs)的,依次下去.

为了解决这个问题, 可以使用 存根 (Stub) 或者 模拟 (Mock) 对象的方法进行测试。

2.2. 存根(Stub) vs. 模拟对象 (Mock)

存根(Stub)类是实现了一个接口或者抽象类的类,可以在测试过程中使用该类,例如:

public class TestStub {
	static interface USB {
		void work();
	}

	static class Mp3Stub implements USB {
		@Override
		public void work() {
			// code.

		}
	 }

	static class Mp4Stub implements USB {
		@Override
		public void work() {
			// code.
		}
	}
}

一个模拟对象(mock object)是一个接口或者抽象类的虚拟实现。例如:

public class TestMock {
	static interface USB {
		void work();
	}

	@Test
	public void testMockObject() {
		USB usb = Mockito.mock( USB.class );
		usb.work();
	}
}

存根和模拟对象都可以传递给其他的对象进行测试。你的一些单元测试可以测这些类的正确性等。利用存根对象或者模拟对象可以保证测试过程中不受到其他的影响。

存根对象需要自定义实现方法;

模拟对象只需要更少的代码和简单的配置。

以下的内容将详细介绍模拟对象的使用方法。

2.3. 行为测试 vs. 状态测试

Mock 对象允许你对行为进行测试。有一些测试不需要验证结果,但是需要检查某些方法是否被正确的参数调用过。这种测试为行为测试。

状态测试只是关注与结果是否正确,而行为测试能够判断一个应用调用结构以及层次。

2.4. 生成模拟对象

你们可以使用Mock 框架来生成模拟对象。Mock 框架允许你在运行期间创建对象,并且定义它的一些行为。

一个典型的例子就是使用模拟对象来模拟数据库DAO层。在生产环境上是使用运行的数据库,但是在单元测试环境中完全可以用模拟对象来模拟数据,确保单元测试的正确条件。这样就不需要依赖于外部的数据。

3. 模拟框架( Mock Framework)

比较流行的模拟框架有 EasyMock、jMock 和 Mockito。下面的列表是这些框架的链接。
# jMock
http://jmock.org/
# EasyMock
http://easymock.org/
# Mockito
http://mockito.org/

4. Mockito

4.1. 使用 Mockito 模拟对象

Mockito 是比较流行的模拟框架,可以与JUnit 联合起来测试。它允许你进行创建和配置模拟对象。

Mockito的官方网站: Mockito 主页.

4.2. 使用 Mockito

Mockito 支持使用 mock() 静态方法创建模拟对象。

同样也支持 @Mock注解方式,如果使用注解的方式,需要使用在初始化方法调用 MockitoAnnotation.InitMock( this ) 方法

例如,下面的例子就是使用 Mockito 进行对类 ClassToTest 的单元测试。

public class MockitoTest {
	@Mock
	MyDatabase databaseMock;

	@Before
	public void setUp() throws Exception {
		MockitoAnnotations.initMocks(this);
	}


	@Test
	public void testQuery() {
		// 需要测试的类
		ClassToTest t = new ClassToTest(databaseMock);
		// 调用方法
		boolean check = t.query("* from t");
		// 验证结果
		assertTrue(check);
		// 模拟对象是否调用了该方法
		Mockito.verify( databaseMock ).query("* from t");
	}
}

提示

可以使用静态导入方法调用方法 mock()

4.3. Mockito的限制

Mockito 以下的类型不能进行构造:

  • 终态类(final classes)
  • 匿名类(anonymous classes)
  • 基本数据类型(primitive types)

4.4. 模拟对象的配置

Mockito 可以使用 verify() 方法来确认某些方法是否被调用过.

when(....).thenReturn(....) 结构可以为某些条件给定一个预期的返回值.

@Test
public void testList() {
	List mock = Mockito.mock( List.class );
	Mockito.when( mock.get( 0 ) ).thenReturn( 1 );
	assertEquals( "预期返回1", 1, mock.get( 0 ) );
}

同样可以使用doReturn(object).when(kdskfsk).methodCall 结构

4.5. 验证模拟对象的行为

Mockito 跟踪了所有的方法调用和参数的调用情况。verify()可以验证方法的行为。

查看下面的例子:

@Test
public void testMap() {
	Map mock = Mockito.mock( Map.class );
	Mockito.when( mock.get( "city" ) ).thenReturn( "深圳" );
	// test code
	assertEquals( "城市测试", "深圳", mock.get( "city" ) );
	Mockito.verify(mock).get( Matchers.eq( "city" ) );
	Mockito.verify( mock, Mockito.times( 2 ) );
}

4.6. Spy

@Spy 或者方法 spy() 可以包含一个真实的对象. 每次调用,除非特出指定,委托给改真实对象的调用.

@Test
public void testSpy() {
	// Lets mock a LinkedList
	List list = new LinkedList();
	list.add( "yes" );
	List spy = Mockito.spy(list);
	//You have to use doReturn() for stubbing
	assertEquals( "yes", spy.get( 0 ) );
	Mockito.doReturn("foo").when(spy).get(0);
	assertEquals( "foo", spy.get( 0 ) );
 }


@Test( expected = IndexOutOfBoundsException.class)
public void testSpy2() {
	// Lets mock a LinkedList
	List list = new LinkedList();
	List spy = Mockito.spy(list);
	// this would not work
	// real method is called so spy.get(0)
	// throws IndexOutOfBoundsException (list is still empty)
	Mockito.when(spy.get(0)).thenReturn("foo");
	assertEquals( "foo", spy.get( 0 ) );
}

5. Mockito 在 Android 平台测试

5.1. 在 Android 使用 Mockito

Mockito 同样也可以在安卓平台上进行测试。

5.2. 安装

在 Android 测试项目中使用 Mockito。添加下面的包到Android 测试项目的 libs 目录
https://mockito.googlecode.com/files/mockito-all-1.9.5.jar
http://dexmaker.googlecode.com/files/dexmaker-1.0.jar
http://dexmaker.googlecode.com/files/dexmaker-mockito-1.0.jar
接下来可以在你的测试项目中使用 Mockito 。

6. 链接和参考

Mockito 项目主页
Mockito 的依赖注入功能
Unit tests with Mockito - Tutorial
使用 Mockito 单元测试 – 教程

使apk具有system权限

使apk具有system权限的方法:

1. 在应用程序的AndroidManifest.xml中的manifest节点中加入android:sharedUserId="android.uid.system"这个属性。如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.my.application"
    android:sharedUserId="android.uid.system">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

    </application>

</manifest>

2. 修改Android.mk文件,加入LOCAL_CERTIFICATE := platform这一行。

3. 使用mm命令来编译,或者参照ANDROID系统证书PLATFORM.X509.PEM,PLATFORM.PK8转换为.KEYSTORE文件对编译好的APK进行单独签名。

Android自动测试之Monkey工具

什么是Monkey


Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软件的稳定性、健壮性的快速有效的方法。

Monkey的基本用法


基本语法如下:

$adb shell monkey [options]

如果不指定options,Monkey将以无反馈模式启动,并把事件任意发送到安装在目标环境中的全部包。下面是一个更为典型的命令行示例,它启动指定的应用程序,并向其发送500个伪随机事件:

$adb shell monkey -p your.package.name -v 500

更加详细的命令参数,参考下表:

标题 选项 描述
General --help Prints a simple usage guide.
-v Each -v on the command line will increment the verbosity level. Level 0 (the default) provides little information beyond startup notification, test completion, and final results. Level 1 provides more details about the test as it runs, such as individual events being sent to your activities. Level 2 provides more detailed setup information such as activities selected or not selected for testing.
Events -s <seed> Seed value for pseudo-random number generator. If you re-run the Monkey with the same seed value, it will generate the same sequence of events.
--throttle <milliseconds> Inserts a fixed delay between events. You can use this option to slow down the Monkey. If not specified, there is no delay and the events are generated as rapidly as possible.
--pct-touch <percent> Adjust percentage of touch events. (Touch events are a down-up event in a single place on the screen.)
--pct-motion <percent> Adjust percentage of motion events. (Motion events consist of a down event somewhere on the screen, a series of pseudo-random movements, and an up event.)
--pct-trackball <percent> Adjust percentage of trackball events. (Trackball events consist of one or more random movements, sometimes followed by a click.)
--pct-nav <percent> Adjust percentage of "basic" navigation events. (Navigation events consist of up/down/left/right, as input from a directional input device.)
--pct-majornav <percent> Adjust percentage of "major" navigation events. (These are navigation events that will typically cause actions within your UI, such as the center button in a 5-way pad, the back key, or the menu key.)
--pct-syskeys <percent> Adjust percentage of "system" key events. (These are keys that are generally reserved for use by the system, such as Home, Back, Start Call, End Call, or Volume controls.)
--pct-appswitch <percent> Adjust percentage of activity launches. At random intervals, the Monkey will issue a startActivity() call, as a way of maximizing coverage of all activities within your package.
--pct-anyevent <percent> Adjust percentage of other types of events. This is a catch-all for all other types of events such as keypresses, other less-used buttons on the device, and so forth.
Constraints -p  <allowed-package-name> If you specify one or more packages this way, the Monkey will only allow the system to visit activities within those packages. If your application requires access to activities in other packages (e.g. to select a contact) you'll need to specify those packages as well. If you don't specify any packages, the Monkey will allow the system to launch activities in all packages. To specify multiple packages, use the -p option multiple times — one -p option per package.
-c <main-category> If you specify one or more categories this way, the Monkey will only allow the system to visit activities that are listed with one of the specified categories. If you don't specify any categories, the Monkey will select activities listed with the category Intent.CATEGORY_LAUNCHER or Intent.CATEGORY_MONKEY. To specify multiple categories, use the -c option multiple times — one -c option per category.
Debugging --dbg-no-events When specified, the Monkey will perform the initial launch into a test activity, but will not generate any further events. For best results, combine with -v, one or more package constraints, and a non-zero throttle to keep the Monkey running for 30 seconds or more. This provides an environment in which you can monitor package transitions invoked by your application.
--hprof If set, this option will generate profiling reports immediately before and after the Monkey event sequence. This will generate large (~5Mb) files in data/misc, so use with care. See Traceview for more information on trace files.
--ignore-crashes Normally, the Monkey will stop when the application crashes or experiences any type of unhandled exception. If you specify this option, the Monkey will continue to send events to the system, until the count is completed.
--ignore-timeouts Normally, the Monkey will stop when the application experiences any type of timeout error such as a "Application Not Responding" dialog. If you specify this option, the Monkey will continue to send events to the system, until the count is completed.
--ignore-security-exceptions Normally, the Monkey will stop when the application experiences any type of permissions error, for example if it attempts to launch an activity that requires certain permissions. If you specify this option, the Monkey will continue to send events to the system, until the count is completed.
--kill-process-after-error Normally, when the Monkey stops due to an error, the application that failed will be left running. When this option is set, it will signal the system to stop the process in which the error occurred. Note, under a normal (successful) completion, the launched process(es) are not stopped, and the device is simply left in the last state after the final event.
--monitor-native-crashes Watches for and reports crashes occurring in the Android system native code. If --kill-process-after-error is set, the system will stop.
--wait-dbg Stops the Monkey from executing until a debugger is attached to it.

参考


UI/Application Exerciser Monkey
Android自动测试之Monkey工具

TranslateAnimation动画结束后的位置问题

今天在使用TranslateAnimation位移一个LinearLayout时,发现动画完成后又会自动回到初始的状态,设置了fillAfter也不太管用。

仔细研究了一下,发现这种现象很正常,因为TranslateAnimation只负责实现位移动画效果,并不会真正的改变LinearLayout的位置,所以需要加一个AnimationListener ,在动画结束(onAnimationEnd() )后,通过设置LayoutParam里相关属性来改变LinearLayout位置才行。

@Override  
protected void onAnimationEnd() {  
	super.onAnimationEnd();  
	FrameLayout.LayoutParams ll = new FrameLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT);  
	ll.setMargins(-50, 0, 0, 0);  
	main_layout.setLayoutParams(ll);  
}

具体的Margin的数值,根据实际需要进行调整即可。也可以直接设置控件的起始X,Y坐标即可。

参考链接:Android: 解决动画完成后位置恢复到初始位置的问题

Android系统证书platform.x509.pem,platform.pk8转换为.keystore文件

概述


我们在进行Android开发的时候,如果开发的是系统应用,都需要系统的签名才能正常运行。而系统应用一般是把代码统一放到系统代码库中跟随系统一起编译。而如果我们想用Android Studio单独开发某个应用而使用系统签名的时候,有两种开发的方式,一种是编译完成后,使用SignApk.jar来进行签名,另外一种是把系统的platform.x509.pem platform.pk8 转换成为Android Studio常用的.keystore文件,编译时候自动签名。

Android系统签名证书的目录是“build/target/product/security”。

SignApk.jar签名


命令行中使用SignApk.jar签名的方式如下:

$ java -jar SignApk.jar platform.x509.pem platform.pk8 Application.apk Application_signed.apk

转换为.keystore文件


使用keytool-importkeypair本站下载)进行转换,转换命令如下:

$ keytool-importkeypair -k ~/.android/debug.keystore -p android -pk8 platform.pk8 -cert platform.x509.pem -alias platform

Android Studio中使用证书:

signingConfigs {
	release {
		File strFile = new File("../app/keystore/platform.keystore")
		storeFile file(strFile)
		keyAlias 'platform'
		keyPassword 'android'
		storePassword 'android'
	}
	debug {
		File strFile = new File("../app/keystore/platform.keystore")
		storeFile file(strFile)
		keyAlias 'platform'
		keyPassword 'android'
		storePassword 'android'
	}
}

buildTypes {
	release {
		signingConfig signingConfigs.release
		minifyEnabled false
		proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
	}
	debug {
		signingConfig signingConfigs.debug
	}
}

Android Studio中使用SystemProperties

概述


Android的系统属性相当于Windows的注册表,由key和value组成,且都是核心系统的一个基本机制。相对于Windows的注册表,Android的系统属性要简单一些,它没有Windows注册表的树状结构,而只是一个列表,也就是说没有父子关系。value有string,int,long,boolean,但是设置只能通过字符串方式。

读取系统属性,是通过SystemProperties类来实现的。SystemProperties在android.os下,但这个类是隐藏的,上层程序开发无法直接使用。要使用这个类,有两种方法。一个是导入layoutlib.jar,另外一种是通过反射的方式调用。

导入layoutlib.jar


我们只介绍一下如何在Android Studio中导入的方式
在gradle配置文件中,写一个函数,动态获取layoutlib.jar路径,然后加到dependencies中即可,代码如下:

dependencies {
    provided files(getLayoutLibPath())
}

/** ZhangChao time:2014-12-31,get layoutlib.jar path. android.os.SystemProperties need it. */
// must called after "android" definition
def getLayoutLibPath() {
    def rootDir = project.rootDir
    def localProperties = new File(rootDir, "local.properties")
    if (localProperties.exists()) {
        Properties properties = new Properties()
        localProperties.withInputStream {
            instr -> properties.load(instr)
        }
        def sdkDir = properties.getProperty('sdk.dir')
        def compileSdkVersion = android.compileSdkVersion
        Console.println("app compileSdkVersion : " + compileSdkVersion)
        def androidJarPath = sdkDir + "/platforms/" + compileSdkVersion + "/data/layoutlib.jar"
        return androidJarPath
    }
    return rootDir
}

导入之后,直接

import android.os.SystemProperties;

就可以正常使用了。
注意:引入的layoutlib.jar并不会编译到APK包里面,因此不需要担心增加最终的APK的大小的情况。

反射调用


对于不想引入layoutlib.jar的情况,可以直接使用下面的反射类来实现调用。

import java.lang.reflect.Method;

public class PropertyUtils {

    private static volatile Method set = null;
    private static volatile Method get = null;

    public static void set(String prop, String value) {

        try {
            if (null == set) {
                synchronized (PropertyUtils.class) {
                    if (null == set) {
                        Class<?> cls = Class.forName("android.os.SystemProperties");
                        set = cls.getDeclaredMethod("set", new Class<?>[]{String.class, String.class});
                    }
                }
            }
            set.invoke(null, new Object[]{prop, value});
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }


    public static String get(String prop, String defaultvalue) {
        String value = defaultvalue;
        try {
            if (null == get) {
                synchronized (PropertyUtils.class) {
                    if (null == get) {
                        Class<?> cls = Class.forName("android.os.SystemProperties");
                        get = cls.getDeclaredMethod("get", new Class<?>[]{String.class, String.class});
                    }
                }
            }
            value = (String) (get.invoke(null, new Object[]{prop, defaultvalue}));
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return value;
    }
}

参考链接


Android Studio 中如何引入 layoutlib.jar?

GSON序列化时排除字段的几种方式


使用transient


这个方法最简单,给字段加上 transient 修饰符就可以了,如下所示:

class GsonSerialization {
    public transient int x; // <---
    public int y;
    public GsonSerialization(int x, int y) {   
        this.x = x;   
        this.y = y;  
    }   
}

单元测试用例:

@Test 
public void testGsonSerialization() {
	GsonSerialization obj = new GsonSerialization(1, 2);
	String json = new Gson().toJson(obj);
	Assert.assertEquals("{\"y\":2}", json); // <--- 
}

使用Modifier指定


这个方法需要用GsonBuilder定制一个GSON实例,如下所示:

class GsonSerialization {
	protected int x; // <---
	public int y;
	public GsonSerialization(int x, int y) {   
		this.x = x;   
		this.y = y;  
	}   
}

单元测试用例:

@Test 
public void testGsonSerialization() {
	Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.PROTECTED).create();  // <---      
	GsonSerialization obj = new GsonSerialization(1, 2);
	String json = gson.toJson(obj); // <---  
	Assert.assertEquals("{\"y\":2}", json);
}

使用@Expose注解


注意,没有被 @Expose 标注的字段会被排除,如下所示:

class GsonSerialization {
	public int x; // <---
	@Expose
	public int y;
	public GsonSerialization(int x, int y) {   
		this.x = x;   
		this.y = y;  
	}   
}

单元测试用例:

@Test 
public void testGsonSerialization() {
	Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();  // <---      
	GsonSerialization obj = new GsonSerialization(1, 2);
	String json = gson.toJson(obj); // <---  
	Assert.assertEquals("{\"y\":2}", json);
}

使用ExclusionStrategy定制排除策略


这种方式最灵活,下面的例子把所有以下划线开头的字段全部都排除掉:

class GsonSerialization {
	public int x; // <---
	public int y;
	public GsonSerialization(int x, int y) {   
		this.x = x;   
		this.y = y;  
	}   
}

单元测试用例:

@Test 
public void testGsonSerialization() {  
	ExclusionStrategy myExclusionStrategy = new ExclusionStrategy() {   
		@Override   
		public boolean shouldSkipField(FieldAttributes fa){
			return fa.getName().startsWith("_");   
		}   
		@Override   
		public boolean shouldSkipClass(Class<?> clazz){   
			return false;   
		}    
	};    
	Gson gson = new GsonBuilder().setExclusionStrategies(myExclusionStrategy).create();// <---       
	MyObj obj = new MyObj(1, 2);  
	String json = gson.toJson(obj);  
	Assert.assertEquals("{\"y\":2}", json);
}

参考链接


GSON序列化时排除字段的几种方式

Android 4.4 前端调试

前端开发的同学通常会在Chrome的开发者调试工具中来对CSS和js进行即时调试和查看效果,如下图一样,对比调试的好处谁用谁知道。nuiqye.png!web

但是,当我们调试混合模式下通过Webview来调用页面的App时就不能使用这种方法啦。在 Android4.4下,原来PC上的调试体验可以完整继承下来,在PC打开调试工具,手机屏作为视图承载者能即时地显示出样式调整的变化。

让我们来一起看看怎么使用这种调试方法。打开手机=》设置=》开发者选项,进入到如下界面:FnUvqy.png!web

在这个界面上,我们可以看到“USB调试”和“ADB网络调试”,这两种调试模式在使用之前必须勾选打开。

在使用调试工具之前,我们还要确认以下几点:Chrome升级到最新版本、我本机Chrome的版本是 35.0.1862.2 dev-m ,可以在Chrome浏览器地址栏中输入“chrome://chrome/”查看当前Chrome的版本号;PC上安装ADB工具,在命令行中键入 “adb version”以查看adb版本。

这里我们以调试手Q应用中心线上首页为例

1、打开手机QQ=》动态=》应用宝

2、打开Chrome浏览器,在浏览器地址栏输入:chrome://inspect/#devices ,如下图,选中红框处的复选框AJBJ7n.png!web

3、在对应设备的下方会出现正在显示的页面,如下图所示,点击inspect链接,进入Chrome的调试模式

FFrQF3z.png!web

剽窃链接: Android 4.4 让前端调试也能这么酷

Android下WebView中Java与JavaScript通信

背景介绍


Android下面WebView开发,有时候需要调用底层的一部分接口,而这部分接口只有Android的SDK才提供相关的功能,这个时候就需要进行Java与JavaScript通信。

例子


  1. 生成JavaScript调用Java函数的接口类
    public class AndroidJavaScript {
    	@JavascriptInterface
    	public String getJavaString() {
    		return new String("JavaString");
    	}
    }
  2. 开启 JavaScript支持,并向WebView注册接口
    mWebView.getSettings().setJavaScriptEnabled(true);
    mWebView.addJavascriptInterface(new AndroidJavaScript(), "Android");
  3. Html中调用接口例子
    <html>
        <head>
            <script type="text/javascript">
                var JavaString = window.Android.getJavaString()
                document.write(JavaString);
            </script>
        </head>
    </html>
  4. 销毁的时候,反注册接口,避免内存泄漏
    mWebView.removeJavascriptInterface("Android");
  5. 销毁的时候,WebView从父容器中移除,避免内存泄漏
    ViewParent viewParent = mWebView.getParent();
    if(viewParent instanceof ViewGroup) {
        ViewGroup viewGroup = (ViewGroup)viewParent;
        viewGroup.removeView(mWebView);
    }
    mWebView.destroy();

完整的例子代码如下(包含WebView的XML布局文件,请自行生成):

import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;

public class MainActivity extends Activity {

    WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mWebView = (WebView)findViewById(R.id.jsBridgeWebView);
        WebSettings webSettings = mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        mWebView.addJavascriptInterface(new AndroidJavaScript(), mJsInterfaceName);
        mWebView.loadDataWithBaseURL("blarg://ignored", mJsHtml, "text/html", "UTF-8", "");
    }

    @Override
    protected void onDestroy() {
        mWebView.removeJavascriptInterface(mJsInterfaceName);
        ViewParent viewParent = mWebView.getParent();
        if(viewParent instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup)viewParent;
            viewGroup.removeView(mWebView);
        }
        mWebView.destroy();
        super.onDestroy();
    }

    private static class AndroidJavaScript {
        @JavascriptInterface
        public String getString() {
            return new String("JavaString");
        }
    }

    private final String mJsInterfaceName = "Android";
    private final String mJsHtml = "<html>\n" +
            "\t<head>\n" +
            "\t\t<script type=\"text/javascript\">\n" +
            "\t\t\tvar str = window.Android.getString()\r\n" +
            "\t\t\tdocument.write(str)\n" +
            "\t\t</script>\n" +
            "\t</head>\n" +
            "</html>";
}