本例使用了 Apache XML-RPC Server 端在 Android 平台上将 SDK 中的方法暴露出来,客户端分别用了 JMeter, Python, Java 的三种实现方式来对 SDK 进行测试举例。
SDK 中实现了 4 个方法:加法(add)、减法(subtract)、HTTP 请求查询 IP 地址的信息(httpGet)、获取 Android 设备的几条信息(getDeviceInfo)。它需要运行在 Android 平台,所以需要写一个 Android App 来运行 XML-RPC 服务。
注意本 SDK 采用了单例模式,在使用 XML-RPC 时需要小绕一下。
package com.mrqyoung.rpc.target;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
public class Target {
private Target() {
System.out.println("new Target");
}
private static class Holder {
private static final Target target = new Target();
}
/**
* SDK 通常会采用单例模式,返回唯一对象
*/
public static Target getInstance() {
return Holder.target;
}
/**
* SDK 中的基本方法例子1-加法
* @param i1
* @param i2
* @return
*/
public int add(int i1, int i2) {
return i1 + i2;
}
/**
* SDK 中的基本方法例子1-减法
* @param i1
* @param i2
* @return
*/
public int subtract(int i1, int i2) {
return i1 - i2;
}
/**
* 在内部进行网络请求并返回内容的例子
* 本例使用 ipip.net 的IP查询接口
* @param String ipAddr: 需要传入IP地址
* @return String
* @throws Exception
*/
public String httpGet(String ipAddr) throws Exception {
String url = "http://freeapi.ipip.net/" + ipAddr;
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = new DefaultHttpClient().execute(httpGet);
if (httpResponse == null) return "<Null>";
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (statusCode != 200) return "<Http Error: " + statusCode +">";
String result = EntityUtils.toString(httpResponse.getEntity());
if (result.isEmpty()) return "<Empty>";
return result;
}
/**
* 读取 Android 设备的一些简单信息
* @return
* @throws JSONException
*/
public String getDeviceInfo() throws Exception {
JSONObject json = new JSONObject();
json.put("deviceName", android.os.Build.MODEL);
json.put("apiLevel", android.os.Build.VERSION.SDK_INT);
json.put("androidVersion", android.os.Build.VERSION.RELEASE);
json.put("manufactuer", android.os.Build.MANUFACTURER);
json.put("product", android.os.Build.PRODUCT);
return json.toString();
}
}
服务端使用 Apache XML-RPC server 库,它内置了 Servlet,比较方便。
对于本 SDK,交互简单参数简单,服务端实现起来也很方便。对于可直接 new 来实例化的对象,使用代码 PropertyHandlerMapping.addHandler("Target", Target.class);
即可将 Target 类中的所有公开方法绑定到 Server,客户端通过 Target.methodName 并传入参数即可完成方法调用。
Android 服务端仅需要完成方法绑定,单例模式需要通过实现 RequestProcessorFactoryFactory
工厂类来完成实例化,并注意在退出时关闭 Server 即可。
如果 SDK 中的方法比较复杂,或者参数无法通过外部传递(如 Context),则需要自己来实现一个类,在类里面简化操作、传递特殊参数,然后将自己实现的类绑定到 Server ,相当于实现一个简单的中间件。
注意本例中有一个网络请求的方法,需要在 AndroidManifest.xml
中加上网络权限 <uses-permission android:name="android.permission.INTERNET"/>
package com.mrqyoung.rpc.usesdk;
import java.io.IOException;
import com.mrqyoung.rpc.target.Target;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.XmlRpcRequest;
import org.apache.xmlrpc.server.PropertyHandlerMapping;
import org.apache.xmlrpc.server.RequestProcessorFactoryFactory;
import org.apache.xmlrpc.server.XmlRpcServer;
import org.apache.xmlrpc.webserver.WebServer;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity {
private static final int PORT = 8000;
private WebServer webServer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startRPCServer();
}
/**
* 初始化XmlRpcServer,handle被测对象,开启服务
*/
private void startRPCServer() {
webServer = new WebServer(PORT);
XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();
PropertyHandlerMapping phm = new PropertyHandlerMapping();
/**
* 由于SDK中采用了单例模式,外部无法直接初始化,需要通过一个工厂类来完成初始化
* 如果SDK中的对象可以直接new,则下面这行以及InstanceFactory类都不需要。
*/
phm.setRequestProcessorFactoryFactory(new InstanceFactory());
phm.setVoidMethodEnabled(true);
try {
/**
* 为目标SDK添加hander,绑定到"sdk"这个名字上来,
* 在client端调用时使用的就是"sdk"名称,后面接Target类里面的方法
* 例如Target.add()方法,在外部对应为sdk.add()
*/
phm.addHandler("sdk", Target.class);
} catch (XmlRpcException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
xmlRpcServer.setHandlerMapping(phm);
try {
webServer.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 用工厂类来对Target进行初始化
* 参见 https://ws.apache.org/xmlrpc/handlerCreation.html
*/
private class InstanceFactory implements RequestProcessorFactoryFactory {
@Override
public RequestProcessorFactory getRequestProcessorFactory(Class arg0)
throws XmlRpcException {
// TODO Auto-generated method stub
return factory;
}
private final Target instance = Target.getInstance();
private final RequestProcessorFactory factory = new MyRequestProcessorFactory();
private class MyRequestProcessorFactory implements
RequestProcessorFactory {
public Object getRequestProcessor(XmlRpcRequest xmlRpcRequest)
throws XmlRpcException {
return instance;
}
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (webServer != null) {
webServer.shutdown();
}
}
}
JMeter 通过直接发送 HTTP POST 请求的方式来调用服务端的方法,并获得返回结果,可利用内置的正则和断言来判断结果。它不需要写代码,门槛低,编写 CASE 的速度较快较灵活,并且具有跨平台、命令行远程执行、定时任务自动完成等优点。缺点的需要了解 XML-RPC 中的请求参数的 XML 格式,但这点内容并不多,且可随用随查。
注意 JMeter 需要修改默认的结果编码来支持中文:修改 jmeter\bin\jmeter.properties
文件,sampleresult.default.encoding=ISO-8859-1
,修改为 utf-8
,并!重!启!
Python 从 1999 年就内置了 xmlrpc 库(Python2 为 xmlrpclib,Python3 为 xmlrpc),包含 Server 和 Client ,不需要额外安装,十分方便。
作为客户端,它的使用也非常简单:
xmlrpc.client.ServerProxy(SERVER_URI).handler.method(params)
本例使用了 unittest 框架。
# coding: utf-8
import sys
import unittest
if sys.version_info.major == 3:
from xmlrpc.client import ServerProxy
else:
from xmlrpclib import ServerProxy
RPC_SERVER_URI = 'http://192.168.1.4:8000'
class MySdkTests(unittest.TestCase):
def setUp(self):
self.sdk = ServerProxy(RPC_SERVER_URI).sdk
def tearDown(self):
pass
def test_add(self, a=123, b=456):
result = self.sdk.add(a, b)
print('\nsdk.add(%d, %d) = %d' % (a, b, result))
self.assertEqual(result, 579)
def test_subtract(self, a=2015, b=1999):
result = self.sdk.subtract(a, b)
print('\nsdk.subtract(%d, %d) = %d' % (a, b, result))
self.assertEqual(result, 16)
def test_httpGet(self, ip='203.208.48.146'):
result = self.sdk.httpGet(ip)
print('\nsdk.httpGet(%s) is %s' % (ip, result))
self.assertIsNotNone(result)
def test_getDeviceInfo(self):
result = self.sdk.getDeviceInfo()
print('\nsdk.getDeviceInfo() is:\n%s' % (result))
self.assertIsNotNone(result)
if __name__ == '__main__':
unittest.main(verbosity=2)
Java 客户端使用 Apache XML-RPC Client 库,测试方法与 Python 的很相似。
package com.mrqyoung.rpc.testmysdk;
import java.net.URL;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.junit.After;
import org.junit.Before;
public class TestMySDK extends TestCase {
private static final String RPC_SERVER_URI = "http://192.168.1.4:8000/";
private static XmlRpcClient rpc;
@Before
public void setUp() throws Exception {
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL(RPC_SERVER_URI));
rpc = new XmlRpcClient();
rpc.setConfig(config);
}
@After
public void tearDown() throws Exception {
}
public void testAdd() throws XmlRpcException {
Object[] params = new Object[]{123, 456};
int result = (Integer) rpc.execute("sdk.add", params);
System.out.println("sdk.add(123, 456) = " + result);
Assert.assertEquals(579, result);
}
public void testSubtract() throws XmlRpcException {
Object[] params = new Object[]{2015, 1999};
int result = (Integer) rpc.execute("sdk.subtract", params);
System.out.println("sdk.subtract(2015, 1999) = " + result);
Assert.assertEquals(16, result);
}
public void testHttpGet() throws XmlRpcException {
Object[] params = new Object[]{"203.208.48.146"};
String result = (String) rpc.execute("sdk.httpGet", params);
System.out.println("sdk.httpGet(203.208.48.146) is " + result);
Assert.assertNotNull(result);
}
public void testGetDeviceInfo() throws XmlRpcException {
String result = (String) rpc.execute("sdk.getDeviceInfo", new Object[]{});
System.out.println("sdk.getDeviceInfo() is:\n" + result);
Assert.assertNotNull(result);
}
}
XML-RPC 有开源的 C/C++
代码(XML-RPC for C and C++),在 iOS 上的服务端需要自行编译并实现一个 Servlet 容器(未作尝试)。而客户端相对来说是非常丰富的,支持的语言也比较多。
RPC 并不是什么新技术,但它的用途还挺广,比如 Appium 采用的是 JSON-RPC ,在 Android Studio\lib\
下也发现了一个 xmlrpc 的 jar 文件。
XML-RPC 具有简单、高效且易于实现,HTTP 协议,无关平台,适合持续集成。
特殊参数的传递、特殊方法的调用还需要自行实现中间件来完成。