概述
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);
}
}