Other languages
Use Droidline from Go, Java, C#, PHP or any other language, over HTTP or a raw socket that carries one JSON line per command, with tested examples.
There is no SDK for every language, and you do not need one. The server accepts commands two ways that any language can use:
- HTTP: one request per command. The simplest choice; see the HTTP guide for every endpoint.
- A raw socket: open one TCP connection to
127.0.0.1:8780, write one JSON object per line, read one JSON object per line back. Better for long-running programs and the only way to receive events such as notifications.
Every example on this page was run against droidline-fakephone. Each opens the demo app, tries to tap an element that is not there to show error handling, and reads a value.
Over HTTP
// Java 11 or later. Run with: java Droid.java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Droid {
static final HttpClient http = HttpClient.newHttpClient();
// Runs one command on the only online phone and returns the JSON reply as text.
static String run(String command, String jsonBody) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("http://localhost:8780/devices/_/" + command))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
return res.statusCode() + " " + res.body();
}
public static void main(String[] args) throws Exception {
System.out.println(run("launch", "{\"package\":\"dev.droidline.demo\"}"));
System.out.println(run("input", "{\"by\":\"id\",\"value\":\"email\",\"text\":\"knife\"}"));
System.out.println(run("touch", "{\"by\":\"text\",\"value\":\"Nope\",\"timeout\":1}"));
}
}// .NET 6 or later. Run with: dotnet run droid.cs (.NET 10), or paste into Program.cs
using System.Text;
using System.Text.Json.Nodes;
var http = new HttpClient { BaseAddress = new Uri("http://localhost:8780/") };
// Runs one command on the only online phone and returns the parsed reply.
async Task<JsonNode> Run(string command, JsonObject body)
{
var content = new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json");
var res = await http.PostAsync($"devices/_/{command}", content);
return JsonNode.Parse(await res.Content.ReadAsStringAsync())!;
}
Console.WriteLine(await Run("launch", new() { ["package"] = "dev.droidline.demo" }));
var r = await Run("touch", new() { ["by"] = "text", ["value"] = "Nope", ["timeout"] = 1 });
if (!(bool)r["ok"]!)
Console.WriteLine($"{r["error"]}: {r["msg"]}");
var title = await Run("get_text", new() { ["by"] = "text", ["value"] = "Sign in" });
Console.WriteLine(title["value"]);<?php
// Runs one command on the only online phone and returns the decoded reply.
function droid(string $command, array $params = []): array {
$context = stream_context_create(["http" => [
"method" => "POST",
"header" => "Content-Type: application/json",
"content" => json_encode((object) $params),
"ignore_errors" => true, // keep the JSON body of 4xx and 5xx replies
]]);
$body = file_get_contents("http://localhost:8780/devices/_/$command", false, $context);
return json_decode($body, true);
}
print_r(droid("launch", ["package" => "dev.droidline.demo"]));
$r = droid("touch", ["by" => "text", "value" => "Nope", "timeout" => 1]);
if (!$r["ok"]) {
echo $r["error"], ": ", $r["msg"], "\n";
}
echo droid("current")["package"], "\n";What these have in common, and what to copy into any other language:
POSTtohttp://localhost:8780/devices/_/<command>, or put a phone's name in place of_.- Send the header
Content-Type: application/jsonand a JSON object with the command's parameters. - Read
okfrom the reply. If it is false,errorholds the code andmsga readable message; the HTTP status is 4xx or 5xx, so make sure your HTTP library still gives you the body.
Over a raw socket
The socket protocol is plain text: each request and each reply is one JSON object on one line, ending with a newline. You choose an id for each request and the reply carries it back, which lets you send several requests without waiting:
→ {"id":1,"cmd":"launch","package":"dev.droidline.demo"}
← {"id":1,"ok":true,"ms":612}
→ {"id":2,"cmd":"touch","by":"text","value":"Nope","timeout":1}
← {"id":2,"ok":false,"error":"NOT_FOUND","msg":"Could not find text 'Nope' within 1s. ...","retryable":true}
package main
import (
"bufio"
"encoding/json"
"fmt"
"net"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8780")
if err != nil {
panic(err) // is droidline serve running?
}
defer conn.Close()
in := bufio.NewReader(conn)
// send writes one request line and reads one reply line.
send := func(req map[string]any) map[string]any {
line, _ := json.Marshal(req)
conn.Write(append(line, '\n'))
reply, err := in.ReadBytes('\n')
if err != nil {
panic(err)
}
var out map[string]any
json.Unmarshal(reply, &out)
return out
}
fmt.Println(send(map[string]any{"id": 1, "cmd": "launch", "package": "dev.droidline.demo"}))
r := send(map[string]any{"id": 2, "cmd": "touch", "by": "text", "value": "Nope", "timeout": 1})
if r["ok"] != true {
fmt.Println("failed:", r["error"], r["msg"])
}
fmt.Println(send(map[string]any{"id": 3, "cmd": "current"})["package"])
}# The SDK does this for you; this is what happens underneath.
import json, socket
s = socket.create_connection(("127.0.0.1", 8780))
f = s.makefile("rw", encoding="utf-8")
def send(req):
f.write(json.dumps(req) + "\n")
f.flush()
return json.loads(f.readline())
print(send({"id": 1, "cmd": "launch", "package": "dev.droidline.demo"}))
print(send({"id": 2, "cmd": "touch", "by": "text", "value": "Nope", "timeout": 1}))The example above reads one reply after each request, which is the simplest pattern. The full rules:
- The request fields are the command's parameters plus
id,cmdand optionallydevice(a phone's name or ID). - Replies to commands for one phone come back in order. Replies for different phones can arrive in any order, so match them by
id. - After
{"id":9,"cmd":"subscribe","events":["notification"]}, lines that start with{"event":...}arrive between replies. Your reader should tell the two apart by theeventfield. GET /wson the same port carries the same lines over a WebSocket, for languages where that is easier than TCP.
Protocol describes every field, the events, and how network-cutting commands report their final result.
Which one to choose
| HTTP | Raw socket | |
|---|---|---|
| Effort | Lowest: any HTTP library | A little: keep a connection and read lines |
| Speed | One connection per command | One connection for everything |
| Several commands at once | Several requests in parallel | Pipelined on one connection |
| Events (notifications, devices going offline) | No | Yes, after subscribe |
Start with HTTP. Switch to the socket when you need events or many commands per second.