在《Appium基础学习之 | Appium-Desktop日志分析》中说到,推送了一个Package为io.appium.uiautomator2.server.test的APK到设备端,然后通过adb shell am instrument -w io.appium.uiautomator2.server.test/androidx.test.runner.AndroidJunitRunner运行。经过前面学习UiAutomator2.0基于Instrumentation运行,从这里可以大概猜测io.appium.uiautomator2.server.test就是一个基于Instrumentation运行的Android工程,下面开始进入源码分析。
源码下载地址:https://github.com/appium/appium-uiautomator2-server,下载源码解压
在源码中找到appium-uiautomator2-server-master\app\src\androidTestServer\java\io\appium\uiautomator2\server\test目录,在目录下找到入口类AppiumUiAutomator2Server类
1.AppiumUiAutomator2Server类
@RunWith(AndroidJUnit4.class)
public class AppiumUiAutomator2Server {
private static ServerInstrumentation serverInstrumentation;
@Test
public void startServer() {
if (serverInstrumentation == null) {
serverInstrumentation = ServerInstrumentation.getInstance();
Logger.info("[AppiumUiAutomator2Server]", " Starting Server");
try {
while (!serverInstrumentation.isServerStopped()) {
SystemClock.sleep(1000);
serverInstrumentation.startServer();
}
} catch (SessionRemovedException e) {
//Ignoring SessionRemovedException
}
}
}
}
看到代码一目了然,基于AndroidJunit4运行;先是调用了ServerInstrumentation的getInstance方法
2.ServerInstrumentation类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\server目录下
public static synchronized ServerInstrumentation getInstance() {
if (instance == null) {
instance = new ServerInstrumentation(getApplicationContext(), getServerPort());
}
return instance;
}
getInstance方法实例化一个ServerInstrumentation对象,入参是getApplicationContext()、getServerPort()执行方法返回的内容,getApplicationContext得到是一个Context上下文对象,这个是Android应用中Application中唯一的。再到getServerPort()方法返回是一个6790端口,这个端口就是由Appium Server机器的8200端口转发到设备端的6790端口接收请求。
public class ServerConfig {
private final static int PORT = 6790;public static int getServerPort() {
return PORT;
}
}
然后回到AppiumUiAutomator2Server类,实例化ServerInstrumentation对象后,调用了它的startServer方法
private HttpdThread serverThread;
public void startServer() throws SessionRemovedException {
......serverThread = new HttpdThread(this.serverPort);
serverThread.start();
......
}
在startServer方法中实例化了HttpdThread这个内部类对象,继承Thread所以它是一个线程。
3.HttpdThread类
是ServerInstrumentation的内部类
private class HttpdThread extends Thread {
private final AndroidServer server;
private Looper looper;public HttpdThread(int serverPort) {
// Create the io.appium.uiautomator2.server but absolutely do not start it here
server = new AndroidServer(serverPort);
}@Override
public void run() {
Looper.prepare();
looper = Looper.myLooper();
startServer();
Looper.loop();
}}
HttpdThread构造函数实例化了AndroidServer对象,
4.AndroidServer类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\server目录下
public class AndroidServer {
private final HttpServer webServer;public AndroidServer(int port) {
webServer = new HttpServer(port);
init();
Logger.info("AndroidServer created on port " + port);
}protected void init() {
webServer.addHandler(new AppiumServlet());
}public void start() {
webServer.start();
}}
实例化了一个HttpServer对象,然后执行addHandler方法入参一个AppiumServlet对象
5.HttpServer类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\http目录下
public class HttpServer {
private final int port;
private final Listhandlers = new ArrayList ();
private Thread serverThread;public HttpServer(int port) {
this.port = port;
}public void addHandler(IHttpServlet handler) {
handlers.add(handler);
}
}
到HttpServer类中,看到addHander做的就是把AppiumServlet对象加入到了list列表中
6.AppiumServlet类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\server目录下
进入到AppiumServlet类中,可以看到它把appium操作对象都加入到了一个Map中,包括get、post、delete三种请求的所有操作
(1)Delete
protected static ConcurrentMap
deleteHandler = new ConcurrentHashMap<>(); private void registerDeleteHandler() {
register(deleteHandler, new DeleteSession("/wd/hub/session/:sessionId"));
}
(2)get
protected static ConcurrentMap
getHandler = new ConcurrentHashMap<>(); private void registerGetHandler() {
register(getHandler, new Status("/wd/hub/status"));
register(getHandler, new GetSessionDetails("/wd/hub/session/:sessionId"));
register(getHandler, new CaptureScreenshot("/wd/hub/session/:sessionId/screenshot"));
register(getHandler, new GetScreenOrientation("/wd/hub/session/:sessionId/orientation"));
register(getHandler, new GetRotation("/wd/hub/session/:sessionId/rotation"));
register(getHandler, new GetText("/wd/hub/session/:sessionId/element/:id/text"));
register(getHandler, new GetElementAttribute("/wd/hub/session/:sessionId/element/:id/attribute/:name"));
register(getHandler, new GetRect("/wd/hub/session/:sessionId/element/:id/rect"));
register(getHandler, new GetSize("/wd/hub/session/:sessionId/element/:id/size"));
register(getHandler, new GetName("/wd/hub/session/:sessionId/element/:id/name"));
// W3C endpoint
register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/element/:id/screenshot"));
// JSONWP endpoint
register(getHandler, new GetElementScreenshot("/wd/hub/session/:sessionId/screenshot/:id"));
register(getHandler, new Location("/wd/hub/session/:sessionId/element/:id/location"));
register(getHandler, new GetDeviceSize("/wd/hub/session/:sessionId/window/:windowHandle/size"));
register(getHandler, new Source("/wd/hub/session/:sessionId/source"));
register(getHandler, new GetSystemBars("/wd/hub/session/:sessionId/appium/device/system_bars"));
register(getHandler, new GetBatteryInfo("/wd/hub/session/:sessionId/appium/device/battery_info"));
register(getHandler, new GetSettings("/wd/hub/session/:sessionId/appium/settings"));
register(getHandler, new GetDevicePixelRatio("/wd/hub/session/:sessionId/appium/device/pixel_ratio"));
register(getHandler, new FirstVisibleView("/wd/hub/session/:sessionId/appium/element/:id/first_visible"));
register(getHandler, new GetAlertText("/wd/hub/session/:sessionId/alert/text"));
register(getHandler, new GetDeviceInfo("/wd/hub/session/:sessionId/appium/device/info"));
}
(3)post
protected static ConcurrentMap
postHandler = new ConcurrentHashMap<>(); private void registerPostHandler() {
register(postHandler, new NewSession("/wd/hub/session"));
register(postHandler, new FindElement("/wd/hub/session/:sessionId/element"));
register(postHandler, new FindElements("/wd/hub/session/:sessionId/elements"));
register(postHandler, new Click("/wd/hub/session/:sessionId/element/:id/click"));
register(postHandler, new Click("/wd/hub/session/:sessionId/appium/tap"));
register(postHandler, new Clear("/wd/hub/session/:sessionId/element/:id/clear"));
register(postHandler, new RotateScreen("/wd/hub/session/:sessionId/orientation"));
register(postHandler, new RotateScreen("/wd/hub/session/:sessionId/rotation"));
register(postHandler, new PressBack("/wd/hub/session/:sessionId/back"));
register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/element/:id/value"));
register(postHandler, new SendKeysToElement("/wd/hub/session/:sessionId/keys"));
register(postHandler, new Swipe("/wd/hub/session/:sessionId/touch/perform"));
register(postHandler, new TouchLongClick("/wd/hub/session/:sessionId/touch/longclick"));
register(postHandler, new OpenNotification("/wd/hub/session/:sessionId/appium/device/open_notifications"));
register(postHandler, new PressKeyCode("/wd/hub/session/:sessionId/appium/device/press_keycode"));
register(postHandler, new LongPressKeyCode("/wd/hub/session/:sessionId/appium/device/long_press_keycode"));
register(postHandler, new Drag("/wd/hub/session/:sessionId/touch/drag"));
register(postHandler, new Flick("/wd/hub/session/:sessionId/touch/flick"));
register(postHandler, new ScrollTo("/wd/hub/session/:sessionId/touch/scroll"));
register(postHandler, new MultiPointerGesture("/wd/hub/session/:sessionId/touch/multi/perform"));
register(postHandler, new W3CActions("/wd/hub/session/:sessionId/actions"));
register(postHandler, new TouchDown("/wd/hub/session/:sessionId/touch/down"));
register(postHandler, new TouchUp("/wd/hub/session/:sessionId/touch/up"));
register(postHandler, new TouchMove("/wd/hub/session/:sessionId/touch/move"));
register(postHandler, new UpdateSettings("/wd/hub/session/:sessionId/appium/settings"));
register(postHandler, new NetworkConnection("/wd/hub/session/:sessionId/network_connection"));
register(postHandler, new ScrollToElement("/wd/hub/session/:sessionId/appium/element/:id/scroll_to/:elementId"));
register(postHandler, new GetClipboard("/wd/hub/session/:sessionId/appium/device/get_clipboard"));
register(postHandler, new SetClipboard("/wd/hub/session/:sessionId/appium/device/set_clipboard"));
register(postHandler, new AcceptAlert("/wd/hub/session/:sessionId/alert/accept"));
register(postHandler, new DismissAlert("/wd/hub/session/:sessionId/alert/dismiss"));
}
7.HttpServer类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\http目录下
经过一串系列的实例化对象干完该干的数据初始化后,接着回到HttpdThread,是继承Thread线程,所以会执行run方法,run方法这里又使用到了Looper,真是无处不在,然后调用startServer方法,这个方法就不贴代码了,一直往下走调用了AndroidServer的start方法,继续调用的是HttpServer的start方法,
public void run() {
//管理进来请求
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//新建服务
ServerBootstrap bootstrap = new ServerBootstrap();
//最大请求队列
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
//支持端口共用
bootstrap.option(ChannelOption.SO_REUSEADDR, true);
//接受请求后交给ServerInitializer处理
bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ServerInitializer(handlers));
//绑定端口,传入的是6790,开始接收请求处理,一个channel表示一个请求
Channel ch = bootstrap.bind(port).sync().channel();
//等待请求关闭
ch.closeFuture().sync();
} catch (InterruptedException ignored) {
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
start方法是一个netty框架的使用,这个框架有需要了解后面再写一篇文章说这个框架吧。大概的意思就是初始化一个服务,然后接受请求,交给ServerInitializer对象来处理。接下来就看ServerInitializer的代码。
8.ServerInitializer类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\http目录下
public class ServerInitializer extends ChannelInitializer
{ private final List
handlers; public ServerInitializer(List
handlers) {
this.handlers = handlers;
}@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("codec", new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("io/appium/uiautomator2/handler", new io.appium.uiautomator2.http.ServerHandler(handlers));
}
}
ServerInitializer继承ChannelInitializer作为实际处理数据类,重写initChannel方法用初始化ChannelHandler,而ChannelHandler可以理解为就是处理Channel请求的。创建ChannelPipeline对象,这个对象是每一个请求Channel必须且只有一个,它是用来管理和处理ChannelHandler的。addLast方法就是把一个ChannelHandler交给ChannelPipeline管理。前面2个先不管,直接看
pipeline.addLast("io/appium/uiautomator2/handler", new io.appium.uiautomator2.http.ServerHandler(handlers));
ChannelHandler的名字是io/appium/uiautomator2/handler,ChannelHandler的处理由ServerHandler完成
9.ServerHandler类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\http目录下
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
......io.appium.uiautomator2.http.IHttpRequest httpRequest = new NettyHttpRequest(request);
io.appium.uiautomator2.http.IHttpResponse httpResponse = new NettyHttpResponse(response);for (io.appium.uiautomator2.http.IHttpServlet handler : httpHandlers) {
handler.handleHttpRequest(httpRequest, httpResponse);
if (httpResponse.isClosed()) {
break;
}
}......
}
ServerHandler处理进来的请求,channelRead方法在收到请求数据触发,通过NettyHttpRequest、NettyHttpResponse对象处理请求、响应的数据;然后进入for循环。
(1)先搞清楚for循环的httpHnadlers里面的数据,它是在AndroidServer的start方法中,调用init方法把AppiumServlet对象加进入的(在上面说到delete、get、post方法的所有请求uri哪里提到的)而AppiumServlet是实现了IhttpServlrt接口的。
(2)循环取出一个AppiumServlet对象,执行handleHttpRequest方法,回到AppiumServlet类
10.AppiumServlet类
在appium-uiautomator2-server-master\app\src\main\java\io\appium\uiautomator2\server目录下
先看看handleHttpRequest方法,分别是根据发送的请求,get、post、delete进入不同的语句块
@Override
public void handleHttpRequest(IHttpRequest request, IHttpResponse response) {
BaseRequestHandler handler = null;
if ("GET".equals(request.method())) {
handler = findMatcher(request, getHandler);
} else if ("POST".equals(request.method())) {
handler = findMatcher(request, postHandler);
} else if ("DELETE".equals(request.method())) {
handler = findMatcher(request, deleteHandler);
}
handleRequest(request, response, handler);
}
然后调用了findMatcher方法
protected BaseRequestHandler findMatcher(IHttpRequest request, Map
handler) {
String[] urlToMatchSections = getRequestUrlSections(request.uri());
for (Map.Entryentry : handler.entrySet()) {
String[] mapperUrlSections = getMapperUrlSectionsCached(entry.getKey());
if (isFor(mapperUrlSections, urlToMatchSections)) {
return entry.getValue();
}
}
return null;
}
经过getRequestUrlSections、getMapperUrlSectionsCached、isFor一系列方法处理后,拿到请求的uri对比后找到了执行的Handler,然后执行了handleRequest方法,先经过addHandlerAttributesToRequest方法对请求数据的处理,然后调用了handle的handle方法(如new Status(),就调用了Status的handle的方法),最终执行了handleResponse返回结果。