Mobile Development 6 min read

Adding a Persistent Floating Button to Every Android Screen via DecorView Instead of WindowManager

The article explains why using WindowManager for floating buttons on Android is problematic across versions, demonstrates how the JD.com app adds a floating view directly to the Activity's DecorView, and provides a complete lifecycle‑aware implementation for Beike's app using ActivityLifecycleCallbacks and view injection.

Beike Product & Technology
Beike Product & Technology
Beike Product & Technology
Adding a Persistent Floating Button to Every Android Screen via DecorView Instead of WindowManager

Background: The Beike real‑estate Android app needs a floating button on every screen. The initial approach used WindowManager, but recent Android versions (6.0‑8.0) impose strict permissions for system‑level window types, causing BadTokenException and invisible windows.

Problem Cause: Android restricts WindowManager types (SYSTEM_ALERT, TYPE_PHONE, etc.) unless the user explicitly grants the floating‑window permission, which is disabled by default. Debugging these restrictions often leads to crashes or missing UI elements.

Technical Benchmark: By inspecting the JD.com app with UIAutomatorViewer, it was discovered that JD does not use WindowManager. Instead, it adds a child View directly to the root FrameLayout (DecorView) of each Activity, making the floating UI independent of system window permissions.

Solution: Implement a global ActivityLifecycleCallbacks listener in the Application class. In onActivityStarted , after setContentView has created the DecorView, check whether the floating view already exists; if not, inflate the custom layout, configure its click behavior, and add it to the root FrameLayout with appropriate layout parameters. This approach works for both normal and plugin‑based modules because all UI runs in the same process context.

Core Code:

((Application)mApplicationContext).registerActivityLifecycleCallbacks(
        new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {}
            @Override
            public void onActivityStarted(final Activity activity) {
                if (TextUtils.isEmpty(sBackName) || TextUtils.isEmpty(sBackUrl)) {
                    return;
                }
                FrameLayout root = (FrameLayout) activity.getWindow().getDecorView();
                View linkView = root.findViewById(R.id.ll_deeplink_beike);
                if (linkView == null) {
                    View view = UIUtils.inflate(R.layout.layout_baidu_deeplink_window, null);
                    TextView tvBackName = (TextView) view.findViewById(R.id.tv_back_name);
                    LinearLayout ltBack = (LinearLayout) view.findViewById(R.id.lt_back);
                    tvBackName.setText(UIUtils.getString(R.string.back_baidu, sBackName));
                    ltBack.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            Intent intent = new Intent();
                            intent.setData(Uri.parse(sBackUrl));
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            try { activity.startActivity(intent); } catch (ActivityNotFoundException ex) { ex.printStackTrace(); }
                        }
                    });
                    FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                            ViewGroup.LayoutParams.WRAP_CONTENT);
                    layoutParams.topMargin = (int)(activity.getResources().getDisplayMetrics().heightPixels * 0.75);
                    layoutParams.leftMargin = 0;
                    root.addView(view, layoutParams);
                } else {
                    // do nothing
                }
            }
            @Override public void onActivityResumed(Activity activity) {}
            @Override public void onActivityPaused(Activity activity) {}
            @Override public void onActivityStopped(Activity activity) {}
            @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
            @Override public void onActivityDestroyed(Activity activity) {}
        });

Conclusion: When a floating‑window feature is required, prefer adding a View to the Activity's DecorView rather than using WindowManager, as the former avoids permission issues across Android versions. Comparing with other products helps uncover simpler, more robust implementations.

mobile developmentAndroidWindowManagerActivityLifecycleDecorViewFloatingButton
Beike Product & Technology
Written by

Beike Product & Technology

As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.

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.