Mobile Development 27 min read

How to Build a Scalable Android Ad‑Monitoring System with Multi‑Device Automation

This article details the design and implementation of an Android ad‑monitoring platform that controls multiple devices concurrently, automates app interactions, uses OCR for ad detection, and provides real‑time status monitoring via a floating window, while covering architecture, core modules, communication strategies, and performance optimizations.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
How to Build a Scalable Android Ad‑Monitoring System with Multi‑Device Automation

Project Background and Overall Architecture

Manual ad monitoring is inefficient, especially with the rapid growth of mobile apps. The Android Ad Monitoring System was created to automate detection of splash and feed ads, improve accuracy, performance, and cross‑device compatibility, and provide real‑time status feedback.

Core Functional Modules

1. Device Management and Multi‑Process Concurrency

The system uses Python’s multiprocessing module to launch a separate process for each connected Android device, ensuring independent, non‑blocking ad‑monitoring tasks.

# === Startup entry: multi‑device concurrent execution ===
if __name__ == "__main__":
    # Get all connected devices
    all_devices = utils.get_connected_devices()
    if not all_devices:
        print("❌ No physical devices detected")
        exit(1)
    # Create a process per device
    processes = []
    for device_id in all_devices:
        p = Process(target=ad_monitor.run_on_device, args=(device_id,))
        p.start()
        processes.append(p)
    # Wait for all processes to finish
    for p in processes:
        p.join()

This multi‑process approach avoids the Global Interpreter Lock (GIL) and provides better stability than multi‑threading for I/O‑heavy operations.

2. Device Control and Application Automation

ADB is used for low‑level device actions (wake, unlock, launch apps). The uiautomator2 library handles in‑app interactions such as swipes and clicks.

# Device operation methods
def wake_and_unlock(device_id):
    subprocess.run(["adb", "-s", device_id, "shell", "input", "keyevent", "224"])
    subprocess.run(["adb", "-s", device_id, "shell", "wm", "dismiss-keyguard"])

def lock_screen(device_id):
    subprocess.run(["adb", "-s", device_id, "shell", "input", "keyevent", "26"], stdout=subprocess.DEVNULL)
def scroll_half_screen(device):
    try:
        size = device.window_size()
        start_x = size[0] // 2
        start_y = size[1] * 3 // 4
        end_y = size[1] // 4
        device.swipe(start_x, start_y, start_x, end_y, 0.2)
        time.sleep(1)
    except Exception as e:
        print(f"⚠️ Swipe failed: {e}")

3. Ad Recognition with OCR

Captured screenshots are processed by an OCR engine; extracted text is matched against predefined keyword lists to determine whether an ad is present.

# OCR detection function
def ocr_detect_ad(image_path, keywords):
    try:
        text = fuOCR.getTextFromImage(image_path)
        for keyword in keywords:
            if keyword in str(text):
                return True, keyword, str(text)
        return False, None, None
    except Exception as e:
        print(f"⚠️ OCR failed: {e}")
        return False, None, None
# Splash ad detection example
def capture_splash_ad(device, app_name, save_dir, i, log_file, device_id):
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    temp_filepath = os.path.join(save_dir, f"{app_name}_splash_temp_{timestamp}_{i+1}.png")
    log_message(log_file, f"📸 Screenshot saved: {temp_filepath}")
    device.screenshot(temp_filepath)
    is_ad, keyword, txt = ocr_detect_ad(temp_filepath, config.SPLASH_AD_KEYWORDS)
    if is_ad:
        log_message(log_file, f"🔍 OCR text: {txt}")
        notify_status(device_id, "found_ad", f"Found splash AD in {app_name}")
        upload_and_delete(temp_filepath, {"media": app_name, "position_type": "splash", "os": "Android", "detected_keyword": keyword}, log_file)
        log_message(log_file, f"✅ Detected splash ad (keyword: {keyword}) (attempt {i+1})")
        return True
    else:
        log_message(log_file, f"⚠️ No splash ad detected (attempt {i+1})")
        try:
            os.remove(temp_filepath)
        except Exception as e:
            log_message(log_file, f"⚠️ Failed to delete temp file: {e}")
        return False

4. Data Flow Management and File System Design

Screenshots and logs are stored in a layered directory structure organized by device model and timestamp. Automatic cleanup removes files older than a configurable retention period.

# Path configuration
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_DIR = os.path.join(BASE_DIR, "./Android_log")
SAVE_ROOT = os.path.join(BASE_DIR, "./Android_screenshort")
TEMP_DIR = os.path.join(SAVE_ROOT, "temp")
RETENTION_TIME = timedelta(days=2)  # Production setting

5. Status Monitoring and Cross‑Process Communication

A floating window on each device displays real‑time status (idle, detecting, ad found, uploading, completed, error). Communication between the Python monitoring script and the Android overlay uses ADB broadcast as the primary channel, falling back to a lightweight socket when needed.

# Send status notification via ADB broadcast (fallback to socket)
def notify_status(device_id, status, message):
    try:
        status_message = f"STATUS|{device_id}|{status}|{message}"
        broadcast_cmd = ["adb", "-s", device_id, "shell", "am", "broadcast", "-a", "com.monitor.statuswindow.UPDATE_STATUS", "--es", "message", status_message]
        result = subprocess.run(broadcast_cmd, capture_output=True, text=True)
        if "Broadcasting: Intent" in result.stdout:
            print(f"✅ Sent broadcast: {status_message}")
            return True
        else:
            print("⚠️ Broadcast failed, trying socket")
            try:
                with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                    s.settimeout(1)
                    s.connect(("localhost", 8888))
                    s.sendall(status_message.encode('utf-8'))
                print(f"✅ Sent socket message: {status_message}")
                return True
            except Exception as e:
                print(f"❌ Socket send failed: {e}")
                return False
    except Exception as e:
        print(f"❌ Notification error: {e}")
        return False
// Android side: BroadcastReceiver (Kotlin)
class StatusBroadcastReceiver(private val statusListener: StatusServer.StatusListener) : BroadcastReceiver() {
    private val TAG = "StatusBroadcastReceiver"
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == "com.monitor.statuswindow.UPDATE_STATUS") {
            // Extract and handle status message
        }
    }
}

6. Floating Window Design (Android Overlay)

The overlay is built with a Service + WindowManager architecture, using TYPE_APPLICATION_OVERLAY on Android 8.0+ and TYPE_PHONE on older versions. It supports drag‑to‑move, click‑to‑open the main UI, and updates its appearance based on the current status.

// Initialize floating view (Kotlin)
private fun initFloatingView() {
    windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
    floatingView = LayoutInflater.from(this).inflate(R.layout.floating_window, null)
    val layoutParams = WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_PHONE,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        PixelFormat.TRANSLUCENT
    )
    layoutParams.gravity = Gravity.TOP or Gravity.START
    layoutParams.x = 0
    layoutParams.y = 100
    // Add view and set touch listeners …
}
// Touch handling for drag and click (Kotlin)
floatingView.setOnTouchListener { _, event ->
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            initialX = event.rawX.toInt()
            initialY = event.rawY.toInt()
            lastX = initialX
            lastY = initialY
        }
        MotionEvent.ACTION_MOVE -> {
            val dx = event.rawX.toInt() - lastX
            val dy = event.rawY.toInt() - lastY
            layoutParams.x += dx
            layoutParams.y += dy
            windowManager.updateViewLayout(floatingView, layoutParams)
            lastX = event.rawX.toInt()
            lastY = event.rawY.toInt()
        }
        MotionEvent.ACTION_UP -> {
            val distanceX = Math.abs(event.rawX.toInt() - initialX)
            val distanceY = Math.abs(event.rawY.toInt() - initialY)
            if (distanceX < 10 && distanceY < 10) {
                val intent = Intent(this, MainActivity::class.java)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
            }
        }
    }
    true
}

7. System Scheduling and Workflow

The workflow for each app includes device preparation, app launch, splash‑ad detection, feed‑ad detection with simulated scrolling, optional channel switching, result handling (save & upload), and device cleanup. Detailed logging and status updates keep operators informed.

8. Optimization and Performance Tuning

Key improvements include faster device detection, pre‑loading OCR models, asynchronous initialization, aggressive temporary‑file cleanup, and a hybrid communication scheme that reduces CPU and memory usage. Reported gains: ~40% faster startup, ~30% lower memory footprint, ~25% reduced CPU load, and smoother operation on low‑end Windows machines.

9. Future Directions

Integrate deep‑learning‑based ad recognition to reduce reliance on keyword matching.

Add real‑time analytics for collected ad data.

Migrate to a cloud‑native architecture for scalability.

Develop a mobile management app for remote control.

Extend support to iOS and other platforms.

AndroidAutomationOCRADBFloating WindowmultiprocessingAd Monitoring
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.