Skip to content

Commit 4e7c12d

Browse files
authored
Android SDK (#183)
* Android SDK * Run Android Unit Tests * Fix sample app * Bonjour service is working * Update main.yml * Update main.yml * Websocket for OKHTTP * Create publish.sh
1 parent ca08192 commit 4e7c12d

45 files changed

Lines changed: 5470 additions & 106 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/main.yml

Lines changed: 53 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -9,125 +9,72 @@ on:
99
- main
1010

1111
jobs:
12-
build-and-test:
12+
swiftpm-test:
13+
name: SwiftPM Tests
1314
runs-on: macos-15
14-
strategy:
15-
fail-fast: false
16-
matrix:
17-
include:
18-
- xcode: "16.2"
19-
ios: "18"
2015

2116
steps:
2217
- name: Checkout
2318
uses: actions/checkout@v4
2419

25-
- name: Select Xcode ${{ matrix.xcode }}
26-
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
20+
- name: Select latest Xcode
21+
uses: maxim-lobanov/setup-xcode@v1
22+
with:
23+
xcode-version: latest-stable
2724

2825
- name: Show Xcode and Swift version
2926
run: |
3027
xcodebuild -version
3128
swift --version
3229
33-
- name: List available simulators
34-
run: xcrun simctl list devices available
30+
- name: Cache SwiftPM
31+
uses: actions/cache@v4
32+
with:
33+
path: |
34+
.build
35+
~/Library/Caches/org.swift.swiftpm
36+
key: ${{ runner.os }}-swiftpm-${{ hashFiles('Package.swift', 'Package.resolved') }}
37+
restore-keys: |
38+
${{ runner.os }}-swiftpm-
3539
36-
- name: List SwiftPM schemes
37-
run: xcodebuild -workspace .swiftpm/xcode/package.xcworkspace -list
40+
- name: Run SwiftPM Tests
41+
run: swift test
3842

39-
- name: Install xcpretty
40-
run: sudo gem install xcpretty
43+
android-test:
44+
name: Android Tests
45+
runs-on: ubuntu-latest
4146

42-
- name: Select iOS simulator for ${{ matrix.ios }}
43-
env:
44-
IOS_VERSION: ${{ matrix.ios }}
45-
run: |
46-
set -euo pipefail
47-
RUNTIME_JSON=$(xcrun simctl list runtimes --json 2>/dev/null || true)
48-
if [ -z "$RUNTIME_JSON" ]; then
49-
echo "Failed to read simctl runtimes JSON"
50-
xcrun simctl list runtimes || true
51-
exit 1
52-
fi
53-
export RUNTIME_JSON
54-
RUNTIME_ID=$(python3 - <<'PY'
55-
import json, os, sys
56-
data = json.loads(os.environ["RUNTIME_JSON"])
57-
target = os.environ["IOS_VERSION"]
58-
runtimes = [
59-
r for r in data.get("runtimes", [])
60-
if r.get("platform") == "iOS"
61-
and r.get("isAvailable")
62-
and (
63-
r.get("version", "") == target
64-
or r.get("version", "").startswith(target + ".")
65-
)
66-
]
67-
if not runtimes:
68-
print(f"Missing iOS runtime for {target}", file=sys.stderr)
69-
sys.exit(1)
70-
print(runtimes[0]["identifier"])
71-
PY
72-
)
73-
export RUNTIME_ID
74-
DEVICE_JSON=$(xcrun simctl list devices --json 2>/dev/null || true)
75-
if [ -z "$DEVICE_JSON" ]; then
76-
echo "Failed to read simctl devices JSON"
77-
xcrun simctl list devices || true
78-
exit 1
79-
fi
80-
export DEVICE_JSON
81-
DEVICE_ID=$(python3 - <<'PY'
82-
import json, os, sys
83-
data = json.loads(os.environ["DEVICE_JSON"])
84-
runtime = os.environ["RUNTIME_ID"]
85-
devices = data.get("devices", {}).get(runtime, [])
86-
for device in devices:
87-
if device.get("isAvailable") and "iPhone 16" in device.get("name", ""):
88-
print(device["udid"])
89-
sys.exit(0)
90-
for device in devices:
91-
if device.get("isAvailable") and "iPhone" in device.get("name", ""):
92-
print(device["udid"])
93-
sys.exit(0)
94-
print("")
95-
PY
96-
)
97-
if [ -z "$DEVICE_ID" ]; then
98-
DEVICE_TYPES_JSON=$(xcrun simctl list devicetypes --json 2>/dev/null || true)
99-
if [ -z "$DEVICE_TYPES_JSON" ]; then
100-
echo "Failed to read simctl device types JSON"
101-
xcrun simctl list devicetypes || true
102-
exit 1
103-
fi
104-
export DEVICE_TYPES_JSON
105-
DEVICE_TYPE=$(python3 - <<'PY'
106-
import json, sys, os
107-
data = json.loads(os.environ["DEVICE_TYPES_JSON"])
108-
devicetypes = [d for d in data.get("devicetypes", []) if d.get("name", "").startswith("iPhone")]
109-
for device in devicetypes:
110-
if device.get("name") == "iPhone 16":
111-
print(device["identifier"])
112-
sys.exit(0)
113-
if devicetypes:
114-
print(devicetypes[0]["identifier"])
115-
sys.exit(0)
116-
print("", end="")
117-
sys.exit(1)
118-
PY
119-
)
120-
DEVICE_ID=$(xcrun simctl create "CI-iPhone-${IOS_VERSION}" "$DEVICE_TYPE" "$RUNTIME_ID")
121-
fi
122-
echo "SIMULATOR_ID=$DEVICE_ID" >> "$GITHUB_ENV"
47+
steps:
48+
- name: Checkout
49+
uses: actions/checkout@v4
12350

124-
- name: Build and Test on iOS ${{ matrix.ios }}
125-
run: |
126-
set -o pipefail
127-
xcodebuild test \
128-
-workspace .swiftpm/xcode/package.xcworkspace \
129-
-scheme Atlantis \
130-
-destination "id=${SIMULATOR_ID}" \
131-
-skipPackagePluginValidation \
132-
-skipMacroValidation \
133-
| xcpretty --color
51+
- name: Set up JDK 17
52+
uses: actions/setup-java@v4
53+
with:
54+
java-version: '17'
55+
distribution: 'temurin'
56+
57+
- name: Cache Gradle packages
58+
uses: actions/cache@v4
59+
with:
60+
path: |
61+
~/.gradle/caches
62+
~/.gradle/wrapper
63+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
64+
restore-keys: |
65+
${{ runner.os }}-gradle-
66+
67+
- name: Grant execute permission for gradlew
68+
working-directory: atlantis-android
69+
run: chmod +x gradlew
70+
71+
- name: Run Android Unit Tests
72+
working-directory: atlantis-android
73+
run: ./gradlew :atlantis:test --no-daemon
74+
75+
- name: Upload Test Results
76+
if: always()
77+
uses: actions/upload-artifact@v4
78+
with:
79+
name: android-test-results
80+
path: atlantis-android/atlantis/build/reports/tests/

README.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [x] ✅ Capture WS/WSS Traffic from URLSessionWebSocketTask
1919
- [x] Capture gRPC traffic (Advanced)
2020
- [x] Support iOS Physical Devices and Simulators, including iPhone, iPad, Apple Watch, Apple TV
21+
- [x] **NEW:** Support Android with OkHttp, Retrofit, and Apollo
2122
- [x] Review traffic log from macOS [Proxyman](https://proxyman.com) app ([Github](https://github.com/ProxymanApp/Proxyman))
2223
- [x] Categorize the log by project and devices.
2324
- [x] Ready for Production
@@ -29,11 +30,23 @@
2930
- If you want to use debugging tools, please use normal Proxy.
3031

3132
## Requirement
33+
34+
### iOS
3235
- macOS Proxyman app
3336
- iOS 16.0+ / macOS 11+ / Mac Catalyst 13.0+ / tvOS 13.0+ / watchOS 10.0+
3437
- Xcode 14+
3538
- Swift 5.0+
3639

40+
### Android
41+
- macOS Proxyman app
42+
- Android API 26+ (Android 8.0 Oreo)
43+
- OkHttp 4.x or 5.x
44+
- Kotlin 1.9+
45+
46+
---
47+
48+
# iOS Integration
49+
3750
## 👉 How to use
3851
### 1. Install Atlantis framework
3952
### Swift Packages Manager (Recommended)
@@ -472,6 +485,160 @@ Atlantis.start()
472485

473486
</details>
474487

488+
---
489+
490+
# Android Integration
491+
492+
Atlantis for Android captures HTTP/HTTPS traffic from OkHttp (including Retrofit and Apollo) and sends it to Proxyman for debugging.
493+
494+
## 1. Install Atlantis Android
495+
496+
### Gradle (Kotlin DSL)
497+
498+
Add to your app's `build.gradle.kts`:
499+
500+
```kotlin
501+
dependencies {
502+
debugImplementation("com.proxyman:atlantis-android:1.0.0")
503+
504+
// You must include OkHttp in your project
505+
implementation("com.squareup.okhttp3:okhttp:4.12.0")
506+
}
507+
```
508+
509+
### Gradle (Groovy)
510+
511+
```groovy
512+
dependencies {
513+
debugImplementation 'com.proxyman:atlantis-android:1.0.0'
514+
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
515+
}
516+
```
517+
518+
### JitPack (Alternative)
519+
520+
Add JitPack repository to your `settings.gradle.kts`:
521+
522+
```kotlin
523+
dependencyResolutionManagement {
524+
repositories {
525+
maven { url = uri("https://jitpack.io") }
526+
}
527+
}
528+
```
529+
530+
Then add the dependency:
531+
532+
```kotlin
533+
debugImplementation("com.github.ProxymanApp:atlantis:1.0.0")
534+
```
535+
536+
## 2. Initialize Atlantis
537+
538+
### In your Application class
539+
540+
```kotlin
541+
import android.app.Application
542+
import com.proxyman.atlantis.Atlantis
543+
544+
class MyApplication : Application() {
545+
override fun onCreate() {
546+
super.onCreate()
547+
548+
// Only enable in debug builds
549+
if (BuildConfig.DEBUG) {
550+
// Simple start - discovers all Proxyman apps on network
551+
Atlantis.start(this)
552+
553+
// Or with specific hostname (find it in Proxyman -> Certificate menu)
554+
// Atlantis.start(this, "MacBook-Pro.local")
555+
}
556+
}
557+
}
558+
```
559+
560+
## 3. Add Interceptor to OkHttpClient
561+
562+
```kotlin
563+
import com.proxyman.atlantis.Atlantis
564+
import okhttp3.OkHttpClient
565+
566+
// Create OkHttpClient with Atlantis interceptor
567+
val okHttpClient = OkHttpClient.Builder()
568+
.addInterceptor(Atlantis.getInterceptor())
569+
.build()
570+
```
571+
572+
### With Retrofit
573+
574+
```kotlin
575+
import retrofit2.Retrofit
576+
import retrofit2.converter.gson.GsonConverterFactory
577+
578+
val retrofit = Retrofit.Builder()
579+
.baseUrl("https://api.example.com/")
580+
.client(okHttpClient) // Use the OkHttpClient with Atlantis
581+
.addConverterFactory(GsonConverterFactory.create())
582+
.build()
583+
```
584+
585+
### With Apollo Kotlin
586+
587+
```kotlin
588+
import com.apollographql.apollo3.ApolloClient
589+
590+
val apolloClient = ApolloClient.Builder()
591+
.serverUrl("https://api.example.com/graphql")
592+
.okHttpClient(okHttpClient) // Use the OkHttpClient with Atlantis
593+
.build()
594+
```
595+
596+
## 4. Required Permissions
597+
598+
Atlantis requires these permissions (automatically added by the library):
599+
600+
```xml
601+
<uses-permission android:name="android.permission.INTERNET" />
602+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
603+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
604+
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
605+
```
606+
607+
## 5. Start Debugging
608+
609+
1. Open **Proxyman** on your Mac
610+
2. Make sure your Android device/emulator and Mac are on the **same Wi-Fi network**
611+
- For emulators: Atlantis automatically connects to `10.0.2.2:10909`
612+
- For physical devices: Uses Network Service Discovery (NSD/mDNS)
613+
3. Run your Android app
614+
4. All HTTP/HTTPS traffic will appear in Proxyman!
615+
616+
## Android Sample App
617+
618+
A sample Android app is included in `atlantis-android/sample/`. To run it:
619+
620+
1. Open `atlantis-android/` in Android Studio
621+
2. Run the `sample` module
622+
3. Tap the buttons to make network requests
623+
4. View the traffic in Proxyman
624+
625+
## Android Troubleshooting
626+
627+
### Traffic not appearing in Proxyman?
628+
629+
1. **Emulator**: Make sure Proxyman is running on your Mac. Atlantis connects to `10.0.2.2:10909`.
630+
631+
2. **Physical device**:
632+
- Ensure both devices are on the same Wi-Fi network
633+
- Try specifying the hostname: `Atlantis.start(this, "Your-Mac.local")`
634+
635+
3. **Check logs**: Look for `[Atlantis]` logs in Logcat for connection status.
636+
637+
### OkHttp version compatibility
638+
639+
Atlantis supports OkHttp 4.x and 5.x. If you're using an older version, please upgrade.
640+
641+
---
475642

476643
## FAQ
477644
#### 1. How does Atlantis work?

0 commit comments

Comments
 (0)