# Multi-Instance Android Emulator Farming with Device Groups

> Multi-instance Android emulator farming in MAS: add each BlueStacks or LDPlayer instance by port, group them, and give every device its own profile and proxy.

Source: https://automationmacro.com/docs/guides/multi-instance (Guides, updated 2026-09-04)

One macro, five accounts, one click: that is multi-instance Android emulator farming. Macro Automation Studio (MAS) treats every emulator instance as a separate device. A device group then runs the same macro on all of them, with per-device arguments, a proxy each, and storage that never mixes counters. This guide builds that setup on BlueStacks, LDPlayer, MuMu Player or MEmu. It is for anyone who runs more than one account.

## Before you start

- MAS is installed and signed in with a plan whose device count covers the instances you want to run at once. See [Billing](/docs/billing).
- The emulator's multi-instance manager has created the instances and each one has ADB enabled. The [BlueStacks](/docs/bluestacks-setup-guide), [LDPlayer](/docs/ldplayer-setup-guide), [MuMu Player](/docs/mumu-setup-guide) and [MEmu](/docs/memu-setup-guide) guides show where the setting and the port live.
- Every instance uses the same resolution and DPI as the one you captured templates on.
- The macro works on one instance from the Code Editor. See the [loops guide](/docs/guides/loops-and-scheduling).

## Each instance of a multi-instance Android emulator is a device

An emulator instance listens for adb on its own local TCP port. MAS finds instances by scanning running processes and keeping the ports inside each emulator's known range. Two BlueStacks windows therefore show up as two entries in the **Port** list. The device is the port; the group is the list of ports.

| Emulator | Ports MAS accepts |
|---|---|
| BlueStacks | 5555 to 8500 |
| LDPlayer | Odd ports from 5555 to 5599 |
| MuMu Player | Odd ports 5555 to 5599, 7555, 16384 to 16576, 26624 to 27264 |
| MEmu | 21503 and every port ending in 3 above it, below 30000 |

Keep every instance open while you add it; a stopped instance has no port to find. The [Devices](/docs/devices) page has the full discovery rules.

## Create a device group

1. Open **Device Groups** and click **Create New Group**.
2. Enter a **Name**, keep the type **Local**, and click **Create Group**.
3. Open the group and click **Add Device**.
4. Enter a **Device Name** that says which account it is, for example `farm-alt-2`.
5. Pick the instance's port under **Port**. Click **Refresh** if it is missing, or choose **Custom Port** and type it.
6. Click **Add Device** and repeat for every instance.
7. On each device card, choose the macro in the **Macro** selector.

A port can belong to one device only. Adding it twice fails with "Port is already in use". See [Device groups](/docs/device-groups).

## Give each device its own arguments

Most bots need one value that differs per account: a name, a target, a number of rounds. There are two ways to pass it.

**Settings profiles (no code changes).** If the macro has an argument form from the UI Builder, each device card has **Edit Arguments**. The dialog carries a **Profile** bar: enter values, choose **Save current values as a new profile**, name it and click **Create profile**. A device follows one profile, and editing the profile updates every device that follows it on its next run. "Default" always exists and cannot be deleted. See [Settings profiles](/docs/settings-profiles) and [UI Builder](/docs/ui-builder).

**The port in code.** The script can ask which instance it is on and pick its own settings. This needs no form and is what the example at the end does.

```python
import mas

port = mas.get_current_device_port()   # 5555, 5557, 5559 ...
```

Use profiles when the values change often or someone else runs the bot; use the port when the mapping is fixed and lives with the code.

## Give each device its own proxy

Accounts that share an IP share a fate. MAS attaches one proxy per device and routes that instance's traffic through it.

1. Open **Proxies**. Under **Add a proxy**, fill in the **Gateway account** tab, or the **Another provider** tab for a proxy of your own (`http`, `https` or `socks5`), and click **Add & test**.
2. Wait for the test. It confirms the proxy answers and detects its country; **Test** repeats it later.
3. Click **Assign** and choose the device, or open the device card in **Device Groups** and pick the proxy under **Proxy**.

The device card shows the proxy state on every run, and run webhooks carry `proxy_exit_ip` and `proxy_country`. A device holds one proxy; attaching another replaces it. See [Proxies](/docs/proxies).

## Start all: multi-instance sync on LDPlayer or BlueStacks

Click **Start All** at the top of the group. MAS starts the devices one after another, half a second apart, each with its own run, its own log and its own exit code. A device without a macro is skipped with an error and the rest continue. **Stop All** ends every run in the group. The **Logs** tab on each card shows that device only, and the **Dashboard** tab shows its runtime UI if the macro has one.

## Keep per-device state with storage

`mas.save`, `mas.retrieve` and `mas.clear` are keyed by this computer, the device's port and a task name. Two instances running the same macro therefore keep separate counters with no extra code. To add them up, `mas.retrieve_all(task_name)` returns one entry per port with `machine_id`, `port`, `data` and `updated_at`.

```python
TASK = "collector"

mine = mas.retrieve(TASK)                          # this instance only
everyone = mas.retrieve_all(TASK)                  # every instance on this computer
total = sum(entry["data"].get("done", 0) for entry in everyone)
```

See [Storage](/docs/sdk/storage).

## Webhooks per group

To be told when any account finishes or fails, add an endpoint on the **Webhooks** page, choose the events under **Send me**, and under **Send me events from** pick **Specific groups** and tick the group. Devices you add to the group later are covered too. Each event carries `device_name`, `device_port`, `group_id`, `status` and `exit_code`, so one endpoint can tell the accounts apart. See [Webhooks](/docs/webhooks).

## Watch the resource limits of a BlueStacks bot farm

Every instance is a full Android system. Before you scale, check three things:

- **CPU and RAM per instance.** Each instance takes the cores and memory set in the emulator's instance settings. Add them up and leave headroom for MAS, which runs template matching and OCR for every instance on the same machine. When taps start landing late, you are over.
- **Devices on your plan.** A plan is sized by how many of your own devices can run at the same time. **Start All** runs as many as the plan allows; see [Billing](/docs/billing) and the [pricing page](/pricing).
- **Scheduled jobs.** The Scheduler runs up to 20 jobs at once, one per port.

Fewer instances running cleanly beat more instances missing templates.

## Schedule the group

The Scheduler targets one port per job, so a group schedule is one job per device.

1. Open **Scheduler** and click **Create New Schedule**.
2. Pick the **Macro**, click **Scan** under **Emulator Port** and choose the first instance.
3. Set **Date**, **Time** and **Recurrence**, then click **Create Schedule**.
4. Repeat for each port. Give the jobs the same time; different ports never collide.

Scheduled runs use the arguments saved on each device card, or the profile the device follows, so per-account values carry over. The app must stay open. See [Scheduler](/docs/scheduler) and the [loops guide](/docs/guides/loops-and-scheduling).

## A complete example

The macro reads its port, picks the account's settings, keeps a per-port counter, and logs the total across every instance at the end. Replace the ports and image IDs with yours.

```python
import random
import sys
import time

import mas

images = mas.images({
    "collect": 501,
    "close_popup": 502,
})

SETTINGS = {   # per instance, keyed by the emulator's adb port
    5555: {"account": "main", "rounds": 20},
    5557: {"account": "alt-1", "rounds": 10},
    5559: {"account": "alt-2", "rounds": 10},
}
DEFAULT = {"account": "unknown", "rounds": 5}
TASK = "collector"
TIME_BUDGET_S = 15 * 60


def main():
    port = mas.get_current_device_port()
    settings = SETTINGS.get(port, DEFAULT)
    account = settings["account"]
    mas.log(f"Port {port}: account {account}, {settings['rounds']} rounds")

    done = mas.retrieve(TASK).get("done", 0)
    started = time.monotonic()
    misses = 0

    while done < settings["rounds"]:
        if time.monotonic() - started > TIME_BUDGET_S:
            mas.log(f"{account}: time budget reached", level="warning")
            break
        popup = mas.find_object(images.close_popup)
        if popup:
            mas.click(popup.x, popup.y, delay_ms=800)
            continue
        button = mas.find_object_retry(images.collect, total_tries=3, time_sleep=2.0)
        if button is None:
            misses += 1
            if misses >= 5:
                mas.log(f"{account}: five misses, giving up", level="error")
                sys.exit(2)
            continue
        misses = 0
        mas.click(button.x, button.y, delay_ms=1000)
        done += 1
        mas.save(TASK, {"done": done, "account": account})
        time.sleep(random.uniform(0.8, 2.5))

    total = sum(entry["data"].get("done", 0) for entry in mas.retrieve_all(TASK))
    mas.log(f"{account}: {done} rounds done; {total} across all instances")
    mas.webhook("account.finished", {"account": account, "port": port, "done": done})


if __name__ == "__main__":
    main()
```

`mas.webhook` sends a `custom.account.finished` event to endpoints subscribed to custom events; leave the line out if you have none. When the mapping grows, move it into an argument form and read `args.<tab>.<key>` instead of the dictionary.

## What can go wrong

### Port is already in use

Another device in one of your groups owns that port. Each port can be added once; remove the old entry or pick the other instance's port.

### One instance runs, the others stay on Connecting

The other instances have ADB off, are still booting, or listen on ports outside the emulator's range. Open each instance's settings, confirm ADB and the port, and click **Refresh** in **Add Device**. If the port sits outside the table above, use **Custom Port**. See [ADB troubleshooting](/docs/adb-troubleshooting).

### Templates match on one instance only

That instance has a different resolution or DPI. Template matching is pixel based; set every instance to the capture resolution in the emulator's instance settings and restart it. The [image recognition guide](/docs/guides/image-recognition-macros) explains the rest.

### Every instance shares one counter

The script passes an explicit `port` or `machine_id` to `mas.save`, or uses a file on disk instead of storage. Call `mas.save(TASK, data)` with the defaults; the current port is added for you.

No automation tool is 100% risk-free, so automate responsibly and at your own discretion.
