You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 10+前台服务无法访问剪贴板内容的解决方案求助

Android 10+ 前台悬浮窗访问剪贴板问题解决

问题描述

Android 10及以上系统中,后台服务无法访问剪贴板内容,但有应用能通过前台服务在Android 10+设备的悬浮窗中显示复制的文本。我的应用在Android P及以下设备运行正常,希望实现该功能,请求帮助!

当前代码

FloatingViewService.java

public class FloatingViewService extends Service implements View.OnClickListener {

private WindowManager wm;
private View floatv;
View collapsed;
View expanded;
CharSequence copiedText;
EditText editText;

public FloatingViewService(){

}

@Override
public void onCreate() {
    super.onCreate();


    // 获取悬浮窗布局
    floatv  = LayoutInflater.from(this).inflate(R.layout.layout_floating_widget,null);


    // 设置WindowManager布局参数
    final WindowManager.LayoutParams wrules = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
    );


    // 添加悬浮窗到WindowManager
    wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    wm.addView(floatv,wrules);



    // 绑定视图控件
    collapsed =  floatv.findViewById(R.id.layoutCollapsed);
    expanded =  floatv.findViewById(R.id.layoutExpanded);
    editText = floatv.findViewById(R.id.pastehere);




    // 添加拖拽监听
    floatv.findViewById(R.id.relativeLayoutParent).setOnTouchListener(new View.OnTouchListener() {
        private int initialX;
        private int initialY;
        private float touchX;
        private float touchY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
               switch (event.getAction()){
                   case MotionEvent.ACTION_DOWN:
                        initialX = wrules.x;
                        initialY = wrules.y;
                        touchX = event.getRawX();
                        touchY = event.getRawY();
                        return true;

                   case MotionEvent.ACTION_UP:

                      int diffX = (int) (event.getRawX() - touchX);
                      int diffY = (int)  (event.getRawY() - touchY);

                      if(diffX < 10 && diffY < 10){

                          if (isViewCollapsed()){
                              collapsed.setVisibility(View.GONE);
                              expanded.setVisibility(View.VISIBLE);
                              wrules.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
                              wm.updateViewLayout(floatv,wrules);
                          }else {
                              collapsed.setVisibility(View.VISIBLE);
                              expanded.setVisibility(View.GONE);
                              wrules.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
                              wm.updateViewLayout(floatv,wrules);
                          }

                      }



                       return true;

                   case MotionEvent.ACTION_MOVE:
                       // 移动悬浮窗
                       wrules.x = initialX + (int) (event.getRawX() - touchX);
                       wrules.y = initialY +  (int) (event.getRawY() - touchY);
                       wm.updateViewLayout(floatv,wrules);


                       return true;


               }

            return false;
        }
    });


    // 监听剪贴板变化
    ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);

    clipboardManager.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() {
        @Override
        public void onPrimaryClipChanged() {
            copiedText = clipboardManager.getPrimaryClip().getItemAt(0).getText();

            if(copiedText != null){
                editText.setText(String.valueOf(copiedText));
                wm.updateViewLayout(floatv,wrules);
            }
            Toast.makeText(getBaseContext(), copiedText, Toast.LENGTH_SHORT).show();
        }
    });

}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onDestroy() {
        super.onDestroy();
        if(floatv != null) wm.removeView(floatv);
}

@Override
public void onClick(View v) {
      if(v.getId() == R.id.layoutExpanded){
          collapsed.setVisibility(View.VISIBLE);
          expanded.setVisibility(View.GONE);
      }else if(v.getId() == R.id.buttonClose){
          stopSelf();
    }
}

private boolean isViewCollapsed() {
    return floatv == null || floatv.findViewById(R.id.layoutCollapsed).getVisibility() == View.VISIBLE;
}

layout_floating_widget.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">


    <RelativeLayout
        android:id="@+id/relativeLayoutParent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <!-- 折叠状态布局 -->
        <RelativeLayout
            android:id="@+id/layoutCollapsed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:visibility="visible">

            <ImageView
                android:id="@+id/collapsed_iv"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:layout_marginTop="8dp"
                android:src="@drawable/ic_baseline_menu_book_24" />

            <ImageView
                android:id="@+id/buttonClose"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:layout_marginLeft="50dp"
                android:src="@drawable/ic_baseline_close_24"
                android:background="@color/teal_200"/>
        </RelativeLayout>

        <!-- 展开状态布局 -->
        <LinearLayout
            android:id="@+id/layoutExpanded"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#a0c3d7"
            android:orientation="horizontal"
            android:padding="8dp"
            android:visibility="gone">

            <ImageView
                android:id="@+id/buttonSimplifiedCodingExpanded"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:src="@drawable/ic_baseline_markunread_24"
                tools:ignore="ContentDescription" />

            <LinearLayout
                android:id="@+id/buttonSimplifiedCoding"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:paddingLeft="15dp"
                    android:paddingRight="15dp"
                    android:paddingTop="15dp"
                    android:text="Dictionary"
                    android:textAlignment="center"
                    android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
                    android:textColor="#ffffff"
                    android:textStyle="bold" />

                <EditText
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:id="@+id/pastehere"
                    android:textAlignment="center"
                    android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium"
                    android:textColor="#ffffff"
                    android:textStyle="bold" />

            </LinearLayout>

        </LinearLayout>
    </RelativeLayout>
</FrameLayout>

效果说明

  • 悬浮窗折叠状态示意图
  • 文本应显示在EditText中的效果(该功能在Android 9正常)

问题原因

Android 10(API 29)起,系统限制后台服务访问剪贴板,仅前台可见的应用能获取剪贴板内容。当前服务虽为悬浮窗服务,但未标记为前台服务,被系统判定为后台,因此无法访问剪贴板。

解决方案

1. 将服务转为前台服务

在FloatingViewService的onCreate方法开头添加前台服务初始化逻辑:

@Override
public void onCreate() {
    super.onCreate();

    // 创建前台服务通知通道(Android 8.0+ 必需)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(
                "FLOATING_CLIPBOARD_CHANNEL",
                "悬浮窗剪贴板监听",
                NotificationManager.IMPORTANCE_LOW
        );
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        notificationManager.createNotificationChannel(channel);
    }

    // 构建前台通知
    Notification notification = new NotificationCompat.Builder(this, "FLOATING_CLIPBOARD_CHANNEL")
            .setSmallIcon(R.drawable.ic_baseline_menu_book_24)
            .setContentTitle("剪贴板监听中")
            .setContentText("悬浮窗正在监听复制内容")
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .build();

    // 启动前台服务,通知ID不能为0
    startForeground(1, notification);

    // 原有初始化代码...
}

2. 调整悬浮窗焦点参数

展开悬浮窗时添加焦点标记,让系统识别应用处于前台:
修改触摸监听中ACTION_UP分支的代码:

if (isViewCollapsed()){
    collapsed.setVisibility(View.GONE);
    expanded.setVisibility(View.VISIBLE);
    // 添加FLAG_FOCUSABLE,让悬浮窗获取焦点
    wrules.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_FOCUSABLE;
    wm.updateViewLayout(floatv,wrules);
}else {
    collapsed.setVisibility(View.VISIBLE);
    expanded.setVisibility(View.GONE);
    wrules.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    wm.updateViewLayout(floatv,wrules);
}

3. 补充必要权限

在AndroidManifest.xml中添加权限:

<!-- 悬浮窗权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<!-- 前台服务权限(Android 8.0+) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- 通知权限(Android 13+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

启动服务前需动态申请悬浮窗权限:

if (!Settings.canDrawOverlays(this)) {
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
            Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, 1001);
}

4. 优化剪贴板监听逻辑

添加前台状态检查,避免无效监听:

private ClipboardManager clipboardManager;
private ClipboardManager.OnPrimaryClipChangedListener clipChangeListener;

@Override
public void onCreate() {
    super.onCreate();
    // ... 前台服务初始化代码

    clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
    clipChangeListener = new ClipboardManager.OnPrimaryClipChangedListener() {
        @Override
        public void onPrimaryClipChanged() {
            // Android 10+ 检查是否处于前台
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                ActivityManager.RunningAppProcessInfo processInfo = new ActivityManager.RunningAppProcessInfo();
                ActivityManager.getMyMemoryState(processInfo);
                if (processInfo.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                    return;
                }
            }

            ClipData clipData = clipboardManager.getPrimaryClip();
            if (clipData != null && clipData.getItemCount() > 0) {
                CharSequence text = clipData.getItemAt(0).getText();
                if (text != null) {
                    editText.setText(text);
                    wm.updateViewLayout(floatv, wrules);
                    Toast.makeText(getBaseContext(), text, Toast.LENGTH_SHORT).show();
                }
            }
        }
    };
    clipboardManager.addPrimaryClipChangedListener(clipChangeListener);
}

@Override
public void onDestroy() {
    super.onDestroy();
    // 注销监听,避免内存泄漏
    if (clipboardManager != null && clipChangeListener != null) {
        clipboardManager.removePrimaryClipChangedListener(clipChangeListener);
    }
    if (floatv != null) {
        wm.removeView(floatv);
    }
}

验证说明

修改后启动服务,通知栏会显示前台服务通知,此时悬浮窗处于前台状态,Android 10+系统将允许访问剪贴板,复制的文本即可正常显示在EditText中。

内容的提问来源于stack exchange,提问作者HS Nain.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 22:06:34