Android10剪贴板Clipboard的适配和解决方案

概述

Android10(Q)开始对剪贴板增加了限制,当应用没有获取到焦点的时候,无法获取剪贴板内容。
对于以上限制来说,于普通用户是百利而无一害的,毕竟我们在用手机的时候,复制个东西都可能被别人知道,想想还是挺可怕的。
对于开发人员来说,想要再监听剪贴板的变化就要做出一些牺牲和适配了。如果非必须,最好不要去监听,你好我好大家好。
说了些废话,如果不是必须,也不会有这个文章的研究了,但基本的职业操守还是要遵守的。

监听剪贴板变化

ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() {
  @Override
  public void onPrimaryClipChanged() {
     if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClip().getItemCount() > 0) {
    	CharSequence txt = clipboard.getPrimaryClip().getItemAt(0).getText();
   		String result = String.valueOf(txt);
    	//result即为拿到的剪贴板上的数据,后续根据需要来作处理即可
	 }
  }
});

上面提到过10开始不再让后台监听了,即还是可以监听,但仅限于默认输入法和当前页面可以监听
如果我想拿到剪贴板数据办呢?

方案一:在onResume中,通过post延时到界面拥有焦点时读取剪切板

也就是在页面恢复时,延迟个1秒杀左右再去检查剪贴板内容

@Override
protected void onResume() {
    super.onResume();
    new Handler().postDelayed(new Runnable() {
        public void run() {
            ClipboardManager cm = (ClipboardManager) MainActivity.this.getSystemService(Context.CLIPBOARD_SERVICE);
            if (!cm.hasPrimaryClip()) {
                return;
            }
            //剪切板操作
            ...
    }, 1000);
}

还有一种是在界面焦点发生变化时,具体没试过,供参考

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        //获取剪切板内容逻辑写到这里。
    }
}

方案二:借助悬浮窗开启前台服务监听

有弊端,个人应用测试使用可以,正式环境还是不要这么做了,只给有这方面需求的朋友参考一下

悬浮窗的创建

需要注意的是Flags的设定,只要一个FLAG_NOT_TOUCH_MODAL就好了,一定不要有FLAG_NOT_FOCURABLE即不能让悬浮窗的焦点离开
但以上设定会有一个问题,就是返回操作等会失效,因为焦点在悬浮窗上,只能通过点击应用本身的返回按钮来解决。

layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

创建前台服务

其实就是创建一个通知,重点是在通知中开启前台服务startForeground()并在onDestroy()中关闭stopForeground(true)

private void createNotification() {
    String channelId = getPackageName() + System.currentTimeMillis();
    NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channelId).setAutoCancel(true);
    builder.setContentText("悬浮监听剪贴板")
            .setWhen(System.currentTimeMillis())
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setOngoing(false)
            .setContentIntent(null)
            .setDefaults(NotificationCompat.DEFAULT_ALL);
    NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    NotificationChannel channel = new NotificationChannel(channelId, getPackageName(), NotificationManager.IMPORTANCE_HIGH);
    manager.createNotificationChannel(channel);
    startForeground(100, builder.build());
}
@Override
public void onDestroy() {
    stopForeground(true);
    super.onDestroy();
}

完整代码参考

public class FloatClipboardService extends Service {
    private View mView;
    private WindowManager windowManager;
    
    @Override
    public void onDestroy() {
        if (mView != null) windowManager.removeView(mView);
        MyApplication.isFloatClipboardShow = false;
        stopForeground(true);
        super.onDestroy();
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        createNotification();
        initWindow();
        return super.onStartCommand(intent, flags, startId);
    }
    
	private void createNotification() {
	   String channelId = getPackageName() + System.currentTimeMillis();
	   NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channelId).setAutoCancel(true);
	   builder.setContentText("悬浮监听剪贴板")
	           .setWhen(System.currentTimeMillis())
	           .setPriority(NotificationCompat.PRIORITY_DEFAULT)
	           .setOngoing(false)
	           .setContentIntent(null)
	           .setDefaults(NotificationCompat.DEFAULT_ALL);
	   NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
	   NotificationChannel channel = new NotificationChannel(channelId, getPackageName(), NotificationManager.IMPORTANCE_HIGH);
	   manager.createNotificationChannel(channel);
	   startForeground(100, builder.build());
	}
	
    private void initWindow() {
        if (Settings.canDrawOverlays(this)) {
            windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
            //region 设置LayoutParams
            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
            layoutParams.format = PixelFormat.RGBA_8888; //背景透明效果
            // 悬浮窗口长宽值,单位为 px 而非 dp
            layoutParams.width = dip2px(95);
            layoutParams.height = dip2px(45);
            layoutParams.gravity = 51; //想要x,y生效,一定要指定Gravity为top和left //Gravity.TOP | Gravity.LEFT
            // 启动位置
            layoutParams.x = 128;
            layoutParams.y = 128;
            //endregion

            //加载悬浮窗布局
            FloatClipboardBinding floatView = FloatClipboardBinding.inflate(LayoutInflater.from(FloatClipboardService.this));
            mView = floatView.getRoot();
            mView.setAlpha((float) 0.8);

            // 悬浮窗控件事件
            floatView.btnFloatClipboardClose.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    stopSelf();
                }
            });
            // 监听剪贴板
            floatView.tvFloatClipboardContent.setText("");
            ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            clipboardManager.addPrimaryClipChangedListener(new ClipboardManager.OnPrimaryClipChangedListener() {
                Long cur;
                @Override
                public void onPrimaryClipChanged() {
                    if(cur != null){
                        if(System.currentTimeMillis() - cur < 1500) return;
                    }
                    cur = System.currentTimeMillis();
                    if (clipboardManager.hasPrimaryClip() && clipboardManager.getPrimaryClip().getItemCount() > 0) {
                        CharSequence txt = clipboardManager.getPrimaryClip().getItemAt(0).getText();
                        String str = PubUtil.getUrl(String.valueOf(txt));
                        if (!TextUtils.isEmpty(str)) {
                            floatView.tvFloatClipboardContent.setText(str);
                            String content = "\n" + str;
                            File clipboardFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + File.separator + "Clipboard.txt");
                            // 由于在载入时就检查了文件是否存在,此处不再作检查
                            try {
                                FileOutputStream fos = new FileOutputStream(clipboardFile, true);
                                fos.write(content.getBytes());
                                fos.close();
                                floatView.tvFloatClipboardStatus.setText("保存成功");
                            } catch (IOException e) {
                                floatView.tvFloatClipboardStatus.setText("保存失败");
                                floatView.tvFloatClipboardContent.setText(e.toString());
                            }
                        }
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                floatView.tvFloatClipboardStatus.setText("等待复制");
                                floatView.tvFloatClipboardContent.setText("");
                            }
                        }, 1500);
                    }
                }
            });
            //加载悬浮窗到窗口管理器
            windowManager.addView(mView, layoutParams);
            MyApplication.isFloatClipboardShow = true;
        }
    }
    
    private int dip2px(int dipValue) {
        float density = this.getResources().getDisplayMetrics().density;
        return (int) (dipValue * density + 0.5f);
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值