Mobile Development 14 min read

Ensuring a Single Instance of the Main Activity in Android Using a Dispatcher Activity

To guarantee a single persistent main Activity while allowing sub‑Activities and third‑party navigation, the article proposes routing all external and internal launches through a dedicated DispatcherActivity that uses FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP, a static weak reference, and onNewIntent handling, avoiding singleTask/instance modes.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Ensuring a Single Instance of the Main Activity in Android Using a Dispatcher Activity

Background Some Android apps need a permanent main (home) Activity that stays beneath all other sub‑Activities. The sub‑Activities should always be able to return to this main Activity, similar to a desktop environment. Implementing this with only fragments leads to complex fragment‑stack management, so a hybrid approach using Activities is preferred. The goal is to guarantee that only one instance of the main Activity is ever created.

Choosing the Launch Mode for the Main Activity Android provides four launch modes:

standard – creates a new instance each time.

singleTop – reuses the instance only when it is already at the top of the stack.

singleTask – keeps a single instance in a task and clears the top of the stack on re‑launch.

singleInstance – a dedicated task for the Activity.

standard and singleTop do not satisfy the requirement. singleTask and singleInstance keep a single instance but cause the app to return to the main Activity when launched from the home screen, breaking the desired user experience. Therefore, singleTop is chosen, but it introduces three problems:

Third‑party apps can start the main Activity with uncontrolled Intent flags.

Third‑party apps can start sub‑Activities directly.

If a sub‑Activity exists, launching the main Activity again still creates a new instance.

Introducing a Unified Dispatcher Activity To solve the above issues, a dedicated DispatcherActivity is added to handle all external and internal navigation requests. Its responsibilities are:

Standardize external jump protocols.

Isolate internal Activity navigation from third‑party interference.

Maintain the “home‑screen” behavior by controlling task‑stack restoration and top‑Activity clearing.

The flow is: external request → DispatcherActivity → MainActivity (or target sub‑Activity). The following code shows the initial implementation of DispatcherActivity:

public class DispacherActivity extends Activity {
    private String TAG = "launcher_test_DispacherActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startRouter();
        finish();
    }

    private void startDispacher() {
        Log.i(TAG, " startDispacher ");
        Intent it = new Intent();
        it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        it.setClass(this, MainActivity.class);
        setIntentInfo(it);
        startActivity(it);
    }

    private void setIntentInfo(Intent it) {
        String toActivity = getIntent().getStringExtra("toActivity");
        int dumpTo = DISPACHER_ACTIVITY_MAIN;
        if (!TextUtils.isEmpty(toActivity)) {
            if (toActivity.equals("2")) {
                dumpTo = DISPACHER_ACTIVITY_SECOND;
            } else if (toActivity.equals("3")) {
                dumpTo = DISPACHER_ACTIVITY_THIRD;
            }
        }
        it.putExtra(DISPACHER_PARAM_ACTIVITY, dumpTo);
    }
}

The MainActivity contains a static weak reference to keep the sole instance alive:

public class MainActivity extends Activity {
    private static String TAG = "launcher_test_MainActivity";
    public static WeakReference
instanceOfMainActivity = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, " onCreate " + this);
        doAction();
    }
}

Key points in the Dispatcher implementation:

Intent.FLAG_ACTIVITY_NEW_TASK ensures the MainActivity runs in its own task, separate from the caller.

If the MainActivity already exists, the task is brought to the foreground.

Further refinements address the situation where the same Intent (same action, category, data) is reused, which would otherwise restore the existing task without delivering new parameters. By explicitly setting the Intent action to Intent.ACTION_MAIN and the category to Intent.CATEGORY_LAUNCHER , the dispatcher guarantees consistent task‑stack behavior for both system launches and third‑party calls.

private void startDispacher() {
    Log.i(TAG, " startDispacher ");
    Intent it = new Intent();
    it.setAction(Intent.ACTION_MAIN);
    it.addCategory(Intent.CATEGORY_LAUNCHER);
    it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    it.setClass(this, MainActivity.class);
    setIntentInfo(it);
    startActivity(it);
}

When a sub‑Activity already exists under the MainActivity, the dispatcher adds Intent.FLAG_ACTIVITY_CLEAR_TOP to clear the top of the stack before reusing the MainActivity:

private void startDispacher() {
    Log.i(TAG, " startDispacher ");
    Intent it = new Intent();
    it.setAction(Intent.ACTION_MAIN);
    it.addCategory(Intent.CATEGORY_LAUNCHER);
    it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    it.setClass(this, MainActivity.class);
    setIntentInfo(it);
    startActivity(it);
}

To prevent duplicate initialization of the MainActivity, onCreate checks a static weak reference. If an instance already exists, the newly created Activity finishes itself and forwards the Intent to the existing instance via onNewIntent :

public static WeakReference
instanceOfMainActivity = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.i(TAG, " onCreate " + this);
    if (!isCreated(this)) {
        setContentView(R.layout.activity_main);
        doAction();
    }
}

private static boolean isCreated(MainActivity activity) {
    if (instanceOfMainActivity != null && instanceOfMainActivity.get() != null) {
        Intent it = activity.getIntent();
        MainActivity act = instanceOfMainActivity.get();
        act.onNewIntent((Intent) it.clone());
        activity.finish();
        return false;
    } else {
        instanceOfMainActivity = new WeakReference<>(activity);
        return true;
    }
}

The MainActivity also overrides onNewIntent to handle the forwarded Intent:

@Override
public void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    Log.i(TAG, " onNewIntent" + this);
    setIntent(intent);
    doAction();
}

Additional recommendations:

Set android:exported="false" for sub‑Activities to prevent direct third‑party launches.

Avoid singleTask or singleInstance for non‑main Activities.

Lock the main Activity to portrait orientation or handle configuration changes ( orientation|keyboardHidden|screenSize ) to avoid re‑initialization on rotation.

Configure task‑related attributes such as taskAffinity , alwaysRetainTaskState , allowTaskReparenting , clearTaskOnLaunch , and finishOnTaskLaunch according to the app’s needs.

Summary

The main Activity acts as a home screen; external jumps must first route through a DispatcherActivity.

The main Activity must be instantiated only once without using singleTask or singleInstance launch modes.

All external navigation is funneled through the DispatcherActivity, which normalizes Intent action, category, and flags.

Using FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP together with the launcher category guarantees a single main instance regardless of the source.

Duplicate initialization is prevented by a static weak reference and forwarding new Intents via onNewIntent .

Other Activities should avoid special launch modes and should be marked non‑exported when appropriate.

Handle orientation changes carefully to keep the single‑instance guarantee.

mobile developmentActivityAndroidDispatcherLaunchModeTaskStack
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

0 followers
Reader feedback

How this landed with the community

login 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.